多处理管理器进程不释放内存

时间:2014-02-20 12:17:23

标签: python python-2.7 memory-leaks multiprocessing

在我正在开发的应用程序中,我使用multiprocessing.BaseManager与主进程并行执行一些繁重而复杂的计算。我使用的是Manager而不是Pool,因为这些计算是以class实现的,需要偶尔执行一次。

每次我在管理器中创建一个新的计算类实例时,调用它的'方法,取回结果,然后删除实例并在管理器中调用gc.collect()。

这是一个伪代码来演示这种情况:

import gc
from multiprocessing.managers import BaseManager

class MyComputer(object):
   def compute(self, args):
      #several steps of computations
      return huge_list

class MyManager(BaseManager): pass
MyManager.register('MyComputer', MyComputer)
MyManager.register('gc_collect', gc.collect)

if __name__ == '__main__':
   manager = MyManager()
   manager.start()

   #obtain args_list from the configuration file

   many_results = []
   for args in args_list:
      comp = manager.MyComputer()
      many_results.append(comp.compute(args))
      del comp
      manager.gc_collect()

   #do somthing with many_results

计算结果很大(200Mb-600Mb)。问题是:根据top,经理过程使用的驻留内存在计算后显着增长(50Mb到1Gb)。如果在所有计算中使用单个comp对象或未调用manager.gc_collect(),则快得多。所以我猜这个对象确实被删除了,垃圾收集器也能正常工作,但还是留下了一些东西。

以下是Manager过程在五轮计算中使用的驻留内存图:http://i.imgur.com/BY6KuXD.png

我的问题是:

  1. 我是否需要在MyComputer实现中搜索内存泄漏,或者这只是python内存管理系统的一个功能?
  2. 如果后者是真的,有没有办法强制管理员进程将其“释放”的内存返回给操作系统?

2 个答案:

答案 0 :(得分:1)

经过一周多的研究,我正在回答自己的问题:

  1. 描述的内存使用情况配置文件确实是Python内存管理系统的一个功能,它不会释放为小对象分配的内存。因此,如果计算期间生成的数据量很大,则最好预先分配将包含它的对象。 NumPy数组是一个选项;也许是内置数组。
  2. 不,没有办法做到这一点。更重要的是:据我所知,即使在C中,free()调用也不一定会导致内存返回操作系统。
  3. 调查的另一个重要结论是:

    请注意这些巨大的内存峰值(http://i.imgur.com/BY6KuXD.png)。它们比产生的任何结果(~250Mb)大得多。事实证明,这是因为他们在这个过程中被腌渍了。酸洗是一个非常昂贵的过程;它的内存使用量与要被pickle的对象的大小有非线性关系。因此,如果你(un)腌制一个大约10Mb的物体,它使用~12-13Mb,但是(〜)酸洗~250Mb使用800-1000Mb!因此,为了挑选一个大对象(包括任何管道,队列,连接,架子等的使用),你需要以某种方式序列化这个过程。 / p>

答案 1 :(得分:0)

很难猜出是什么问题。因为内存泄漏总是很难找到。 如果您没有,我建议您安装memory_profiler。它可以帮助您轻松找到内存问题。

仅举例说明如何使用它:

test.py

@profile                                                                        
def foo():                                                                      
    f = open('CC_2014.csv', 'rb')                                               
    lines_f = f.readlines()*10000                                               
    f.close()                                                                   
    lines_f = None                                                              

foo()

正如您所看到的,我将@profile装饰器添加到我怀疑存在内存问题的函数中。 然后像这样运行你的脚本:

python -m memory_profiler test.py

结果是:

Line #    Mem usage    Increment   Line Contents
================================================
     1    9.316 MiB    0.000 MiB   @profile
     2                             def foo():
     3    9.316 MiB    0.000 MiB       f = open('CC_2014.csv', 'rb')
     4  185.215 MiB  175.898 MiB       lines_f = f.readlines()*10000
     5  185.211 MiB   -0.004 MiB       f.close()
     6    9.656 MiB -175.555 MiB       lines_f = None

从这个输出中你可以很容易地看到哪一行占用了大量的内存。