我非常紧张地注意到np.sum比手写的总和慢10倍。
轴总和:
p1 = np.random.rand(10000, 2)
def test(p1):
return p1.sum(axis=1)
%timeit test(p1)
每个循环186 µs±4.21 µs(平均±标准偏差,共运行7次,每个循环1000次)
不带轴的np.sum:
p1 = np.random.rand(10000, 2)
def test(p1):
return p1.sum()
%timeit test(p1)
每个循环17.9 µs±236 ns(平均±标准偏差,共运行7次,每个循环10000次)
+:
p1 = np.random.rand(10000, 2)
def test(p1):
return p1[:,0] + p1[:,1]
%timeit test(p1)
每个循环15.8 µs±328 ns(平均±标准偏差,共运行7次,每个循环100000次)
乘法:
p1 = np.random.rand(10000, 2)
def test(p1):
return p1[:,0]*p1[:,1]
%timeit test(p1)
每个循环15.7 µs±701 ns(平均±标准偏差,共运行7次,每个循环10000次)
我没有看到任何原因。知道为什么吗?我的numpy版本是1.15.3
。
编辑: 10000000:
np.sum (with axis): 202 ms (5 x)
np.sum (without axis): 12 ms
+ : 46 ms (1 x)
* : 44.3 ms
所以我想一定程度上有一些开销...
答案 0 :(得分:7)
主要区别是计算a.sum(axis=1)
时开销较大。计算减少量(在这种情况下为sum
)不是一件小事:
但是,如果仅添加两个元素,则不需要所有这些操作,也不会比单纯的求和好-您获得相同的结果,但开销却少得多,而且速度更快。
对于仅1000个元素,调用numpy功能的开销可能比实际执行这1000次加法(或乘法)要高,因为在现代CPU上,流水线式的加法/乘法具有相同的成本)-如您所见,对于10 ^ 3,运行时间仅高出约2倍,这无疑表明开销在10 ^ 3中起着更大的作用!在this answer中,将详细研究开销和缓存未命中的影响。
让我们看一下探查器结果,看看上面的理论是否成立(我使用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
并不是每行都被调用,而只会被调用一次,这意味着开销要少得多。因此可以预期a.sum()
会更快(尽管事实是必须添加2000而不是1000,但正如我们所看到的主要是开销和实际工作一样,并不是运行时间的很大一部分。
通过运行获取数据:
perf record python run.py
perf report
和
#run.py
import numpy as np
a=np.random.rand(1000,2)
for _ in range(10000):
a.sum(axis=1)
#a[:,0]+a[:,1]
答案 1 :(得分:0)
对于带有轴vs不带轴的.sum(),该轴必须生成与输入一样长的浮点数组,每行都有一个元素。这意味着它必须沿轴= 1调用reduce() 10,000次。如果没有axis参数,它将把每个元素的总和计算为单个浮点数,这只是通过数组的平面表示来减少的一个调用。
我不确定为什么手动添加功能会更快,而且我不喜欢深入研究源代码,但我想我有一个很好的猜测。我认为,开销来自于必须为每一行在轴= 1上执行reduce,因此要进行10,000个单独的调用以进行reduce。在手动添加功能中,在定义“ +”功能的参数时,仅执行一次轴拆分,然后可以将拆分列的每个元素并行添加在一起。