NumPy内存分配结果异常:求解析B、C变体的行为原因
解析Numpy在Ubuntu 22.04下的内存分配异常行为
环境与测试代码
运行环境:Ubuntu 22.04系统,Python 3.11,可用内存约16.4GB,无交换文件。
测试脚本:
import numpy as np, psutil print(f'{psutil.virtual_memory().available:,}') try: a = np.empty((100_000_000_000), dtype='u1') # variant A # a = np.empty((30_000_000_000), dtype='u1') # variant B # a = np.ones((30_000_000_000), dtype='u1') # variant C print(f'{a.size:,}') except RuntimeError as e: print(e) print('Done')
异常现象
- 变体A(1000亿元素的
np.empty):如预期抛出内存不足异常16,436,457,472 numpy.core._exceptions._ArrayMemoryError: Unable to allocate 93.1 GiB for an array with shape (100000000000,) and data type uint8 - 变体B(300亿元素的
np.empty):意外完成分配,无异常16,335,642,624 30,000,000,000 Done - 变体C(300亿元素的
np.ones):无提示直接运行失败,仅输出可用内存后进程终止16,337,346,560
行为原因解析
变体B:np.empty的延迟内存分配
np.empty()的核心特性是不初始化数组内容,它仅向Linux内核申请一块虚拟地址空间,不会立即分配物理内存。Linux默认采用「写时复制」的延迟内存分配策略:只有当程序实际读写数组元素时,内核才会尝试分配对应的物理内存页。
在变体B的测试中,代码仅完成了数组对象的创建,没有任何访问元素的操作,因此物理内存从未被实际分配,自然不会触发内存不足的异常。此时数组只是一个「空架子」的虚拟地址空间,直到后续对数组进行读写操作时,才会真正触发物理内存分配,进而可能引发内存不足问题。
变体C:np.ones的立即内存分配与系统OOM Killer
np.ones()需要将数组的所有元素初始化为1,这意味着它会立即触发全量物理内存分配——内核需要为300亿个uint8元素(约27.9GB)分配对应的物理内存页,而系统可用内存仅16.4GB且无交换空间,远远无法满足需求。
此时Linux内核会启动**OOM Killer(内存不足杀手)**机制,直接终止占用内存最多的进程(这里就是Python进程)来释放内存。由于进程是被内核强制杀死的,Python没有机会执行异常捕获逻辑和后续的print语句,因此表现为无提示的运行失败。
内容的提问来源于stack exchange,提问作者Paul Jurczak
相关产品推荐
相关产品推荐

