【问题标题】:Multiprocessing manager process does not free memory多处理管理器进程不释放内存
【发布时间】:2014-03-21 09:10:35
【问题描述】:

在我正在开发的应用程序中,我使用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. 如果后者为真,是否有任何方法可以强制管理器进程将其“释放”的内存返回给操作系统?

【问题讨论】:

  • 如果在主进程中进行计算(完全不涉及多处理)会有什么不同吗?
  • 不。第一次执行计算时,进程使用的常驻内存有所增长,但在每次后续迭代结束时它保持不变。所以del results; del comp; gc.collect() 释放迭代期间分配的所有内存。
  • 我的错误。我当然的意思是“是的,它是不同的”

标签: python python-2.7 memory-leaks multiprocessing


【解决方案1】:

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

  1. 描述的内存使用配置文件确实是 Python 内存管理系统的一个特性,它不会释放分配给小对象的内存。因此,如果在计算过程中产生的数据量很大,最好预先分配将包含它的对象。 NumPy 数组是一种选择;也可能是内置数组。
  2. 不,没有办法做到这一点。更重要的是:据我所知,即使在 C 中,free() 调用也不一定会导致内存返回给操作系统。

调查的另一个重要结论:

请注意这些巨大的内存峰值 (http://i.imgur.com/BY6KuXD.png)。它们比产生的任何结果 (~250Mb) 的大小都要大得多。事实证明,这是因为它们在这个过程中被腌制了。酸洗是一个非常昂贵的过程;它的内存使用对要腌制的对象的大小具有非线性依赖性。因此,如果您(取消)腌制一个 ~10Mb 大的对象,它使用 ~12-13Mb,但(取消)腌制 ~250Mb 使用 800-1000Mb!因此,为了腌制一个大对象(其中包括管道、队列、连接、架子等的任何用法),您需要以某种方式序列化该过程。

【讨论】:

    【解决方案2】:

    很难猜出问题所在。因为内存泄漏总是很难找到。 如果您没有安装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
    

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

    【讨论】:

    • 我已经尝试过 pmpler 和 heappy 来配置文件。 memory_profiler 由于某种原因不能与我的代码一起使用(它只输出表头,但没有关于内存使用的数据)。但是你给我指出了一个想法:我将尝试通过子类化 BaseManager 来分析管理器进程......
    猜你喜欢
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多