【问题标题】:Python garbage collection can be that slow?Python 垃圾回收会那么慢吗?
【发布时间】:2011-04-24 09:53:48
【问题描述】:

我的 python 应用程序有问题,我认为它与 python 垃圾收集有关,即使我不确定......

问题是我的应用程序需要很长时间才能退出并从一个功能切换到下一个功能。

在我的应用程序中,我处理非常大的字典,其中包含从包装的 C++ 类实例化的数千个大对象。

我在我的程序中放了一些时间戳输出,我看到在每个函数结束时,当函数内部创建的对象超出范围时,解释器在调用下一个函数之前会花费大量时间。我在应用程序结束时观察到同样的问题,当程序应该退出时:在屏幕上的最后一个时间戳和新提示的出现之间花费了很多时间(〜小时!)。

内存使用稳定,所以我真的没有内存泄漏。

有什么建议吗?

可能是数以千计的大型 C++ 对象的垃圾收集速度很慢?

有没有加快速度的方法?

更新:

非常感谢您的所有回答,您给了我很多调试代码的提示:-)

我在 Scientific Linux 5 上使用 Python 2.6.5,这是一个基于 Red Hat Enterprise 5 的定制发行版。 实际上,我并没有使用 SWIG 为我们的 C++ 代码获取 Python 绑定,而是使用 Reflex/PyROOT 框架。我知道,它在粒子物理学之外不是很为人所知(但仍然是开源的并且免费提供),我必须使用它,因为它是我们主框架的默认设置。

在这种情况下,Python 端的 DEL 命令不起作用,我已经尝试过了。 DEL只删除了链接到C++对象的python变量,而不是内存中的对象本身,仍然归C++端所有……

...我知道,我猜这不是标准的,而且有点复杂,抱歉:-P

但是按照您的提示,我将分析我的代码,并按照您的建议向您提供更多详细信息。

附加更新:

好的,按照你的建议,我用cProfile 检测了我的代码,我发现实际上gc.collect() 函数是占用大部分运行时间的函数!!

这里是cProfile + pstats print_stats() 的输出:

>>> p.sort_stats("时间").print_stats(20) 2010 年 10 月 20 日星期三 17:46:02 mainProgram.profile 在 548.060 CPU 秒内进行 547303 次函数调用(542629 次原语调用) 排序:内部时间 由于限制,名单从 727 减少到 20 ncalls tottime percall cumtime percall filename:lineno(function) 4 345.701 86.425 345.704 86.426 {gc.collect} 1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotSamplesBranches) 28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(保存项目) 9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(组织风格) 6622 5.188 0.001 5.278 0.001 PlotROOTUtils.py:403(__init__) 57 0.625 0.011 0.625 0.011 {内置方法加载} 103 0.625 0.006 0.792 0.008 dbutils.py:41(死锁包裹) 14 0.475 0.034 0.475 0.034 {“cPickle.Pickler”对象的“转储”方法} 6622 0.453 0.000 5.908 0.001 PlotROOTUtils.py:421(CreateCanvas) 26455 0.434 0.000 0.508 0.000 /opt/root/lib/ROOT.py:215(__getattr__) [...] >>> p.sort_stats("累积").print_stats(20) 2010 年 10 月 20 日星期三 17:46:02 mainProgram.profile 在 548.060 CPU 秒内进行 547303 次函数调用(542629 次原语调用) 排序:累计时间 由于限制,名单从 727 减少到 20 ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 548.068 548.068 PlotD3PD_v3.2.py:2492(主) 4 0.000 0.000 346.756 86.689 /usr/lib//lib/python2.5/site-packages/guppy/heapy/Use.py:171(堆) 4 0.005 0.001 346.752 86.688 /usr/lib//lib/python2.5/site-packages/guppy/heapy/View.py:344(堆) 1 0.002 0.002 346.147 346.147 PlotD3PD_v3.2.py:2537(LogAndFinalize) 4 345.701 86.425 345.704 86.426 {gc.collect} 1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotBranches) 28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(保存项目) 9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(组织风格) 13202 0.336 0.000 6.818 0.001 PlotROOTUtils.py:431(PlottingCanvases) 6622 0.453 0.000 5.908 0.001 /root/svn_co/rbianchi/SoftwareDevelopment [...] >>>

因此,在两个输出中,分别按“时间”和“累积”时间排序,gc.collect() 是占用我程序运行时间最多的函数! :-P

这是内存分析器Heapy 的输出,就在返回main() 程序之前。

返回前的内存使用情况: 一组 65901 个对象的分区。总大小 = 4765572 字节。 索引计数 % 大小 % 累积 % 种类(类/类字典) 0 25437 39 1452444 30 1452444 30 力量 1 6622 10 900592 19 2353036 49 PlotROOTUtils.Canvas 的字典 2 109 0 567016 12 2920052 61 模块字典 3 7312 11 280644 6 3200696 67 元组 4 6622 10 238392 5 3439088 72 0xa4ab74c 5 6622 10 185416 4 3624504 76 PlotROOTUtils.Canvas 6 2024 3 137632 3 3762136 79 种代码类型 7 263 0 129080 3 3891216 82 dict(无所有者) 8 254 0 119024 2 4010240 84 字典类型 9 254 0 109728 2 4119968 86型 索引计数 % 大小 % 累积 % 种类(类/类字典) 10 1917 3 107352 2 4264012 88 功能 11 3647 5 102116 2 4366128 90 ROOT.MethodProxy 12 148 0 80800 2 4446928 92 类字典 13 1109 2 39924 1 4486852 93 __builtin__.wrapper_descriptor 14 239 0 23136 0 4509988 93 名单 15 87 0 22968 0 4532956 94 guppy.etc.Glue.Interface 的字典 16 644 1 20608 0 4553564 94 种类型。BuiltinFunctionType 17 495 1 19800 0 4573364 94 __builtin__.weakref 18 23 0 11960 0 4585324 95 guppy.etc.Glue.Share 的字典 19 367 1 11744 0 4597068 95 __builtin__.method_descriptor

知道为什么或如何优化垃圾收集吗?

我可以做更详细的检查吗?

【问题讨论】:

  • “有什么建议吗?”。使用分析器获取更多关于时间花费的事实。将结果发布为您问题的更新。
  • @nos:实际上,Python 使用了引用计数,所以会收集一个未引用的对象。与优秀的 JVM 和 .NET 中的聪明野兽相比,Python 的 GC 相当简单。
  • @delnan 准确地说,CPython 的实现具有这种行为。我似乎记得一些实验版本的算法更高级。
  • @Paul:没错,IronPython 和 Jython 使用各自 VM 的 GC(因此没有重新计算)。但通常,这可以忽略;)
  • 哪个操作系统和哪个版本?所以你的内存使用是稳定的——稳定大于你的进程可用的真实内存?你有多少物理真实内存?在流程开始之前有多少是免费的?就在它退出之前?您是否尝试过以正常数量的对象的 1/4、1/2、3/4 运行它,您观察到了什么?是逐渐变得呆滞,还是突然“撞墙”?考虑告诉我们有多少个对象/分钟/小时,而不是“很多”和“数千”等。

标签: python garbage-collection root performance


【解决方案1】:

是的,它可能是垃圾收集,但也可能是与 C++ 代码的某种同步,或者完全不同的东西(没有代码很难说)。

无论如何,您应该看看SIG for development of Python/C++ integration 以查找问题以及如何加快处理速度。

【讨论】:

  • 嗨,kriss,显然它有垃圾收集器问题。我用 cProfile 的输出更新了我的问题,似乎 gc.collect 占用了大部分运行时间......我会按照你的建议看一下 SIG Python/C++,看看它是否是已知的在某些情况下会出现问题。
  • 原始赌注:gc 可能是在引用计数无法正常工作的情况下(必须使用更复杂的策略来回收内存)。您的数据结构中的任何循环(循环引用)?如果这是问题的根源,“手动”切割它们可能是一个解决方案,它会让 gc 更快乐(因此更快)。
【解决方案2】:

如果您的问题确实是垃圾回收,请尝试在使用 del() 处理完对象后显式释放它们。

一般来说,这听起来不像是垃圾回收问题,除非我们谈论的是 TB 级内存。

我同意 S.Lott... 分析您的应用,然后将代码 sn-ps 和结果返回,我们可以提供更多帮助。

【讨论】:

  • del 不会释放任何东西。它只是从当前范围中删除一个变量,即删除一个引用。但是 Python 被引用了(确实存在并运行了更复杂的 GC,但仅在循环引用上) - 如果一堆对象在函数结束时被 gc'd 或者当你认为你认为你是小块时,这并不重要'完成了。
  • 一般来说,是的。在病态的情况下,释放小块可能会有所帮助。习惯性地到处使用del 往往表明你不是在用 Python 编程。
  • 谢谢,保罗和德尔南。其实我也尝试过使用del(),但是在这种情况下是行不通的。正如我在更新我的问题时所说的,我正在使用一个名为 ROOT (root.cern.ch) 的开源框架,它有自己的 python 绑定系统(称为 Reflex),即使 del() 删除了对C++ 对象,对象本身保留在内存中......但即使现在我找到了一种明确删除它们的方法,gc.collect 函数似乎占用了大部分运行时间......对进一步检查有什么建议吗?再次感谢您的帮助
【解决方案3】:

This is known garbage collector issue in Python 2.6 当许多对象被分配而没有释放它们中的任何一个时,导致垃圾收集的二次时间,即。大名单的人口。
有两个简单的解决方案:

  1. 在填充大型列表之前禁用垃圾收集并在之后启用它

    l = []
    gc.disable()
    for x in xrange(10**6):
      l.append(x)
    gc.enable()
    
  2. 或更新至Python 2.7, where the issue has been solved

我更喜欢第二种解决方案,但它并不总是一种选择;)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-04
    • 1970-01-01
    • 2012-06-23
    • 2010-10-26
    相关资源
    最近更新 更多