導入
Pythonの数値計算ライブラリNumPyのarrayを使って、標準のlistを単純に置き換えてみると遅くなることが多いです。
実際どれぐらい速度に差があるのかlist, array, numpy.arrayで比較してみました。
処理時間の計測
Pythonに含まれるライブラリのtimeitを使います。
100000個の要素(すべてが1)を持つ配列の各要素の総和を1000回計算した時の処理時間を求めます。
forループの場合(イテレータでアクセスした場合)とsum関数、numpy.sum関数を使った場合について調べています。
ソースコードは一番下にあります。
使ったバージョンはPython 2.7.2, NumPy 1.6.2。
結果は以下のとおり(単位は秒)。
for | sum | numpy.sum | |
---|---|---|---|
list | 3.17386984825 | 0.524603128433 | 9.56173205376 |
array | 6.51892805099 | 4.21408200264 | 15.1993541718 |
numpy.array | 30.0375761986 | 25.0769600868 | 0.0625190734863 |
numpy.arrayに対してnumpy.sumしたものが一番速く、対照的にnumpy.arrayに対してforループしたものが一番遅くなっています。
forループの遅さを見るとnumpyを使うときはnumpy.sum関数などのnumpy用の関数やベクトル・行列演算を駆使しないとまずそうです。
逆にlistはforループでもsumでもそこそこの性能を出していて、メモリ効率などを気にしなければ意外とlistもよさそうです。
sum関数とnumpy.sum関数の結果は面白いことになっていて、numpy.arrayについてはnumpy.sumが速いんですが、それ以外はsumが速いです。
おそらくnumpyの関数をlistなどに使うときはnumpy.arrayに変換してるから遅くなるんでしょう。
numpy用の関数とそうでない関数を使い間違えたら結構遅くなりそうです。
PyPy編
ものすごく大雑把に説明すると、PyPyという高速なPythonのインタプリタがあって、単純なスクリプトなら少し書き換えるだけで高速に実行できます。
たとえばNumPyを使うときは"import numpy"の代わりに"import numpypy"としなければいけません。
こちらでも速度を調べてみました。
ソースコードの"import numpy"を"import numpypy as numpy"に書き換えれば動きます。
使ったPyPyのバージョンは1.8.0(Python 2.7.2)
for | sum | numpy.sum | |
---|---|---|---|
list | 0.168792963028 | 0.170847892761 | 61.3819468021 |
array | 11.2487630844 | 7.62114405632 | 104.854725838 |
numpy.array | 0.155326843262 | 0.189166069031 | 0.0643138885498 |
listのforやnumpy(py)のforが大幅に速くなっています。
しかし、こちらでもlistやarrayにnumpy(py).sumをつかうと非常に遅くなることには注意が必要そうです。
結論
PyPy使おう!
……NumPyとかSciPyの機能で対応してないのたくさんあった気がしますが(ぇ
NumPyは行列演算とかだと速いんだと思いますが、forループで使うには向いてないっぽいです。
数式を行列演算に直す方法としては以下の記事が参考になります。
- 第2回 Tokyo.SciPy で「数式を numpy に落としこむコツ」を発表してきました - Mi manca qualche giovedi`?
- 「数式を numpy に落とし込むコツ」を HMM に当てはめてみる - Mi manca qualche giovedi`?
本当に速度が必要ならCythonとかC++を使ったほうがいいのかもしれませんが……。
実験設定や内容に誤りや変なところがあったらコメントかTwitterでご指摘ください。
よろしくお願いします
ソースコード
処理時間の計測
import timeit common_for = """ for d in data: s += d """ common_sum = """ sum(data) """ common_numpy_sum = """ numpy.sum(data) """ def timeit_list(n, iter): list_setup = """ import numpy data = [1] * {} s = 0 """.format(n) print 'list:', print timeit.timeit(common_for, list_setup, number = iter), print timeit.timeit(common_sum, list_setup, number = iter), print timeit.timeit(common_numpy_sum, list_setup, number = iter) def timeit_array(n, iter): array_setup = """ import numpy import array data = array.array('L', [1] * {}) s = 0 """.format(n) print 'array:', print timeit.timeit(common_for, array_setup, number = iter), print timeit.timeit(common_sum, array_setup, number = iter), print timeit.timeit(common_numpy_sum, array_setup, number = iter) def timeit_numpy(n, iter): numpy_setup = """ import numpy data = numpy.array([1] * {}) s = 0 """.format(n) print 'numpy.array:', print timeit.timeit(common_for, numpy_setup, number = iter), print timeit.timeit(common_sum, numpy_setup, number = iter), print timeit.timeit(common_numpy_sum, numpy_setup, number = iter) if __name__ == '__main__': timeit_list(100000, 1000) timeit_array(100000, 1000) timeit_numpy(100000, 1000)