让我们从三个 dtype=np.double
数组开始。计时是在 Intel CPU 上执行的,使用 numpy 1.7.1和 icc
一起编译,并链接到 Intel 的 mkl
。一个 AMD CPU 与 numpy 1.6.1编译与 gcc
没有 mkl
也被用来验证时间。请注意时间刻度几乎与系统大小成线性关系,并不是因为数字函数 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
比其他等价的数字函数更快呢?
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 的 ufuns 要到 numpy 1.8才能使用(参见 更改日志)。我相信这是正确的答案,但是有 没有能够证实它。通过改变输入阵列的 dtype 和观察速度差,以及不是每个人都能观察到同样的定时趋势,可以找到一些有限的证明。