我对 numpymemmap在使用 copy-on-write ( mmap_mode=c)时如何处理数据更改感到困惑。由于没有将任何内容写入磁盘上的原始数组,我希望它必须将所有更改存储在内存中,因此如果您修改每个元素,可能会耗尽内存。令我惊讶的是,它没有。
我正在尝试减少我在共享集群上运行的机器学习脚本的内存使用量(每个实例占用的内存越少,我可以同时运行的实例越多)。我的数据是非常大的 numpy 数组(每个 > 8 Gb)。我的希望是使用np.memmap这些具有小内存(<4Gb 可用)的阵列。
然而,每个实例可能会以不同的方式修改数据(例如,可能会选择每次以不同的方式标准化输入数据)。这对存储空间有影响。如果我使用该r+模式,则在我的脚本中规范化数组将永久更改存储的数组。
由于我不想要数据的冗余副本,而只想将原始数据存储在磁盘上,因此我认为我应该使用'c'(copy-on-write)模式来打开阵列。但是你的改变去哪里了?更改是否仅保存在内存中?如果是这样,如果我更改整个阵列,我会不会在小内存系统上耗尽内存?
这是我预计会失败的测试示例:
在大内存系统上,创建数组:
import numpy as np
GB = 1000**3
GiB = 1024**3
a = np.zeros((50000, 20000), dtype='float32')
bytes = a.size * a.itemsize
print('{} GB'.format(bytes / GB))
print('{} GiB'.format(bytes / GiB))
np.save('a.npy', a)
# Output:
# 4.0 GB
# 3.725290298461914 GiB
Run Code Online (Sandbox Code Playgroud)
现在,在只有 2 Gb 内存的机器上,这会按预期失败:
a = np.load('a.npy')
Run Code Online (Sandbox Code Playgroud)
但正如预期的那样,这两个会成功:
a = np.load('a.npy', mmap_mode='r+')
a = np.load('a.npy', mmap_mode='c')
Run Code Online (Sandbox Code Playgroud)
问题 1:我运行此代码时内存不足,试图修改 memmaped 数组(无论 r+/c 模式如何都失败):
for i in range(a.shape[0]):
print('row {}'.format(i))
a[i,:] = i*np.arange(a.shape[1])
Run Code Online (Sandbox Code Playgroud)
为什么这会失败(特别是,为什么即使在r+模式下它也会失败,它可以写入磁盘)?我以为memmap只会将数组的一部分加载到内存中?
问题 2:当我强制 numpy 每隔一段时间刷新一次更改时,两种 r+/c 模式都成功完成了循环。但是c模式如何做到这一点呢?我没想到flush()会为c模式做任何事情?更改不会写入磁盘,因此它们保存在内存中,但不知何故,必须超过 3Gb 的所有更改不会导致内存不足错误?
for i in range(a.shape[0]):
if i % 100 == 0:
print('row {}'.format(i))
a.flush()
a[i,:] = i*np.arange(a.shape[1])
Run Code Online (Sandbox Code Playgroud)
Numpy 在这里并没有做任何聪明的事情,它只是遵循内置memmap模块,该模块有一个access参数:
接受四个值之一:
ACCESS_READ、ACCESS_WRITE、 或ACCESS_COPY分别指定只读、直写或写时复制内存。
在 Linux 上,这是通过调用mmap系统调用来实现的
MAP_PRIVATE创建私有写时复制映射。映射的更新对于映射同一文件的其他进程来说是不可见的,并且不会传递到底层文件。
关于你的问题
更改不会写入磁盘,因此它们保存在内存中,但不知何故,所有更改(必须超过 3Gb)不会导致内存不足错误?
更改可能会写入磁盘,但不会写入您打开的文件。它们可能被分页到虚拟内存中的某个地方。