Pythonを高速化したい人は、是非ともご覧ください。
冗談抜きで、本当に高速になります。
Pythonコードのコンパイルと聞くと、大変そうに思うかもしれません。
でも、 Numbaを利用すれば、全然大変ではありません。
Numbaのインストールも利用も、普通に簡単です。
本記事の内容
- Numbaとは?
- Numbaのシステム要件
- Numbaのインストール
- Numbaの動作確認
それでは、上記に沿って解説していきます。
Numbaとは?
Numba公式
https://numba.pydata.org/
Numbaは、Pythonのコードを高速なマシンコードに変換するJITコンパイラとなります。
正確には、NumbaはPythonのライブラリですけどね。
高速化する対象は、演算処理部分(Numpy)となります。
Numbaを利用すれば、Pythonの数値アルゴリズムをCやFORTRANの速度に近づけることが可能とのこと。
それであるのに、Numbaの利用方法はとても簡単と言えます。
@jit('void(double[:], double[:], double[:])', nopython=True, nogil=True)
上記のような呪文をコードに記述するだけです。
これだけで数十倍も処理が高速になったら、嬉しくないですか?
これは、嘘のような本当の話なのです。
試してみるのは簡単なので、是非とも試してみてください。
詳細は後述しますが、ソフトウェアを別途インストールする必要はありません。
パスを通したり、設定ファイルを変更することもありません。
あくまで、ライブラリをインストールするだけです。
以上、 Numbaについて説明しました。
次は、Numbaのシステム要件を確認しましょう。
Numbaのシステム要件
現時点(2021年7月)でのNumbaの最新バージョンは、0.53.1となります。
この最新バージョンは、2021年3月26日にリリースされています。
サポートOSに関しては、以下を含むクロスプラットフォーム対応です。
- Windows
- macOS
- Linux
基本的には、IntelのCPUで動くことが想定されています。
Linuxの場合は、ARMでも動くようです。
そして、サポート対象となるPythonのバージョンは以下。
- Python 3.6
- Python 3.7
- Python 3.8
- Python 3.9
これは、下記のPythonの公式開発サイクル通りです。
バージョン | リリース日 | サポート期限 |
3.6 | 2016年12月23日 | 2021年12月 |
3.7 | 2018年6月27日 | 2023年6月 |
3.8 | 2019年10月14日 | 2024年10月 |
3.9 | 2020年10月5日 | 2025年10月 |
やはり、Anacondaがスポンサーであるだけはありますね。
このようなところは、ちゃんとしています。
あとは、次の二つが公式には明示されています。
- llvmlite 0.36.*
- NumPy >=1.15
すでにインストール済みなら、バージョンには注意が必要です。
まだ、未インストールであれば、無視してOK。
自動的にpipで必要なモノをインストールしてくれます。
以上、Numbaのシステム要件についての説明でした。
次は、Numbaをインストールしていきます。
Numbaのインストール
最初に、Pythonのバージョンを確認しておきます。
>python -V Python 3.9.6
次に、現状のインストール済みパッケージを確認します。
>pip list Package Version ---------- ------- pip 21.1.3 setuptools 57.2.0
次にするべきことは、pipとsetuptoolsの更新です。
pipコマンドを使う場合、常に以下のコマンドを実行しておきましょう。
python -m pip install --upgrade pip setuptools
では、Numbaのインストールです。
Numbaのインストールは、以下のコマンドとなります。
pip install numba
インストールは、少しだけ時間がかかります。
では、どんなパッケージがインストールされたのかを確認しましょう。
>pip list Package Version ---------- ------- llvmlite 0.36.0 numba 0.53.1 numpy 1.21.0 pip 21.1.3 setuptools 57.2.0
依存するnumpyとllvmliteのパッケージが、インストールされています。
そして、「llvmlite.dll」が自動的にダウンロード済みです。
※Linuxの場合は「libllvmlite.so」となる
したがって、LLVM(ソフトウェア)をわざわざインストールする必要がありません。
環境が汚れないので、素晴らしいです。
以上、Numbaのインストールについて説明しました。
次は、Numbaの動作確認を行います。
Numbaの動作確認
公式から、以下のサンプルコードを持ってきました。
このコードを使うと、Numbaの動作確認が可能です。
さらには、Numbaの威力も体感できます。
実際に試す場合には、CPUのコア数に注意してください。
import math import threading from timeit import repeat import numpy as np from numba import jit nthreads = 4 size = 10**6 def func_np(a, b): """ Control function using Numpy. """ return np.exp(2.1 * a + 3.2 * b) @jit('void(double[:], double[:], double[:])', nopython=True, nogil=True) def inner_func_nb(result, a, b): """ Function under test. """ for i in range(len(result)): result[i] = math.exp(2.1 * a[i] + 3.2 * b[i]) def timefunc(correct, s, func, *args, **kwargs): """ Benchmark *func* and print out its runtime. """ print(s.ljust(20), end=" ") # Make sure the function is compiled before the benchmark is # started res = func(*args, **kwargs) if correct is not None: assert np.allclose(res, correct), (res, correct) # time it print('{:>5.0f} ms'.format(min(repeat( lambda: func(*args, **kwargs), number=5, repeat=2)) * 1000)) return res def make_singlethread(inner_func): """ Run the given function inside a single thread. """ def func(*args): length = len(args[0]) result = np.empty(length, dtype=np.float64) inner_func(result, *args) return result return func def make_multithread(inner_func, numthreads): """ Run the given function inside *numthreads* threads, splitting its arguments into equal-sized chunks. """ def func_mt(*args): length = len(args[0]) result = np.empty(length, dtype=np.float64) args = (result,) + args chunklen = (length + numthreads - 1) // numthreads # Create argument tuples for each input chunk chunks = [[arg[i * chunklen:(i + 1) * chunklen] for arg in args] for i in range(numthreads)] # Spawn one thread per chunk threads = [threading.Thread(target=inner_func, args=chunk) for chunk in chunks] for thread in threads: thread.start() for thread in threads: thread.join() return result return func_mt func_nb = make_singlethread(inner_func_nb) func_nb_mt = make_multithread(inner_func_nb, nthreads) a = np.random.rand(size) b = np.random.rand(size) correct = timefunc(None, "numpy (1 thread)", func_np, a, b) timefunc(correct, "numba (1 thread)", func_nb, a, b) timefunc(correct, "numba (%d threads)" % nthreads, func_nb_mt, a, b)
上記のままだと、Numbaが機能します。
以下のコードを無効(コメントアウト)にすると、Numbaは機能しません。
@jit('void(double[:], double[:], double[:])', nopython=True, nogil=True)
では、それぞれの結果を確認しましょう。
なお、利用マシンのCPUはインテル Core i9 9900KFです。
Numbaあり | Numbaなし |
numpy (1 thread) 55 ms numba (1 thread) 31 ms numba (4 threads) 13 ms | numpy (1 thread) 59 ms numba (1 thread) 1932 ms numba (4 threads) 1956 ms |
興味深い結果が、出ています。
- numba (1 thread)の結果
- numba (4 threads)の結果
それぞれを見ていきます。
numba (1 thread)の結果
ここの値を比較すれば、1コアにおけるNumbaの威力を確認できます。
単純に、62倍の処理速度の差があると言えます。
1932 ÷ 31 = 62.32・・・
大々的には、この数字を言うことが適切なのでしょう。
numba (4 threads)の結果
ここの値を比較すれば、4コアにおけるNumbaの威力を確認できます。
並列処理の場合には、Numbaはさらにその効果を発揮すると言えます。
1956 ÷ 13 = 150.46・・・
処理速度の差は、150倍です。
まとめ
Numbaの偉大さが、十分にわかる結果となりました。
62倍~150倍の性能差を確認できました。
この結果を見ると、Numbaを利用しないという選択肢はありませんね。