数组访问慢主因是内存非连续导致缓存失效,改用连续存储顺序或强制连续可提速;切片如arr[::2,::2]产生非连续视图,破坏空间局部性;按操作轴选择c/f顺序,创建后及时用ascontiguousarray统一格式,但避免无谓拷贝。

数组访问慢,八成不是CPU瓶颈,而是内存跳着读——改存储顺序或强制连续就能明显提速。
为什么arr[::2, ::2]求和比arr.sum()慢3倍
这种切片返回的是非连续视图(strided view),内存地址不连贯,CPU每次取数都要跨一大段,缓存反复失效。即使数组本身是C连续的,[::2, ::2]也会让步长(strides)变大,破坏空间局部性。
- 用
arr.flags['C_CONTIGUOUS']和arr.flags['F_CONTIGUOUS']检查连续性,别只看形状 - 转置
.T、高步长切片、np.take(..., axis=)都容易产生非连续数组 - 后续调用
np.dot、np.linalg.svd等C/Fortran库函数时,非连续数组常触发隐式复制,反而更慢
选order='C'还是order='F'?看你的axis怎么动
不是“行优先就一定快”,关键匹配你最频繁操作的轴。比如做列归一化:arr[:, i] = (arr[:, i] - mean) / std,这是按列取整列再运算,order='F'让每列元素在内存里挨着,一次缓存加载就能喂饱整个列计算。
- 按行操作(如
np.sum(arr, axis=1))、图像逐行处理 → 用order='C'(默认) - 按列操作(如
np.sum(arr, axis=0))、矩阵特征向量提取 → 用order='F' - 创建后立刻用
np.ascontiguousarray(arr, dtype=...)统一格式,别依赖初始order
np.ascontiguousarray()不是万能胶,该省则省
它会分配新内存并拷贝数据,对小数组无感,但对GB级数组就是几百毫秒+几百MB开销。真正该用它的场景,是后续要进Numba、Cython或调用scipy.linalg等外部库前的“临门一脚”。
- 纯NumPy内部运算(如
+ - * / np.where)大多能自动处理非连续输入,不必提前转 - 确认慢点后,先用
%timeit np.sum(arr)和%timeit np.sum(np.ascontiguousarray(arr))实测差距 - 若只是临时读取子块,用
arr.copy()比ascontiguousarray更直白,且明确语义
别忽略dtype和strides这两个隐形加速器
dtype影响单元素大小和对齐,strides决定你怎么在内存里“跳”。一个int32数组比int64省内存、缓存更友好;而strides=(8, 4)意味着跨行跳8字节、跨列跳4字节——如果这个步长导致每次访问都错开缓存行(通常是64字节),性能就废了。
- 用
arr.itemsize确认单元素字节数,大数组优先选np.float32而非float64 - 避免混用不同
dtype数组做广播运算,隐式转换可能生成临时非连续副本 -
arr.strides值异常大(如远超arr.itemsize * shape)往往是非连续或高步长切片的信号
最常被跳过的动作:在热循环前加一行if not arr.flags.c_contiguous: arr = np.ascontiguousarray(arr)。它不起眼,但能让后续所有向量化操作稳在最佳路径上——不是所有优化都得重写逻辑,有时只是把内存摆正位置。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











