如何高效实现NumPy数组按固定大小分块求和
NumPy固定长度分块求和性能优化
问题背景
假设存在如下NumPy数组:
x = np.array([3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
需求是将数组按每3个元素为一组分块求和,预期输出为:
np.array([12, 21, 30, 39])
目前已有的实现方案为:
n = x.size out = x.reshape(n//3, 3) @ np.ones(3)
核心疑问:是否存在速度更快的实现方式?
已完成的基准测试
针对常见的几种实现方式,已经做了性能基准测试,测试代码与结果如下:
n = int(1e6) arr = np.random.random(4*n) def method1(arr): return arr.reshape(n, 4) @ np.ones(4) def method2(arr): return arr.reshape(n, 4).sum(-1) def method3(arr): return np.add.reduceat(arr, np.arange(0, 4*n, 4)) %timeit method1(arr) # 1.53 ms ± 85.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit method2(arr) # 14.6 ms ± 867 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit method3(arr) # 14.2 ms ± 369 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
优化方案
你测试中矩阵乘法实现(method1)的性能已经属于第一梯队,速度远超另外两种方案的核心原因是:@矩阵乘算子会调用NumPy绑定的底层BLAS库(如OpenBLAS、MKL),这类库针对现代CPU的SIMD向量指令、多核并行做了极致的底层优化;而.sum(-1)和np.add.reduceat走的是NumPy自带的通用归约逻辑,优化程度低且默认单线程执行,因此性能差距明显。
如果要进一步压榨性能,可以参考两个优化方向:
- 提前初始化分块求和用的全1向量,不要在函数内部重复创建,避免不必要的内存分配开销:
# 提前全局初始化,不要放在循环/函数内部重复生成 chunk_size = 4 ones_vec = np.ones(chunk_size, dtype=arr.dtype) def method1_opt(arr): return arr.reshape(-1, chunk_size) @ ones_vec
该优化通常能带来5%~15%的性能提升。
- 对于固定的小尺寸分块(比如测试中用的分块大小4),直接用步长切片相加的方式实现,可以省去BLAS调用的调度开销,大数据量下性能和优化后的矩阵乘持平,小数据量下速度更快:
def method_slice_sum(arr): return arr[::4] + arr[1::4] + arr[2::4] + arr[3::4]
注意:所有基于reshape的实现都要求数组总长度能被分块大小整除,否则会抛出形状不匹配错误。如果遇到数组长度不能被分块大小整除的场景,再考虑使用reduceat或者末尾补零的方案,这类通用方案的性能必然低于固定分块大小的特化实现。
内容的提问来源于stack exchange,提问作者Xanshiz
相关产品推荐
相关产品推荐

