分块计算的本质是避免内存溢出,核心是将大矩阵按行/列切片,每次仅加载计算一小块并累加结果,需注意维度匹配、dtype统一、及时释放内存及使用memmap配合flush。

分块计算的本质是避免内存溢出
当矩阵尺寸超过可用内存(比如 20GB 的 float64 矩阵需要约 160GB 内存),直接 np.dot(A, B) 会触发 MemoryError。分块不是“加速技巧”,而是“能跑起来的前提”。核心思路是把大矩阵沿行或列切片,每次只加载和计算一小块,再累加结果。
用 np.split + 手动循环实现可控分块
NumPy 本身不提供自动分块的矩阵乘法函数,得自己组织循环。关键在于拆分维度的选择:左矩阵 A 按行拆、右矩阵 B 按列拆,才能保证每步计算 A_block @ B_block 得到结果块并可累加到对应位置。
常见错误现象:ValueError: operands could not be broadcast together —— 多因拆分后维度不匹配,比如对 A 按列拆却想做 A_block @ B。
- 若
A形状为(m, k),B为(k, n),则建议按行拆A(即沿 axis=0 拆成若干(m_i, k)),按列拆B(axis=1 拆成若干(k, n_j)) - 每轮计算
A_i @ B_j,结果形状为(m_i, n_j),需写入结果矩阵C的对应切片:C[i_start:i_end, j_start:j_end] = A_i @ B_j - 块大小不宜过小(增加 Python 循环开销),也不宜过大(失去分块意义);经验上设每块含 1e6 ~ 1e7 个元素较稳妥,例如
block_size = int(np.sqrt(1e7))
np.memmap 配合分块读写超大矩阵
当矩阵根本装不进内存(如磁盘上 50GB 的 .npy 文件),必须用 np.memmap 映射为伪数组,再配合分块计算。此时不能直接调用 @ 或 np.dot 整体运算,否则仍会尝试加载全量数据。
使用场景:从硬盘读取超大 A 和 B,输出结果也存为 memmap。
- 创建映射时指定
mode='r'(只读)或'c'(copy-on-write),避免意外写入原始文件 -
memmap对象支持切片索引,如A_memmap[i:i+bs, :]仅加载该行块,这是分块可行的基础 - 务必用
flush()强制写回磁盘,尤其在循环中更新结果memmap后;否则可能丢失最后几块计算结果
警惕广播与 dtype 导致的隐式内存暴涨
分块本为省内存,但两处容易翻车:一是中间结果未及时 del,二是 dtype 升级。例如 A 是 float32、B 是 float64,A_block @ B_block 默认返回 float64,单块就多占一倍内存。
- 显式指定输出 dtype:
np.dot(A_i, B_j, out=C_block.astype(np.float32)),或提前用.astype()统一输入类型 - 每轮计算完立即
del A_i, B_j, temp_result,必要时加gc.collect()(尤其在长循环中) - 避免在分块循环内做
np.concatenate或np.vstack——这会重建整个数组,瞬间吃光内存
真正难的不是写对第一块,而是确保第 1000 块运行时内存占用和第一块基本一致。监控 psutil.Process().memory_info().rss 是最简单的验证方式。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











