Python垃圾回收会这么慢吗?

18

我的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("time").print_stats(20)
Wed Oct 20 17:46:02 2010    mainProgram.profile

         547303个函数调用(542629个原始调用)共计548.060秒CPU时间
按: 内部时间排序 列表由于限制而从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(SaveItems) 9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(HistoStyle) 6622 5.188 0.001 5.278 0.001 PlotROOTUtils.py:403(__init__) 57 0.625 0.011 0.625 0.011 {built-in method load} 103 0.625 0.006 0.792 0.008 dbutils.py:41(DeadlockWrap) 14 0.475 0.034 0.475 0.034 {method 'dump' of 'cPickle.Pickler' objects} 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__)
在这段代码中,这是一条记录函数调用的统计信息。该函数名称为"__getattr__",位于"/opt/root/lib/ROOT.py"中的第215行。该函数被调用26455次,它的tottime是0.434秒,percall是0.000秒,cumtime是0.508秒,percall是0.000秒。6622 0.453 0.000 5.908 0.001 /root/svn_co/rbianchi/SoftwareDevelopment
[...] >>>
因此,无论是按“时间”还是按“累计”时间排序,gc.collect()函数都是我程序运行时间消耗最大的函数! :-P
这是在返回main()程序之前进行内存分析的Heapy输出。
在返回之前的内存使用情况:
一组65901个对象的划分。总大小=4765572字节。
索引   数量     %     大小      %   累计占用     %  类型(类/类的字典)
     0  25437  39  1452444  30   1452444  30 str
     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 types.CodeType
     7    263   0   129080   3   3891216  82 字典(无所有者)
     8    254   0   119024   2   4010240  84 类型字典
     9    254   0   109728   2   4119968  86 类型
索引   数量     %     大小      %   累计占用     %  类型(类/类的字典)
10 1917 3 107352 2 4264012 88 function 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 内建函数类型 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

你有什么想法,或者怎样优化垃圾回收?

是否还能进行更详细的检查?


9
有建议吗?使用分析器以获取更多有关时间花费的信息。将结果作为更新发布到您的问题中。 - S.Lott
@nos:实际上,Python使用引用计数,因此未被引用的对象被收集。与良好的JVM和.NET中的聪明野兽相比,Python的GC相当简单。 - user395760
1
哪个操作系统和版本?因此,您的内存使用情况是稳定的 - 稳定大于进程可用的实际内存?您有多少物理实际内存?在进程启动之前有多少空闲?它退出之前呢?您是否尝试过以正常对象数量的1/4、1/2、3/4运行它,您观察到了什么?它是否逐渐变得缓慢,还是突然“撞墙”?请考虑告诉我们有多少个对象/分钟/小时,而不是“很多”和“数千”等。 - John Machin
感谢大家提供的有用评论!我按照你们的建议进行了分析,实际上看起来gc.collect函数占用了大部分时间... @John:我也发布了Heapy的输出,但我仍然需要尝试测试我的对象数量的1/4、1/2、3/4。我会尽快发布这些测试结果。 再次感谢! - rmbianchi
最后还有一件事要检查,我想你没有提到:你的磁盘是否在频繁读写?你提到你的对象很大,确保你没有触发交换分区,因为进行磁盘I/O操作总会减慢速度。 - Thanatos
显示剩余4条评论
3个回答

14

这是Python 2.6中已知的垃圾收集器问题,当分配许多对象而没有释放它们时,即大量列表的填充,会导致垃圾收集的二次时间。
有两个简单的解决方案:

  1. 在填充大型列表之前禁用垃圾收集,然后在之后启用垃圾收集。

  2. l = []
    gc.disable()
    for x in xrange(10**6):
      l.append(x)
    gc.enable()
    
  3. 或者升级到Python 2.7,在那里这个问题已经被解决了

我更喜欢第二种解决方案,但并不总是可行的;)


5

是的,可能是垃圾回收,但也可能是与C++代码同步或完全不同的问题(没有代码很难说)。

无论如何,您应该查看Python/C++集成开发SIG,以查找问题并加速处理。


嗨Kriss,显然它有一个垃圾收集器问题。我更新了我的问题,并附上了cProfile的输出,看起来gc.collect占用了大部分运行时间......像你建议的那样,我会查看SIG Python / C ++,以查看在某些环境中是否存在已知问题。 - rmbianchi
原始赌注:gc可能处于引用计数不起作用的情况下(必须使用更复杂的策略来回收内存)。你的数据结构中有任何循环(循环引用)吗?如果它是问题的根源,手动切断它们可能是一个解决方案,这将使gc更加快乐(因此更快)。 - kriss

-5

如果您的问题确实与垃圾回收有关,请尝试使用del()显式释放对象。

一般来说,除非我们谈论的是几TB的内存,否则这不像是一个垃圾回收问题。

我同意S.Lott的观点...对您的应用程序进行分析,然后带回代码片段和结果,我们可以提供更多帮助。


3
del并不会释放任何东西。它只是从当前作用域中删除一个变量,即删除一个引用。但是Python是引用计数的(存在更复杂的GC并运行,但仅在循环引用时)-如果一堆对象在函数结束时或在你认为完成时以小块被gc'd掉,这并不重要。 - user395760
一般来说,是的。在病态情况下,释放小块可能会有所帮助。习惯性地在各个地方使用 del 命令通常意味着你不是在用 Python 编程。 - Paul McMillan
1
谢谢,Paul和Delnan。实际上我也尝试使用del(),但在这种情况下不起作用。正如我在问题更新中所说的那样,我正在使用一个名为ROOT(http://root.cern.ch)的开源框架,它有自己的Python绑定系统(称为Reflex),即使del()删除了所有Python对C++对象的引用,对象本身仍然留在内存中...但是,即使现在我找到了一种明确删除它们的方法,gc.collect函数似乎占用了大部分运行时间...有没有进一步检查的建议?再次感谢您的帮助。 - rmbianchi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接