Numbaを用いたPython/Numpyの高速化
まえがき
機械学習関連のプロジェクトではPythonを使う人が多いと思うのですが、Pythonでの処理って遅いですよね。
Tensorflowでの学習などにおいてはPython側で計算をしているわけではないので大丈夫なのですが、前処理の際などにPythonの実行速度の遅さがとても気になります。コンパイラ言語とスクリプト言語を比べるのは酷ですが、「C++なら数秒で終わるのになあ」という処理も数分かかってしまったりします。
もちろんC++のプログラムに匹敵するほどの速度を出すのは無理ですが、できるだけ高速化したいですよね。 そのための方法はいろいろあるのですが、今回はNumbaというJITコンパイラを用いる方法を紹介したいと思います。
Numbaとは
公式サイトによると、
Numba is an open source JIT compiler that translates a subset of Python and NumPy code into fast machine code.
「Python/Numpyコード(の一部)を、高速な機械語に変換するためのオープンソースJITコンパイラ」ってことみたいですね。
もう少し細かい特徴としては以下のような感じです。
- 関数にデコレータをつけるだけで動作する
- Numpyの配列や関数を用いた処理に特化している
- ランタイム時に、最適化された機械語を作成して実行してくれる
- Numbaを利用するためにPythonのインタープリターを変えたり、コードの実行前にコンパイルするようにしたりする必要がない
- CPU/GPUを用いた並列計算のための機能もある
実際に使ってみる
高速化前のコード
以下のような関数があったとします。
配列(arrとします)を受け取り、[log(arr[1]/arr[0]), log(arr[2]/arr[1])... ]
のような(長さがarrより1短い)配列を返します。
def calculate_diffs(arr): ret = numpy.empty(arr.size - 1) for i in range(arr.size - 1): ret[i] = numpy.log(arr[i + 1] / arr[i]) return ret
以下のコードを用いて測定をすると(かなり雑ですが、今回はだいたいのパフォーマンスの比較ということで...)、出力は 183.20000410079956
となりました。約3分です。
なお関数内のnumpy.empty(...)
の部分は計算とは関係ありませんがe-5秒くらいのオーダーだったので無視してよさそうです。(ちなみにですがnumpy.random.rand(...)
の部分は無視できないほど時間がかかります。)
import time import numpy # 以上の関数定義 if __name__ == '__main__': arr = numpy.random.rand(10 ** 8) start_t = time.time() calculate_diffs(arr) end_t = time.time() print(end_t - start_t)
JITを有効化してみる
以下のようにデコレータをつけて、JITを有効化してみます。
@numba.jit(nopython=True) def calculate_diffs(arr): ret = numpy.empty(arr.size - 1) for i in range(arr.size - 1): ret[i] = numpy.log(arr[i + 1] / arr[i]) return ret
測定の結果、出力は1.805896282196045
となり、100倍ほどの高速化に成功しました。
JITを有効化/並列化してみる
以下のようにデコレータを変更し、range関数をnumba.prange関数に置き換え、並列化してみます。
ret = numpy.empty(arr.size - 1)
の部分は、numbaが勝手にうまいこと(hoist)してくれて、期待通りの動作をします。
@numba.jit(nopython=True, parallel=True) def calculate_diffs(arr): ret = numpy.empty(arr.size - 1) for i in numba.prange(arr.size - 1): ret[i] = numpy.log(arr[i + 1] / arr[i]) return ret
測定の結果、出力は0.7335679531097412
となり、200倍以上の高速化に成功したことになります。
まとめ
限られたケースにはなりますが、Numbaをうまく使えば非常に簡単に高速化ができることがわかりました。