このコードはどのように最適化しますか(withoutこれは、計算のセマンティクスを使用することにつながります。多くの場合、自明ではありません):
slow_lib.py:
import numpy as np
def foo():
size = 200
np.random.seed(1000031212)
bar = np.random.Rand(size, size)
moo = np.zeros((size,size), dtype = np.float)
for i in range(0,size):
for j in range(0,size):
val = bar[j]
moo += np.outer(val, val)
ポイントは、そのような種類のループは、いくつかのベクトル演算で二重和がある演算にかなり頻繁に対応することです。
これはかなり遅いです:
>>t = timeit.timeit('foo()', 'from slow_lib import foo', number = 10)
>>print ("took: "+str(t))
took: 41.165681839
では、明日はそれをCynothizeして、型注釈を追加しましょう。明日はありません。
c_slow_lib.pyx:
import numpy as np
cimport numpy as np
import cython
@cython.boundscheck(False)
@cython.wraparound(False)
def foo():
cdef int size = 200
cdef int i,j
np.random.seed(1000031212)
cdef np.ndarray[np.double_t, ndim=2] bar = np.random.Rand(size, size)
cdef np.ndarray[np.double_t, ndim=2] moo = np.zeros((size,size), dtype = np.float)
cdef np.ndarray[np.double_t, ndim=1] val
for i in xrange(0,size):
for j in xrange(0,size):
val = bar[j]
moo += np.outer(val, val)
>>t = timeit.timeit('foo()', 'from c_slow_lib import foo', number = 10)
>>print ("took: "+str(t))
took: 42.3104710579
...えっ...何? Numbaが助けになりました!
numba_slow_lib.py:
import numpy as np
from numba import jit
size = 200
np.random.seed(1000031212)
bar = np.random.Rand(size, size)
@jit
def foo():
bar = np.random.Rand(size, size)
moo = np.zeros((size,size), dtype = np.float)
for i in range(0,size):
for j in range(0,size):
val = bar[j]
moo += np.outer(val, val)
>>t = timeit.timeit('foo()', 'from numba_slow_lib import foo', number = 10)
>>print("took: "+str(t))
took: 40.7327859402
これをスピードアップする方法は本当にないのですか?ポイントは:
outer
のコードは次のとおりです:
_def outer(a, b, out=None):
a = asarray(a)
b = asarray(b)
return multiply(a.ravel()[:, newaxis], b.ravel()[newaxis,:], out)
_
したがって、outer
への各呼び出しには、多数のpython呼び出しが含まれます。これらの呼び出しは、最終的に乗算を実行するためにコンパイルされたコードを呼び出します。ただし、それぞれのサイズとは関係のないオーバーヘッドが発生します。アレイ。
したがって、outer
への200(200 ** 2?)呼び出しはすべてのオーバーヘッドを持ちますが、すべての200行を含むouter
への1回の呼び出しには1つのオーバーヘッドセットがあり、その後に1つの高速コンパイル操作が続きます。
cython
とnumba
はコンパイルしないか、Pythonのコードをouter
でバイパスしません。それらができることは、あなたが書いた-そしてそれは多くの時間を消費していません。
詳細に踏み込むことなく、MATLAB jitは「外部」をより高速なコードに置き換えることができなければなりません-反復を書き換えます。しかし、MATLABでの私の経験は、そのjitより前の時代のものです。
cython
とnumba
で実際の速度を向上させるには、プリミティブなnumpy/pythonコードを使用する必要があります。またはより良いまだ遅いインナーピースに努力を集中します。
outer
を合理化されたバージョンに置き換えると、実行時間が約半分になります。
_def foo1(N):
size = N
np.random.seed(1000031212)
bar = np.random.Rand(size, size)
moo = np.zeros((size,size), dtype = np.float)
for i in range(0,size):
for j in range(0,size):
val = bar[j]
moo += val[:,None]*val
return moo
_
完全な_N=200
_では、関数はループごとに17秒かかりました。内側の2行をpass
(計算なし)に置き換えると、ループあたりの時間が3msに減少します。言い換えると、外側のループメカニズムは、少なくともouter()
への多くの呼び出しと比較して、それほど大きな時間の消費者ではありません。
メモリが許せば使用できます np.einsum
これらの重い計算をベクトル化された方法で実行するには-
moo = size*np.einsum('ij,ik->jk',bar,bar)
moo = size*np.tensordot(bar,bar,axes=(0,0))
または単にnp.dot
-
moo = size*bar.T.dot(bar)
Cython、Numbaなどの多くのチュートリアルとデモンストレーションでは、これらのツールがコードを自動的に高速化できるように見えますが、実際にはそうではない場合がほとんどです。コードを少し変更して、最適なコードを抽出する必要があります。パフォーマンス。すでにある程度のベクトル化を実装している場合、通常はすべてのループを書き出すことを意味します。 Numpy配列演算が最適ではない理由は次のとおりです。
NumbaまたはCythonを使用しても、これらの問題は最適化されません。代わりに、これらのツールを使用すると、単純なPythonよりもはるかに高速なループ状のコードを作成できます。
また、Numbaの場合は特に、 "オブジェクトモード"と "nopythonモード" の違いに注意する必要があります。例のタイトなループは、大幅なスピードアップを提供するためにnopythonモードで実行する必要があります。ただし、_numpy.outer
_は まだNumbaではサポートされていません であるため、関数はオブジェクトモードでコンパイルされます。 jit(nopython=True)
で装飾して、そのような場合に例外をスローできるようにします。
スピードアップを実証する例は確かに可能です:
_import numpy as np
from numba import jit
@jit
def foo_nb(bar):
size = bar.shape[0]
moo = np.zeros((size, size))
for i in range(0,size):
for j in range(0,size):
val = bar[j]
moo += np.outer(val, val)
return moo
@jit
def foo_nb2(bar):
size = bar.shape[0]
moo = np.zeros((size, size))
for i in range(size):
for j in range(size):
for k in range(0,size):
for l in range(0,size):
moo[k,l] += bar[j,k] * bar[j,l]
return moo
size = 100
bar = np.random.Rand(size, size)
np.allclose(foo_nb(bar), foo_nb2(bar))
# True
%timeit foo_nb(bar)
# 1 loop, best of 3: 816 ms per loop
%timeit foo_nb2(bar)
# 10 loops, best of 3: 176 ms per loop
_