让我们从三个dtype=np.double
数组开始。使用用icc
编译并链接到英特尔mkl
的numpy 1.7.1在英特尔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
的两倍快。这些应该是苹果与苹果的比较,因为所有的东西都是特定于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
,Einsum的速度似乎至少是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
主要的理论来自@sebergs的评论,即np.einsum
可以利用SSE2,但numpy的ufuncs直到numpy 1.8才能使用(参见change log)。我相信这是正确的答案,但还无法确认。一些有限的证据可以通过改变输入数组的数据类型和观察速度差异以及不是每个人都观察到相同的计时趋势的事实来找到。
发布于 2013-10-27 05:28:35
现在,numpy 1.8发布了,根据文档,所有ufuncs都应该使用SSE2,我想再次检查一下Seberg对SSE2的评论是否有效。
为了执行测试,我们创建了一个新的python2.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'
Numpy 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
Numpy 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
我认为这是相当有说服力的,上交所在时间差异中扮演了很大的角色,应该注意的是,重复这些测试的时间只有0.003秒。其余的不同之处应该在这个问题的其他答案中涵盖。
发布于 2013-08-22 03:27:57
首先,在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-22 03:07:13
我认为这些计时解释了正在发生的事情:
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
所以当你在np.einsum
上调用np.sum
时,你基本上有一个几乎恒定的3us开销,所以它们基本上运行得一样快,但是一个运行起来需要更长的时间。为什么会这样呢?我的赌注如下:
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
所以大部分的开销是恒定的,一旦他们开始工作,运行速度就不会更快。
https://stackoverflow.com/questions/18365073
复制相似问题