主要区别在于计算
a.sum(axis=1)
时的开销更大。计算缩减(在此情况下为
sum
)不是一件琐碎的事情:
- 必须考虑舍入误差,因此使用成对求和来减少误差。
- 对于更大的数组,平铺很重要,因为它可以充分利用可用的缓存。
- 为了能够使用现代CPU的SIMD指令/乱序执行能力,应同时计算多行。
我在此处和此处更详细地讨论了上述主题。
但是,如果只有两个元素需要相加,则所有这些都是不必要的,也不如朴素求和好 - 您将获得相同的结果,但开销更小且速度更快。
对于仅有1000个元素的情况,调用NumPy功能的开销可能比实际执行这1000次加法(或乘法)还要高 - 如您所见,对于10^4,运行时间仅大约增加了2倍,这是开销对于10^3起更大作用的明确标志!在此回答中更详细地研究了开销和缓存未命中的影响。
让我们查看分析器结果,看看上述理论是否成立(我使用perf
):
对于a.sum(axis=1)
:
17,39% python umath.cpython-36m-x86_64-linux-gnu.so [.] reduce_loop
11,41% python umath.cpython-36m-x86_64-linux-gnu.so [.] pairwise_sum_DOUBLE
9,78% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] npyiter_buffered_reduce_iternext_ite
9,24% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_add
4,35% python python3.6 [.] _PyEval_EvalFrameDefault
2,17% python multiarray.cpython-36m-x86_64-linux-gnu.so [.] _aligned_strided_to_contig_size8_src
2,17% python python3.6 [.] lookdict_unicode_nodummy
...
使用 reduce_loop
和 pairwise_sum_DOUBLE
的开销很大。
对于 a[:,0]+a[:,1]
:
7,24% python python3.6 [.] _PyEval_EvalF
5,26% python python3.6 [.] PyObject_Mall
3,95% python python3.6 [.] visit_decref
3,95% python umath.cpython-36m-x86_64-linux-gnu.so [.] DOUBLE_add
2,63% python python3.6 [.] PyDict_SetDef
2,63% python python3.6 [.] _PyTuple_Mayb
2,63% python python3.6 [.] collect
2,63% python python3.6 [.] fast_function
2,63% python python3.6 [.] visit_reachab
1,97% python python3.6 [.] _PyObject_Gen
预料之中:Python开销扮演了重要角色,简单的DOUBLE_add
被使用。
调用a.sum()
时,开销较小。
- 一次只调用
reduce_loop
一次,而不是为每一行都调用一次,这意味着显著减少开销。
- 没有创建新的结果数组,不再需要将1000个双精度浮点数写入内存。
因此可以预期,a.sum()
更快(尽管必须执行2000个加法而不是1000个 - 但正如我们所看到的,大部分时间都花在开销和实际工作上,而不是加法产生的负担)。
通过运行获得的数据:
perf record python run.py
perf report
以及
import numpy as np
a=np.random.rand(1000,2)
for _ in range(10000):
a.sum(axis=1)
prod
不会进行成对操作,而且在我的测试中,prod
的运行时间约为sum
的 5/6。我认为 NumPy 也在使用 SIMD 进行+
而不是sum
,但我还不确定。 - user2357112p1
,并基本忽略了p2
。 - Sam Masonp1 = np.random.rand(10000, 2)
更改为p1 = np.random.rand(2, 10000)
和p1.sum(axis=1)
更改为p1.sum(axis=0)
来改变代码性能约10倍。 - Alex Huszagh