我们从三个dtype=np.double
的数组开始。时间是在使用numpy 1.7.1编译的icc
并链接到英特尔的mkl
的英特尔CPU上进行的。还使用了一个带有numpy 1.6.1编译的gcc
但没有mkl
的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
时速度都可以提升两倍。这些应该是苹果与苹果之间的比较,因为所有内容都明确指定了 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.inner
,np.outer
,np.kron
和np.sum
,无论选择哪个axes
,Einsum
似乎至少快两倍。主要的例外是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
主要的理论来自于@sebergs的评论,即
np.einsum
可以利用SSE2,但直到numpy 1.8(请参见change log),numpy的ufuncs才能使用它。我相信这是正确的答案,但我无法确认。一些有限的证据可以通过改变输入数组的dtype并观察速度差异以及不是每个人都观察到相同的时间趋势来找到。
sum
),但我对einsum
始终比outer
、inner
、kron
等快大约两倍感到惊讶。了解这种差异的来源将会很有趣。 - Joe Kington