为什么返回np.memmap时并发.futures保留在内存中?

时间:2018-08-14 15:13:15

标签: python parallel-processing concurrent.futures numpy-memmap

问题

我的应用程序正在提取内存中的zip文件列表,并将数据写入临时文件。然后,我将数据映射到临时文件中,以供其他功能使用。当我在单个过程中执行此操作时,它可以正常工作,读取数据不会影响内存,最大RAM约为40MB。但是,当我使用current.futures执行此操作时,RAM可达500MB。

我看过this示例,并且我了解我可以以更好的方式提交作业,以在处理期间节省内存。但是我不认为我的问题与之相关,因为我在处理过程中没有耗尽内存。我不明白的问题是,即使返回内存映射后,为什么它仍保留在内存中。我也不了解内存中的内容,因为在单个过程中执行此操作不会将数据加载到内存中。

任何人都可以解释一下内存中的实际内容以及为什么单处理和并行处理之间的区别吗?

我使用memory_profiler来衡量内存使用率的PS

代码

主代码:

def main():
    datadir = './testdata'
    files = os.listdir('./testdata')
    files = [os.path.join(datadir, f) for f in files]
    datalist = download_files(files, multiprocess=False)
    print(len(datalist))
    time.sleep(15)
    del datalist # See here that memory is freed up
    time.sleep(15)

其他功能:

def download_files(filelist, multiprocess=False):
    datalist = []
    if multiprocess:
        with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
            returned_future = [executor.submit(extract_file, f) for f in filelist]
        for future in returned_future:
            datalist.append(future.result())
    else:
        for f in filelist:
            datalist.append(extract_file(f))
    return datalist

def extract_file(input_zip):
    buffer = next(iter(extract_zip(input_zip).values()))
    with tempfile.NamedTemporaryFile() as temp_logfile:
        temp_logfile.write(buffer)
        del buffer
        data = memmap(temp_logfile, dtype='float32', shape=(2000000, 4), mode='r')
    return data

def extract_zip(input_zip):
    with ZipFile(input_zip, 'r') as input_zip:
        return {name: input_zip.read(name) for name in input_zip.namelist()}

数据的帮助程序代码

我无法共享我的实际数据,但是下面是一些简单的代码来创建演示问题的文件:

for i in range(1, 16):
    outdir = './testdata'
    outfile = 'file_{}.dat'.format(i)
    fp = np.memmap(os.path.join(outdir, outfile), dtype='float32', mode='w+', shape=(2000000, 4))
    fp[:] = np.random.rand(*fp.shape)
    del fp
    with ZipFile(outdir + '/' + outfile[:-4] + '.zip', mode='w', compression=ZIP_DEFLATED) as z:
        z.write(outdir + '/' + outfile, outfile)

1 个答案:

答案 0 :(得分:1)

问题是您试图在进程之间传递np.memmap,但这是行不通的。

最简单的解决方案是传递文件名,并使子进程memmap拥有相同的文件。


当您pass an argument to a child process or pool method via multiprocessing或从一个值返回一个值(包括通过ProcessPoolExecutor间接返回值)时,它可以通过调用该值上的pickle.dumps来使泡菜跨进程传递(细节各不相同,但是无论是Pipe还是Queue还是其他东西都没关系,然后从另一侧解开结果。

memmap基本上只是一个mmap对象,在ndarray内存中分配了mmap

Python不知道如何腌制mmap对象。 (如果尝试这样做,则将根据您的Python版本出现PicklingErrorBrokenProcessPool错误。)

可以腌制np.memmap ,因为它只是np.ndarray的子类,但是腌制和去腌制实际上会复制数据并为您提供一个简单的内存数组。 (如果您查看data._mmap,它就是None。)如果它给您一个错误而不是静默地复制所有数据,则可能会更好(泡菜替换库dill确实可以完全是这样:TypeError: can't pickle mmap.mmap objects),但事实并非如此。


在进程之间传递基础文件描述符不是不可能的-每个平台上的细节都不同,但是所有主要平台都可以做到这一点。然后,您可以使用传递的fd在接收方建立mmap,然后在其中建立memmap。您甚至可以将其包装在np.memmap的子类中。但是我怀疑这是否还算困难,有人已经做到了,实际上,如果不是dill本身,它可能已经成为numpy的一部分。

另一种替代方法是显式使用shared memory features of multiprocessing,然后在共享内存中分配数组而不是mmap

但是,最简单的解决方案是,就像我在顶部所说的那样,只是传递文件名而不是对象,并让memmap的每一面都使用相同的文件。不幸的是,这确实意味着您不能只使用关闭时删除NamedTemporaryFile(尽管您使用它的方式已经不可移植,并且无法像在Windows上那样使用Unix),但是更改它的工作量可能仍然比其他方法少。