不同轴上Numpy求和运算的性能差异探究
Numpy不同尺寸数组沿不同轴求和速度反转的原因分析
我们知道Numpy默认采用**C顺序(行优先)**存储数组,理论上沿axis=1(行内求和)的内存访问是连续的,运算速度应该快于沿axis=0(列内求和)的非连续内存访问。但实际测试却出现了相反的结果:
小尺寸数组测试(64x64)
arr = np.ones([64, 64]) arr.flags %timeit arr.sum(axis=1) # 4.15 µs ± 97.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit arr.sum(axis=0) # 4.67 µs ± 188 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
此时sum(axis=1)的速度符合理论预期,比sum(axis=0)更快。
大尺寸数组测试(10000x10000)
当数组尺寸放大后,结果完全反转:
arr = np.ones([10000, 10000]) arr.flags %timeit arr.sum(axis=1) # 109 ms ± 2.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) %timeit arr.sum(axis=0) # 57.5 ms ± 2.49 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
核心原因解析
小尺寸数组:缓存命中率主导
64x64的float64数组总大小仅32KB,完全能装进CPU的L1缓存。连续内存访问(axis=1)的缓存命中率接近100%,而非连续访问(axis=0)需要跨行跳转,缓存命中率稍低,所以sum(axis=1)更快。大尺寸数组:并行计算与缓存局部性的权衡
10000x10000的数组总大小约763MB,远超过CPU缓存容量,此时缓存命中率不再是唯一核心因素:- 沿
axis=0求和时,虽然内存访问是非连续的,但现代CPU的预取机制能针对固定跨步的访问做优化;更关键的是,列求和任务可以拆分成大量独立子任务,能充分利用多CPU核心的并行计算能力,抵消内存访问的劣势。 - 沿
axis=1求和时,虽然内存访问连续,但单一行的计算量小,多线程调度开销会抵消部分优势;同时,数组远大于缓存时,连续访问也会频繁触发缓存淘汰,内存带宽成为瓶颈,最终速度不如axis=0。
- 沿
Numpy底层库的优化策略
Numpy的sum函数依赖BLAS/LAPACK等高度优化的底层库,这些库会根据数组尺寸自动选择最优计算路径。对于大尺寸数组,BLAS库会优先启用多线程并行和SIMD指令,而axis=0的求和模式更适配这些优化策略,进一步放大了速度优势。
内容的提问来源于stack exchange,提问作者nan
相关产品推荐
相关产品推荐

