使用Python多处理的高内存使用率

时间:2014-01-31 16:40:38

标签: python performance memory multiprocessing

我已经看过几篇关于使用Python Multiprocessing模块的内存使用情况的帖子。然而问题似乎没有回答我在这里遇到的问题。我发布我的分析,希望有人可以帮助我。

问题

我正在使用多处理来并行执行任务,我注意到工作进程的内存消耗无限增长。我有一个小的独立示例,应该复制我注意到的内容。

import multiprocessing as mp
import time

def calculate(num):
    l = [num*num for num in range(num)]
    s = sum(l)
    del l       # delete lists as an  option
    return s

if __name__ == "__main__":
    pool = mp.Pool(processes=2)
    time.sleep(5)
    print "launching calculation"
    num_tasks = 1000
    tasks =  [pool.apply_async(calculate,(i,)) for i in range(num_tasks)]
    for f in tasks:    
        print f.get(5)
    print "calculation finished"
    time.sleep(10)
    print "closing  pool"
    pool.close()
    print "closed pool"
    print "joining pool"
    pool.join()
    print "joined pool"
    time.sleep(5)

系统

我正在运行Windows,我使用任务管理器来监控内存使用情况。我正在运行Python 2.7.6。

观察

我已经通过以下2个工作进程总结了内存消耗。

+---------------+----------------------+----------------------+
|  num_tasks    |  memory with del     | memory without del   |
|               | proc_1   | proc_2    | proc_1   | proc_2    |
+---------------+----------------------+----------------------+
| 1000          | 4884     | 4694      | 4892     | 4952      |
| 5000          | 5588     | 5596      | 6140     | 6268      |
| 10000         | 6528     | 6580      | 6640     | 6644      |
+---------------+----------------------+----------------------+

在上表中,我尝试更改任务数量并观察在所有计算结束时以及join pool之前消耗的内存。 'del'和'without del'选项分别是我对del l函数内的calculate(num)行取消评论或评论。在计算之前,内存消耗大约是4400。

  1. 看起来手动清除列表会导致工作进程的内存使用量降低。我以为垃圾收集者会照顾这个。有没有办法强制垃圾收集?
  2. 令人费解的是,随着任务数量的增加,两种情况下的内存使用量都在不断增长。有没有办法限制内存使用量?
  3. 我有一个基于这个例子的过程,并且意味着长期运行。我观察到这个工作进程在一夜之间运行后会占用大量内存(~4GB)。执行join释放内存不是一种选择,我试图找到一种没有join的方法。

    这看起来有点神秘。有没有人遇到类似的东西?我该如何解决这个问题?

2 个答案:

答案 0 :(得分:59)

我做了很多研究,但无法找到解决问题的解决方案。但是有一个不错的工作方法可以以很小的代价防止内存井喷,尤其是在服务器端长时间运行的代码上。

解决方案主要是在执行固定数量的任务后重新启动单个工作进程。 python中的Pool类以maxtasksperchild为参数。您可以指定maxtasksperchild=1000,从而限制在每个子进程上运行1000个任务。达到maxtasksperchild后,池会刷新其子进程。使用谨慎的数字来执行最大任务,可以平衡消耗的最大内存,以及与重新启动后端进程相关的启动成本。 Pool构造完成如下:

pool = mp.Pool(processes=2,maxtasksperchild=1000)

我在这里提供完整的解决方案,以便对其他人有用!

import multiprocessing as mp
import time

def calculate(num):
    l = [num*num for num in range(num)]
    s = sum(l)
    del l       # delete lists as an  option
    return s

if __name__ == "__main__":

    # fix is in the following line #
    pool = mp.Pool(processes=2,maxtasksperchild=1000)

    time.sleep(5)
    print "launching calculation"
    num_tasks = 1000
    tasks =  [pool.apply_async(calculate,(i,)) for i in range(num_tasks)]
    for f in tasks:    
        print f.get(5)
    print "calculation finished"
    time.sleep(10)
    print "closing  pool"
    pool.close()
    print "closed pool"
    print "joining pool"
    pool.join()
    print "joined pool"
    time.sleep(5)

答案 1 :(得分:1)

这里的一个潜在问题是结果可能以任何顺序返回,但是由于您按顺序读取它们,因此必须将所有从进程返回的结果存储在内存中。 num_tasks 越高,它可能必须存储在内存中的结果越多,等待您的 for任务中的f 循环处理结果。

在最坏的情况下,结果以完全相反的顺序计算。在这种情况下,必须先将所有结果保存在内存中的多处理模块中,然后任务的 for 循环才会开始处理任何内容。

虽然它们使用的内存量似乎比我在这种情况下的预期要高(存储的calculate()函数返回的1000-10000个数字要多于此数量),但是可能存储的每个工作人员结果的开销总是很高。

您是否尝试为 apply_async 指定 callback 参数,以便您可以在完成结果后立即处理结果,也可以使用 imap_unordered ,这样可以在准备好结果后立即将结果给您?