77

の 3 つの配列から始めましょうdtype=np.doubleiccタイミングは、Intelの でコンパイルおよびリンクされた numpy 1.7.1 を使用して Intel CPU で実行されmklます。gccコンパイルなしでコンパイルされた numpy 1.6.1 を搭載した AMD CPUmklも、タイミングの検証に使用されました。タイミングはシステム サイズにほぼ比例して変化することに注意してください。これは、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

上記のすべては、 を使用すると 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

np.innerEinsum は、np.outer、 、np.kron、および選択にnp.sum関係なく、少なくとも 2 倍高速であるようaxesです。主な例外はnp.dot 、BLAS ライブラリから DGEMM を呼び出す場合です。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

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

4

4 に答える 4

35

まず、numpy リストでこれについて過去に多くの議論がありました。例: http://numpy-discussion.10968.n7.nabble.com/poor-performance-of-sum-with-sub-machine-word-integer-types-td41.html http://numpy- Discussion.10968.n7.nabble.com/odd-performance-of-sum-td3332.html

einsum古い numpy 関数の多くは、高度に最適化されたものよりも簡単に移植できる実装に焦点を当てている一方で、いくつかは新しいという事実に要約され、おそらくキャッシュ アライメントやその他のメモリ アクセスの問題を改善しようとしています。私はただ推測しているだけです。


ただし、あなたが行っていることのいくつかは、「同じもの同士」の比較ではありません。

@Jamieがすでに言ったことに加えてsum、配列により適切なアキュムレータを使用します

たとえばsum、入力の型をチェックし、適切なアキュムレータを使用することに、より注意を払っています。たとえば、次のことを考慮してください。

In [1]: x = 255 * np.ones(100, dtype=np.uint8)

In [2]: x
Out[2]:
array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255], dtype=uint8)

sumが正しいことに注意してください。

In [3]: x.sum()
Out[3]: 25500

一方einsum、間違った結果が得られます:

In [4]: np.einsum('i->', x)
Out[4]: 156

しかし、制限の少ない を使用してもdtype、期待どおりの結果が得られます。

In [5]: y = 255 * np.ones(100)

In [6]: np.einsum('i->', y)
Out[6]: 25500.0
于 2013-08-21T19:27:56.720 に答える
25

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

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

これは、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秒しかないことに注意してください. 残りの違いは、この質問に対する他の回答でカバーする必要があります。

于 2013-10-26T21:28:35.413 に答える
20

これらのタイミングが何が起こっているかを説明していると思います:

a = np.arange(1000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 3.32 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 6.84 us per loop

a = np.arange(10000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 12.6 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 16.5 us per loop

a = np.arange(100000, dtype=np.double)
%timeit np.einsum('i->', a)
10000 loops, best of 3: 103 us per loop
%timeit np.sum(a)
10000 loops, best of 3: 109 us per loop

したがって、 を呼び出すとき、基本的にほぼ一定の 3us のオーバーヘッドがnp.sumあるnp.einsumため、基本的には同じ速度で実行されますが、実行に時間がかかります。それはなぜでしょうか?私のお金は次のとおりです。

a = np.arange(1000, dtype=object)
%timeit np.einsum('i->', a)
Traceback (most recent call last):
...
TypeError: invalid data type for einsum
%timeit np.sum(a)
10000 loops, best of 3: 20.3 us per loop

何が起こっているのか正確にはわかりませんが、np.einsum型固有の関数を抽出して乗算と加算を行うためのチェックをスキップしているようで、標準 C 型のみを直接使用して*+ます。


多次元の場合も同じです。

n = 10; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
100000 loops, best of 3: 3.79 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 7.33 us per loop

n = 100; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
1000 loops, best of 3: 1.2 ms per loop
%timeit np.sum(a)
1000 loops, best of 3: 1.23 ms per loop

そのため、ほとんど一定のオーバーヘッドであり、いったん取り掛かると実行速度が速くなるわけではありません。

于 2013-08-21T19:07:13.083 に答える