首页 > 解决方案 > numpy 的 memmap 写时复制模式是如何工作的?

问题描述

我对 numpymemmap在使用写时复制 () 时如何处理数据更改感到困惑mmap_mode=c。由于没有将任何内容写入磁盘上的原始数组,因此我希望它必须将所有更改存储在内存中,因此如果您修改每个元素,可能会耗尽内存。令我惊讶的是,它没有。

我正在尝试减少我在共享集群上运行的机器学习脚本的内存使用量(每个实例占用的内存越少,我可以同时运行的实例越多)。我的数据是非常大的 numpy 数组(每个 > 8 Gb)。我希望使用np.memmap这些具有小内存(<4Gb 可用)的阵列。

然而,每个实例可能会以不同的方式修改数据(例如,可能每次都选择以不同的方式规范化输入数据)。这对存储空间有影响。如果我使用该r+模式,那么在我的脚本中规范化数组将永久更改存储的数组。

由于我不想要数据的冗余副本,而只想将原始数据存储在磁盘上,所以我认为我应该使用'c'模式(写时复制)来打开数组。但是你的改变去哪里了?更改是否仅保存在内存中?如果是这样,如果我更改整个数组,我不会在小内存系统上耗尽内存吗?

这是我预计会失败的测试示例:

在大型内存系统上,创建数组:

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

现在,在只有 2 Gb 内存的机器上,这会按预期失败:

a = np.load('a.npy')

但正如预期的那样,这两个会成功:

a = np.load('a.npy', mmap_mode='r+')
a = np.load('a.npy', mmap_mode='c')

问题 1:我运行此代码时内存不足,试图修改 memmapped 数组(无论 r+/c 模式如何都失败):

for i in range(a.shape[0]):
    print('row {}'.format(i))
    a[i,:] = i*np.arange(a.shape[1])

为什么会失败(特别是为什么即使在r+可以写入磁盘的模式下也会失败)?我以为memmap只会将数组的一部分加载到内存中?

问题 2:当我强制 numpy 每隔一段时间刷新一次更改时,两种 r+/c 模式都会成功完成循环。但是cmode 怎么能做到这一点呢?我不认为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])

标签: pythonnumpy

解决方案


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

接受以下四个值之一:ACCESS_READ、、ACCESS_WRITEACCESS_COPY分别指定只读、直写或写时复制内存

在 linux 上,这通过调用mmap系统调用来工作

MAP_PRIVATE

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

关于你的问题

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

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


推荐阅读