numpy.sum可能比Python循环慢。

8

当针对特定轴对数组进行求和时,使用专门的数组方法array.sum(ax)可能比使用for循环更慢:

v = np.random.rand(3,1e4)

timeit v.sum(0)                             # vectorized method
1000 loops, best of 3: 183 us per loop

timeit for row in v[1:]: v[0] += row        # python loop
10000 loops, best of 3: 39.3 us per loop

向量化方法比普通的for循环慢了4倍以上!这里出了什么问题,我不能相信在numpy中向量化方法比for循环更快?

1
你正在比较苹果和橙子。第二个是一个3项长的Python循环,对于一个1000项长的C循环向量化加法进行操作,而第一个则是(可能是)1000项长的C,创建了3-4项的循环。此外,第二种情况下的内存布局访问更好,但这并没有太大的区别。 - seberg
换句话说,是的,你可以相信它会更快...但不要认为Python很慢,以至于你不能在不等待太久的情况下执行一个包含3个项目的for循环... - seberg
1
@seberg 我不同意苹果和橙子的比较:这里的for循环明显比numpy.sum更高效,这一点必须解释清楚。 - Stefano M
1个回答

9
不行。正如你提供的有趣示例所指出的那样,numpy.sum 可能不够优化,并且通过显式的 for 循环更好地布置操作可以更有效率。
让我再举一个例子:
>>> N, M = 10**4, 10**4
>>> v = np.random.randn(N,M)
>>> r = np.empty(M)
>>> timeit.timeit('v.sum(axis=0, out=r)', 'from __main__ import v,r', number=1)
1.2837879657745361
>>> r = np.empty(N)
>>> timeit.timeit('v.sum(axis=1, out=r)', 'from __main__ import v,r', number=1)
0.09213519096374512

在这里,您清楚地看到numpy.sum在快速运行索引(v是C连续的)上求和时是最优的,而在慢速运行轴上求和时则不太优秀。有趣的是,对于for循环,相反的模式是正确的:

>>> r = np.zeros(M)
>>> timeit.timeit('for row in v[:]: r += row', 'from __main__ import v,r', number=1)
0.11945700645446777
>>> r = np.zeros(N)
>>> timeit.timeit('for row in v.T[:]: r += row', 'from __main__ import v,r', number=1)
1.2647287845611572

我没有时间检查numpy代码,但我怀疑差别在于连续内存访问或跨步访问。

正如这个例子所示,在实现数值算法时,正确的内存布局非常重要。向量化代码并不一定解决每个问题。


是的,内存布局很重要,尽管在他的情况下,他只对少量项目求和...他看到的区别仅仅是由于调用最低级别循环10000次而不是4次,在C内部reduce方法具有更多开销,但我真的不认为这本身是值得担心的事情。 - seberg
你可以在上面的代码中尝试不同的 N, M 值。numpy.sum 中存在一些效率低下的区域,这是正常的,除非它成为应用程序的瓶颈,否则不必担心。对我来说,有些在 C 代码中恢复效率的策略可以应用于 Python 解释器级别,并且胜过 numpy,这是出乎意料的!OP 提出了一个非常好的观点。(最后的备注:当然,如果这种减少是瓶颈,那么自定义、精细调整的 C 扩展是最好的解决方案;接口到供应商库(如 MKL)也应该被分析。) - Stefano M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接