Python的垃圾回收真的这么慢?

18 投票
3 回答
17296 浏览
提问于 2025-04-16 05:22

我在使用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()这个函数占用了大部分的运行时间!!

这是cProfilepstats的输出结果:


    >>> p.sort_stats("time").print_stats(20)
Wed Oct 20 17:46:02 2010    mainProgram.profile

         547303 function calls (542629 primitive calls) in 548.060 CPU seconds

   Ordered by: internal time
   List reduced from 727 to 20 due to restriction 

   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__)
[...]

>>> p.sort_stats("cumulative").print_stats(20)
Wed Oct 20 17:46:02 2010    mainProgram.profile

         547303 function calls (542629 primitive calls) in 548.060 CPU seconds

   Ordered by: cumulative time
   List reduced from 727 to 20 due to restriction 

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001  548.068  548.068 PlotD3PD_v3.2.py:2492(main)
        4    0.000    0.000  346.756   86.689 /usr/lib//lib/python2.5/site-packages/guppy/heapy/Use.py:171(heap)
        4    0.005    0.001  346.752   86.688 /usr/lib//lib/python2.5/site-packages/guppy/heapy/View.py:344(heap)
        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(SaveItems)
     9900   10.425    0.001   10.426    0.001 PlotD3PD_v3.2.py:1973(HistoStyle)
    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()程序之前的输出。

memory usage before return:
Partition of a set of 65901 objects. Total size = 4765572 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  25437  39  1452444  30   1452444  30 str
     1   6622  10   900592  19   2353036  49 dict of PlotROOTUtils.Canvas
     2    109   0   567016  12   2920052  61 dict of module
     3   7312  11   280644   6   3200696  67 tuple
     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 dict (no owner)
     8    254   0   119024   2   4010240  84 dict of type
     9    254   0   109728   2   4119968  86 type
  Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
    10   1917   3   107352   2   4264012  88 function
    11   3647   5   102116   2   4366128  90 ROOT.MethodProxy
    12    148   0    80800   2   4446928  92 dict of class
    13   1109   2    39924   1   4486852  93 __builtin__.wrapper_descriptor
    14    239   0    23136   0   4509988  93 list
    15     87   0    22968   0   4532956  94 dict of guppy.etc.Glue.Interface
    16    644   1    20608   0   4553564  94 types.BuiltinFunctionType
    17    495   1    19800   0   4573364  94 __builtin__.weakref
    18     23   0    11960   0   4585324  95 dict of guppy.etc.Glue.Share
    19    367   1    11744   0   4597068  95 __builtin__.method_descriptor

有没有什么想法,或者如何优化垃圾回收?

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

3 个回答

-5

如果你的问题真的是因为垃圾回收,那你可以在用完对象后,明确地使用 del() 来释放它们。

一般来说,这听起来不像是垃圾回收的问题,除非你在处理的是几TB的内存。

我同意S.Lott的看法……先对你的应用进行性能分析,然后把代码片段和结果带回来,这样我们才能提供更有效的帮助。

5

是的,这可能是垃圾回收的问题,但也可能是和C++代码的同步问题,或者是其他完全不同的原因(没有代码的话很难判断)。

无论如何,你应该去看看 Python/C++集成开发的特别兴趣小组,那里可以找到一些问题和加速的方法。

14

这是一个在Python 2.6中已知的垃圾回收问题,导致当你创建很多对象而不释放它们时,垃圾回收的速度变得很慢,就像在处理一个很大的列表一样。
有两个简单的解决办法:

  1. 第一种是,在填充大列表之前先关闭垃圾回收,填充完后再开启它。

    l = []
    gc.disable()
    for x in xrange(10**6):
      l.append(x)
    gc.enable()
    
  2. 第二种是,升级到Python 2.7,这个问题在这个版本中已经解决了

我更喜欢第二种解决办法,但有时候这并不是一个可行的选择;)

撰写回答