[python] なぜnumpyの組み込み関数よりもnumpyの組み込み関数が速いのですか?



Answers

今度はnumpy 1.8がリリースされます。すべてのufuncsがSSE2を使用すべきドキュメントによれば、SSE2に関するSebergのコメントが有効であることを再確認したいと思います。

テストを行うために、新しいpython 2.7のインストールが作成されました。-numpy 1.7と1.8は、Ubuntuを実行するAMD opteronコアの標準オプションを使用してiccコンパイルされました。

これは、1.8アップグレードの前と後の両方で実行されるテストです。

import numpy as np
import timeit

arr_1D=np.arange(5000,dtype=np.double)
arr_2D=np.arange(500**2,dtype=np.double).reshape(500,500)
arr_3D=np.arange(500**3,dtype=np.double).reshape(500,500,500)

print 'Summation test:'
print timeit.timeit('np.sum(arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ijk->", arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Power test:'
print timeit.timeit('arr_3D*arr_3D*arr_3D',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ijk,ijk,ijk->ijk", arr_3D, arr_3D, arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Outer test:'
print timeit.timeit('np.outer(arr_1D, arr_1D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("i,k->ik", arr_1D, arr_1D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Einsum test:'
print timeit.timeit('np.sum(arr_2D*arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ij,oij->", arr_2D, arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'

ナンシー1.7.1:

Summation test:
0.172988510132
0.0934836149216
----------------------

Power test:
1.93524689674
0.839519000053
----------------------

Outer test:
0.130380821228
0.121401786804
----------------------

Einsum test:
0.979052495956
0.126066613197

ナンシー1.8:

Summation test:
0.116551589966
0.0920487880707
----------------------

Power test:
1.23683619499
0.815982818604
----------------------

Outer test:
0.131808176041
0.127472200394
----------------------

Einsum test:
0.781750011444
0.129271841049

SSEがタイミングの違いに大きな役割を果たしていることはかなり確信していると思いますが、これらのテストを繰り返すことは、タイミングが非常に〜0.003秒しかないことに注意してください。 残りの相違点については、この質問に対するその他の回答で説明する必要があります。

Question

dtype=np.double 3つの配列で始めることができます。 タイミングは、 iccでコンパイルされ、intelのmklリンクされたnumpy 1.7.1を使用して、Intel CPUで実行されます。 mklなしでgccコンパイルされたnumpy 1.6.1のAMD CPUも、タイミングを検証するために使用されました。 タイミングはシステムのサイズにほぼ直線的に変化することに注意してください。numpy関数if文で発生する小さなオーバーヘッドのためではありません。これらの違いはミリ秒ではなくマイクロ秒単位で表示されます。

arr_1D=np.arange(500,dtype=np.double)
large_arr_1D=np.arange(100000,dtype=np.double)
arr_2D=np.arange(500**2,dtype=np.double).reshape(500,500)
arr_3D=np.arange(500**3,dtype=np.double).reshape(500,500,500)

最初にnp.sum関数を見てみましょう:

np.all(np.sum(arr_3D)==np.einsum('ijk->',arr_3D))
True

%timeit np.sum(arr_3D)
10 loops, best of 3: 142 ms per loop

%timeit np.einsum('ijk->', arr_3D)
10 loops, best of 3: 70.2 ms per loop

パワーズ:

np.allclose(arr_3D*arr_3D*arr_3D,np.einsum('ijk,ijk,ijk->ijk',arr_3D,arr_3D,arr_3D))
True

%timeit arr_3D*arr_3D*arr_3D
1 loops, best of 3: 1.32 s per loop

%timeit np.einsum('ijk,ijk,ijk->ijk', arr_3D, arr_3D, arr_3D)
1 loops, best of 3: 694 ms per loop

外品:

np.all(np.outer(arr_1D,arr_1D)==np.einsum('i,k->ik',arr_1D,arr_1D))
True

%timeit np.outer(arr_1D, arr_1D)
1000 loops, best of 3: 411 us per loop

%timeit np.einsum('i,k->ik', arr_1D, arr_1D)
1000 loops, best of 3: 245 us per loop

上記のすべては、 np.einsum 2倍の速さnp.einsum 。 これらはリンゴとリンゴの比較である必要があります。具体的にはdtype=np.doubleです。 私はこのような操作でスピードアップを期待しています:

np.allclose(np.sum(arr_2D*arr_3D),np.einsum('ij,oij->',arr_2D,arr_3D))
True

%timeit np.sum(arr_2D*arr_3D)
1 loops, best of 3: 813 ms per loop

%timeit np.einsum('ij,oij->', arr_2D, arr_3D)
10 loops, best of 3: 85.1 ms per loop

アインサムは、 axes選択に関係なく、 np.kronnp.outernp.kron 、およびnp.sum少なくとも2倍の速さであるnp.innerです。 BLASライブラリからDGEMMを呼び出すnp.dotの主な例外はnp.dotです。 だから、なぜnp.einsumは同等の他のnumpy関数より速いのですか?

完全性のためのDGEMMの場合:

np.allclose(np.dot(arr_2D,arr_2D),np.einsum('ij,jk',arr_2D,arr_2D))
True

%timeit np.einsum('ij,jk',arr_2D,arr_2D)
10 loops, best of 3: 56.1 ms per loop

%timeit np.dot(arr_2D,arr_2D)
100 loops, best of 3: 5.17 ms per loop

主な理論は、np.einsumがSSE2を利用できるという@sebergsのコメントですが、numpyのufuncsはnumpy 1.8( 変更ログ参照)まではありません。 私はこれが正しい答えだと信じてますが、それを確認することはできませんでした。 入力配列のdtypeを変更して速度差を観察することで、誰もがタイミングの同じ傾向を観察するわけではないという事実から、いくつかの限定的な証拠を見つけることができます。






Links