python - 为什么 numpy sum 比 + 运算符慢 10 倍?
问题描述
我注意到非常奇怪的是,np.sum 比手写总和慢 10 倍。
np.sum 与轴:
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
所以我想在某种程度上有一些开销......
解决方案
主要区别a.sum(axis=1)
是计算时开销较大。计算减少(在这种情况下sum
)不是一件小事:
- 必须考虑舍入误差,因此使用成对求和来减少它。
- 平铺对于更大的阵列很重要,因为它可以充分利用可用缓存
- 为了能够使用现代 CPU 的 SIMD 指令/乱序执行能力,应该并行计算多行
但是,如果只有两个元素要添加,那么所有这些都不是必需的,并且并不比简单的求和更好 - 你会得到相同的结果,但开销要少得多,速度更快。
对于只有 1000 个元素,调用 numpy 功能的开销可能高于实际执行这 1000 个加法(或乘法,因为在现代 CPU 上流水线加法/乘法具有相同的成本)-如您所见,对于 10 ^ 4 运行时间仅高出大约 2 倍,这肯定表明开销对 10^3 起更大的作用!在这个答案中,更详细地研究了开销和缓存未命中的影响。
让我们看一下 profiler-result 看看上面的理论是否成立(我使用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
和
#run.py
import numpy as np
a=np.random.rand(1000,2)
for _ in range(10000):
a.sum(axis=1)
#a[:,0]+a[:,1]
推荐阅读
- dynamics-crm - 需要帮助创建汇总字段以在父记录上聚合子记录的毛利润值
- spacy - 有没有一种快速的方法来获取 spaCy 中每个句子的标记?
- c++ - 如果一个对象是在本地创建的并在 C++ 中作为异常抛出,那么本地对象如何在其范围之外有效。即在 catch 块中?
- swift - 如何在 Swift 中使用字典初始化结构
- python-3.x - 使用 .splitlines() 时如何删除特殊字符
- c# - 如何从视图中获取输入列表?
- javascript - 填充轮盘的内容
- ckeditor5 - 如何向 Ckeditor5 添加 Font Awesome 支持?
- javascript - 异步函数返回承诺,但调用函数未解析
- javascript - npm 漏洞对前端很重要吗?