唯物是真 @Scaled_Wurm

プログラミング(主にPython2.7)とか機械学習とか

Pythonのlistとnumpy.arrayとかの速度比較

導入

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ループで使うには向いてないっぽいです。
数式を行列演算に直す方法としては以下の記事が参考になります。

本当に速度が必要なら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)