numpy memmap内存使用 - 想要迭代一次

时间:2017-07-16 20:16:13

标签: python numpy numpy-memmap

假设我在磁盘上保存了一些大矩阵。将它全部存储在内存中并不可行,所以我使用memmap来访问它

A = np.memmap(filename, dtype='float32', mode='r', shape=(3000000,162))

现在让我说我想迭代这个矩阵(基本上不是以有序的方式),这样每行只能被访问一次。

p = some_permutation_of_0_to_2999999()

我想做那样的事情:

start = 0
end = 3000000
num_rows_to_load_at_once = some_size_that_will_fit_in_memory()
while start < end:
    indices_to_access = p[start:start+num_rows_to_load_at_once]
    do_stuff_with(A[indices_to_access, :])
    start = min(end, start+num_rows_to_load_at_once)

随着这个过程在我的计算机上变得越来越慢,我的RAM和虚拟内存使用量也在不断增长。

有没有办法强制np.memmap使用一定数量的内存? (我知道我不会需要超过我计划一次阅读的行数而且缓存不会真正帮助我,因为我只能访问每一行一次)

也许还有其他方法可以在自定义顺序中对np数组进行迭代(生成器)吗?我可以使用file.seek手动编写它,但它恰好比np.memmap实现慢得多

do_stuff_with()不保留对它接收的数组的任何引用,因此没有&#34;内存泄漏&#34;在那方面

感谢

2 个答案:

答案 0 :(得分:3)

这是我一直试图解决的问题。我使用大型图像数据集,numpy.memmap为使用这些大型集提供了一个方便的解决方案。

但是,正如您所指出的,如果我需要访问每个帧(或您的情况下的行)来执行某些操作,RAM的使用最终会最大化。

幸运的是,我最近找到了一个解决方案,允许您在限制RAM使用量的同时迭代整个memmap阵列。

解决方案:

import numpy as np

# create a memmap array
input = np.memmap('input', dtype='uint16', shape=(10000,800,800), mode='w+')

# create a memmap array to store the output
output = np.memmap('output', dtype='uint16', shape=(10000,800,800), mode='w+')

def iterate_efficiently(input, output, chunk_size):
    # create an empty array to hold each chunk
    # the size of this array will determine the amount of RAM usage
    holder = np.zeros([chunk_size,800,800], dtype='uint16')

    # iterate through the input, replace with ones, and write to output
    for i in range(input.shape[0]):
        if i % chunk_size == 0:
            holder[:] = input[i:i+chunk_size] # read in chunk from input
            holder += 5 # perform some operation
            output[i:i+chunk_size] = holder # write chunk to output

def iterate_inefficiently(input, output):
    output[:] = input[:] + 5

时间安排结果:

In [11]: %timeit iterate_efficiently(input,output,1000)
1 loop, best of 3: 1min 48s per loop

In [12]: %timeit iterate_inefficiently(input,output)
1 loop, best of 3: 2min 22s per loop

磁盘阵列的大小约为12GB。使用iterate_efficiently函数将内存使用量保持在1.28GB,而iterate_inefficiently函数最终在RAM中达到12GB。

这是在Mac OS上测试的。

答案 1 :(得分:3)

我已经尝试了几天这个问题,看来有两种使用np.mmap来控制内存消耗的方法。第一个是可靠的,而第二个则需要进行一些测试,并且取决于操作系统。

选项1 -每次读取/写入都会重建内存映射:

def MoveMMapNPArray(data, output_filename):
    CHUNK_SIZE = 4096
    for idx in range(0,x.shape[1],CHUNK_SIZE):
        x = np.memmap(data.filename, dtype=data.dtype, mode='r', shape=data.shape, order='F')
        y = np.memmap(output_filename, dtype=data.dtype, mode='r+', shape=data.shape, order='F')
        end = min(idx+CHUNK_SIZE, data.shape[1])
        y[:,idx:end] = x[:,idx:end]

data的类型为np.memmap。每次读取都会丢弃memmap对象,从而避免将数组收集到内存中,如果块大小较小,则将使内存消耗非常低。它可能会带来一些CPU开销,但在我的设置(MacOS)上却很小。

选项2 -自己构造mmap缓冲区并提供内存建议

如果查看np.memmap源代码here,您会发现相对容易地创建自己的映射的numpy数组相对简单。具体来说,使用代码段:

mm = mmap.mmap(fid.fileno(), bytes, access=acc, offset=start)
mmap_np_array = ndarray.__new__(subtype, shape, dtype=descr, buffer=mm, offset=array_offset, order=order)

请注意,此python mmap实例存储为np.memmap的私有_mmap属性。

可以访问python mmap对象和python 3.8,可以使用其madvise方法,如here所述。

这使您可以建议操作系统释放可用的内存。 here针对Linux https://dev.mysql.com/doc/refman/5.6/en/mysqldump.html#option_mysqldump_no-create-db进行了描述,并指定了一些通用的跨平台选项。

MADV_DONTDUMP常量看起来很有希望,但是我没有像选项1那样测试它的内存消耗。