使用Python的multiprocessing.Pool实现高内存使用率

时间:2014-10-05 00:56:56

标签: python memory parallel-processing multiprocessing pool

我写了一个程序,它将~2600个文本文档解析成Python对象。 这些对象有很多对其他对象的引用,整体上它们描述了文档的结构。

使用pickle对这些对象进行序列化是没有问题的并且速度非常快。

在将这些文档解析为Python对象之后,我必须对它们进行一些繁重的计算,我想使它们并行。 我当前的解决方案将其中一个文档对象传递给一个工作函数,然后进行那些繁重的计算。

这些计算的结果被写入对象中,这些对象是文档对象中的属性。 然后,worker函数返回那些已更改的对象(仅返回那些属性对象,而不是原始文档对象)。

所有这些都适用于以下简化代码:

def worker(document_object):
    # Doing calculations on information of document_object and altering objects which are attributes of document_object
    return document_object.attribute_objects

def get_results(attribute_objects):
    # Save results into memory of main process

# Parsing documents
document_objects = parse_documents_into_python_objects()

# Dividing objects into smaller pieces (8 and smaller worked for me)
for chunk in chunker(document_objects, 8):
    pool = multiprocessing.Pool()
    pool.map_async(worker, chunk, callback=get_results)
    pool.close()
    pool.join()

然而,有几个问题:

  • 仅当我将所有document_objects的小块传递给map_async()时才有效。否则,即使使用15GB内存,我也会遇到内存错误。
  • htop告诉我,所有8个核心中只有2-3个被使用。
  • 我觉得它并不比单一流程版本快得多(我可能错了)。

我知道每个document_object都必须被pickle并复制到一个worker进程中,而map_async()会将所有这些数据保存在内存中,直到pool.join()发生。

我不明白为什么这会占用如此多的内存(高达~12GB)。 当我将一个document_object pickle到一个文件中时,该文件的最大值约为500KB。

  • 为什么这会占用这么多内存?
  • 为什么只使用2-3个核心?
  • 有更好的方法吗?例如,有一种方法可以在单个工作程序函数完成后直接将结果保存到主进程,因此我不必等待join()直到所有内存再次可用并且结果可用通过回调函数?

编辑:我在Ubuntu 14.04和Debian Wheezy上使用Python 2.7.6。

编辑:当我打印出工作人员功能的开始和结束时,就像评论中建议的dano一样,我得到类似下面的东西,它看起来并不平行。每一端和开始之间也有约5秒钟。

start <Process(PoolWorker-161, started daemon)>
end <Process(PoolWorker-161, started daemon)>
(~5 seconds delay)
start <Process(PoolWorker-162, started daemon)>
end <Process(PoolWorker-162, started daemon)>
(~5 seconds delay)
start <Process(PoolWorker-163, started daemon)>
end <Process(PoolWorker-163, started daemon)>
(~5 seconds delay)
start <Process(PoolWorker-164, started daemon)>
end <Process(PoolWorker-164, started daemon)>
(~5 seconds delay)
start <Process(PoolWorker-165, started daemon)>
end <Process(PoolWorker-165, started daemon)>

解决方案

首先,我在这里发布的代码的简化版本中找不到问题。

问题是我想使用实例方法作为我的工作函数。 这在Python中没有开箱即用的功能,因为实例方法无法被腌制。 然而,there是Steven Bethard的一种解决方法,它可以解决这个问题(以及我使用过的)。

此解决方法的问题是,它需要pickle包含worker方法的类的实例。 在我的例子中,该实例具有引用大型数据结构的属性。 因此,每次实例方法被腌制时,所有这些庞大的数据结构都会被复制,导致上述问题。

0 个答案:

没有答案