Python:垃圾收集失败了吗?

moo*_*eep 4 python garbage-collection

请考虑以下脚本:

l = [i for i in range(int(1e8))]
l = []
import gc
gc.collect()
# 0
gc.get_referrers(l)
# [{'__builtins__': <module '__builtin__' (built-in)>, 'l': [], '__package__': None, 'i': 99999999, 'gc': <module 'gc' (built-in)>, '__name__': '__main__', '__doc__': None}]
del l
gc.collect()
# 0
Run Code Online (Sandbox Code Playgroud)

关键是,在所有这些步骤之后,我的机器上的这个python进程的内存使用率大约是30%(Python 2.6.5,请求的更多细节?).这是top的输出的摘录:

 PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND  
5478 moooeeeep 20   0 2397m 2.3g 3428 S    0 29.8   0:09.15 ipython  
Run Code Online (Sandbox Code Playgroud)

RESP.ps aux:

moooeeeep 5478  1.0 29.7 2454720 2413516 pts/2 S+   12:39   0:09 /usr/bin/python /usr/bin/ipython gctest.py
Run Code Online (Sandbox Code Playgroud)

根据该文档gc.collect:

并非某些免费列表中的所有项目都可能因特定实现而被释放,特别是intfloat.

这是否意味着,如果我(暂时)需要大量的不同intfloat数字,我需要将其导出到C/C++,因为Python GC无法释放内存?


更新

正如本文所暗示的那样,解释器可能是罪魁祸首:

这是你同时创建了500万个整数,每个int对象消耗12个字节."为了速度",Python维护整数对象的内部空闲列表.不幸的是,这个免费清单既是不朽的,也是无限的.花车也使用不朽的无限列表.

然而问题仍然存在,因为我无法避免这些数据(来自外部源的时间戳/值对).我真的被迫放弃Python并回到C/C++吗?


更新2

可能确实是这样,Python实现会导致问题.找到这个答案最终解释了问题和可能的解决方法.

str*_*cat 8

我做了一些测试,这个问题只发生在CPython 2.x. 这个问题在CPython 3.2.2中消失了(它回到了新解释器的内存使用情况),而PyPy 1.8(python 2.7.2)也降低到了与新的pypy进程相同的水平.

所以不,你不需要切换到另一种语言.但是,可能有一种解决方案不会强制您切换到不同的Python实现.

  • 为您对不同翻译实施的研究提供+1!不幸的是,我有一堆不能用于Python 3或PyPy的依赖项(numpy,matplotlib,pytables等). (2认同)

Ric*_*nes 7

你的答案可能在这里:

Python做了很多分配和解除分配.所有对象(包括整数和浮点数等"简单"类型)都存储在堆上.为每个变量调用malloc和free会非常慢.因此,Python解释器使用各种优化的内存分配方案.最重要的一个是名为pymalloc的malloc实现,专门用于处理大量的小分配.任何小于256字节的对象都使用此分配器,而更大的任何对象使用系统的malloc.此实现永远不会将内存返回给操作系统.相反,它会保留它,以防再次需要它.这在短时间内再次使用时效率很高,但如果需要很长时间,则会浪费.


moo*_*eep 6

发现这也是Alex Martelli在另一个帖子中回答的问题.

不幸的是(取决于你的版本和Python版本),某些类型的对象使用"自由列表",这是一个简洁的局部优化但可能导致内存碎片,特别是通过为特定类型的对象创建更多的内存"专用"和从而无法获得"普通基金".

确保大量但临时使用内存的唯一真正可靠的方法是在完成后将所有资源返回给系统,就是在子进程中使用该进程,这会占用大量内存,然后终止工作.在这种情况下,操作系统将完成其工作,并乐意回收子进程可能已经吞噬的所有资源.幸运的是,多处理模块在现代版本的Python中进行这种操作(过去相当痛苦)并不算太糟糕.

在您的用例中,似乎子进程积累一些结果并确保主进程可用的结果的最佳方法是使用半临时文件(半临时文件,我的意思是,不是那种文件,关闭时会自动消失,只有当你完成它们时才明确删除的普通文件).

幸运的是,我能够将内存密集型工作拆分为单独的块,使得解释器在每次迭代后实际释放临时内存.我使用以下包装器作为子进程运行内存密集型函数:

import multiprocessing

def run_as_process(func, *args):
    p = multiprocessing.Process(target=func, args=args)
    try:
        p.start()
        p.join()
    finally:
        p.terminate()
Run Code Online (Sandbox Code Playgroud)