numpy 的 memmap 写时复制模式是如何工作的?

Ami*_*mir 8 python numpy

我对 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)

Eri*_*ric 1

Numpy 在这里并没有做任何聪明的事情,它只是遵循内置memmap模块,该模块有一个access参数:

接受四个值之一:ACCESS_READACCESS_WRITE、 或ACCESS_COPY分别指定只读、直写或写时复制内存

在 Linux 上,这是通过调用mmap系统调用来实现的

MAP_PRIVATE

创建私有写时复制映射。映射的更新对于映射同一文件的其他进程来说是不可见的,并且不会传递到底层文件。

关于你的问题

更改不会写入磁盘,因此它们保存在内存中,但不知何故,所有更改(必须超过 3Gb)不会导致内存不足错误?

更改可能写入磁盘,但不会写入您打开的文件。它们可能被分页到虚拟内存中的某个地方。