在读取文件的生成器上进行Python多处理

时间:2015-12-07 21:17:15

标签: python multiprocessing python-multiprocessing

我正在尝试读取和处理1000个文件,但遗憾的是,处理文件的时间大约是从磁盘读取文件的3倍,因此我希望在读取这些文件时处理这些文件(而我继续阅读其他文件)。

在一个完美的世界中,我有一个一次读取一个文件的生成器,我想将这个生成器传递给一个工作池,这些工作器在生成(缓慢)生成时处理生成器中的项目。

以下是一个例子:

def process_file(file_string):
     ...
     return processed_file

 pool = Pool(processes=4)
 path = 'some/path/'
 results = pool.map(process_file, (open(path+part,'rb').read() for part in os.listdir(path)))

上面代码的唯一问题是在池开始之前所有文件都被读入内存,这意味着我需要等待磁盘读取所有内容,并且还消耗大量内存。

2 个答案:

答案 0 :(得分:6)

Pool.mapPool.map_async listify the iterable passed to them, so your generator will always be realized fully before processing even begins

各种Pool.imap*函数出现以将输入作为生成器进行处理,因此您可以更改:

results = pool.map(process_file, (open(path+part,'rb').read() for part in os.listdir(path)))

为:

# If you can process outputs one at a time, drop the list wrapper
# If you can process outputs without order mattering, imap_unordered will
# get you the best results
results = list(pool.imap(process_file, (open(path+part,'rb').read() for part in os.listdir(path))))

并且在处理之前得到相同的结果而不是啜饮,但是AFAICT,他们仍然会尽可能快地完全填充队列,这可能导致大量数据未完成且内存使用过多;除此之外,您将在一个过程中读取所有数据,然后通过IPC发送所有数据,这意味着您仍然主要在I / O上遇到瓶颈。

在您的位置,我将读取移动到任务本身(如果可以,请避免读取整个文件,按行或按块处理,而不是阅读整个事情一下子)。您可以获得并行读取,减少IPC,并且在前几个文件被处理之前,您不会冒险诋毁所有文件;你没有比工人更多的文件打开。所以最终结果如下:

def process_file(path):
     with open(path, 'rb') as f:
         file_string = f.read()
     ... same as before ...
     return processed_file

pool = Pool(processes=4)
path = 'some/path/'
results = pool.imap(process_file, (os.path.join(path, part) for part in os.listdir(path)))

答案 1 :(得分:2)

您正在将文件读入父母的内存中,然后将有效负载转移到子级中。那效率很低。只发送文件名,让孩子们进行I / O操作。如果结果是您计划写入文件的一堆文本,那么也要在子文件中执行该操作。

map通常会一次性发布大量工作,以减少池工作者的通信开销。这可能是你获得大记忆飙升的原因。只传递文件名解决了这个问题,但是如果工人之间的处理时间不均匀,那么设置一个小的chunksize仍然是有益的。

def process_file(filename):
     with open(filename, 'rb') as fp:
         file_string = fp.read()
     ...
     return processed_file

 pool = Pool(processes=4)
 path = 'some/path/'
 results = pool.map(process_file, path+part for part in os.listdir(path)), chunksize=1)