如何在 Python 中显式地释放内存?

547 人关注

我写了一个Python程序,作用于一个大的输入文件,创建几百万个代表三角形的对象。 该算法是

  • read an input file
  • process the file and create a list of triangles, represented by their vertices
  • output the vertices in the OFF format: a list of vertices followed by a list of triangles. The triangles are represented by indices into the list of vertices
  • OFF要求我在打印出三角形之前打印出完整的顶点列表,这意味着我在将输出写入文件之前必须在内存中保留三角形列表。 同时,由于列表的大小,我遇到了内存错误。

    什么是告诉Python我不再需要某些数据的最好方法,它可以被释放?

    2 个评论
    为什么不把三角形打印到一个中间文件中,当你需要时再把它们读回来?
    这个问题有可能是关于两件完全不同的事情。这些错误是 来自同一个Python进程的 在这种情况下,我们关心的是将内存释放到Python进程的堆中,还是它们来自系统中的不同进程,在这种情况下,我们关心的是将内存释放到操作系统中?
    python
    memory
    memory-management
    Nathan Fellman
    Nathan Fellman
    发布于 2009-08-23
    10 个回答
    Havenard
    Havenard
    发布于 2009-08-23
    已采纳
    0 人赞同

    根据 Python官方文档 ,你可以明确地调用垃圾收集器,用 gc.collect() 释放未引用的内存。例子。

    import gc
    gc.collect()
    

    你应该在使用del标记你想抛弃的东西之后再做。

    del my_array
    del my_object
    gc.collect()
        
    反正除了一些不寻常的情况外,东西都是经常被垃圾收集的,所以我认为这不会有什么帮助。
    一般来说,gc.collect()是应该避免的。 垃圾收集器知道如何做它的工作。 这就是说,如果OP是在一个情况下,他突然去分配一个 lot 的对象(如数以百万计),gc.collect可能被证明是有用的。
    RobM
    实际上,在一个循环的末尾自己调用 gc.collect() 可以帮助避免内存碎片化,这反过来有助于保持性能。我见过这样做有很大的区别(约20%的运行时间,我记得)。
    John
    我使用 python 3.6。从hdf5加载一个pandas数据框架(500k行)后,调用 gc.collect() ,内存占用从1.7GB减少到500MB。
    我需要在一个32GB内存的系统中加载和处理几个25GB的numpy数组。在处理完数组后,使用 del my_array gc.collect() 是唯一能真正释放内存并让我的进程存活到加载下一个数组的方法。
    Alex Martelli
    Alex Martelli
    发布于 2009-08-23
    0 人赞同

    不幸的是 (取决于你的Python版本和发行版),一些类型的对象使用 "自由列表",这是一个整洁的局部优化,但可能会导致内存碎片化,特别是通过使越来越多的内存 "专门用于 "某种类型的对象,从而使 "普通基金 "无法使用。

    唯一真正可靠的方法是让子进程完成对内存的大量临时使用,并在完成后将所有资源返还给系统,而子进程在完成消耗内存的工作后会终止。在这种情况下,操作系统将完成它的工作,并高兴地回收子进程可能占用的所有资源。幸运的是, multiprocessing 模块使这种操作 (过去是相当痛苦的) 在现代版本的 Python 中不至于太糟糕。

    在你的用例中,似乎子进程积累一些结果并确保这些结果对主进程可用的最好方法是使用半临时文件(我说的半临时文件是指,不是那种关闭后自动消失的文件,只是你在用完后明确删除的普通文件)。

    我当然希望看到这样一个微不足道的例子。
    我是认真的。@AaronHall说的是。
    @AaronHall 微不足道的例子 现在可用 ,使用 multiprocessing.Manager 而不是文件来实现共享状态。
    如果我有一个被打开的文件指针的列表,我是1)需要del整个列表还是2)列表中的每个元素一个一个地调用 gc.collect()
    @CharlieParker 假设列表是 x = [obj1, obj2, ...obj20] 。为了释放内存,以下任何一种措施都可以做到(1) del x (2) x=[] (3) del x[:] 。只是,对于方法(1),变量 x 被删除,不再可以访问,因此列表 x 的内存也将被释放。而对于方法(2)和(3), x 仍然是可访问的,并且仍然消耗内存。
    Aiden Bell
    Aiden Bell
    发布于 2009-08-23
    0 人赞同

    替换代码0】的声明可能有用,但是IIRC 并不保证能释放内存 . The 文档在这里 ...和一个 为什么不放的原因在这里 .

    我曾听说有人在Linux和Unix类型的系统上分叉一个Python进程来做一些工作,得到结果后就把它杀掉。

    这篇文章 有关于Python垃圾收集器的说明,但我认为 缺乏对内存的控制是管理内存的缺点。

    IronPython和Jython是否是避免这一问题的另一种选择?
    @voyager:不,它不会。任何其他语言也不会,真的。问题是他把大量的数据读进了一个列表,而这些数据对内存来说太大。
    It would likely be worse 在IronPython或Jython下。 在这些环境下,如果没有其他东西持有引用,你甚至不能保证内存会被释放。
    @voyager,是的,因为Java虚拟机会在全局范围内寻找需要释放的内存。对于JVM来说,Jython并没有什么特别之处。另一方面,JVM也有自己的缺点,比如说,你必须事先声明它能使用多大的堆。
    这是对Python垃圾收集器相当糟糕的实现。Visual Basic 6和VBA也有管理内存,但从来没有人抱怨过那里的内存没有被释放。
    Ned Batchelder
    Ned Batchelder
    发布于 2009-08-23
    0 人赞同

    Python 是垃圾收集的,所以如果你减少列表的大小,它将回收内存。 你也可以使用 "del "语句来完全摆脱一个变量。

    biglist = [blah,blah,blah]
    del biglist
        
    这是不对的,也是不对的。 虽然减少列表的大小允许内存被回收,但不能保证何时会发生。
    没有,但通常会有帮助。然而,按照我对这里的问题的理解,问题是他必须有这么多的对象,以至于他在处理所有的对象之前就耗尽了内存,如果他把它们读成一个列表的话。在他处理完之前删除这个列表不太可能是一个有用的解决方案。)
    还要注意,del并不保证一个对象会被删除。 如果有其他对该对象的引用,它将不会被释放。
    biglist = [ ] 会否释放内存?
    是的,如果旧列表没有被其他东西引用的话。
    Eric O Lebigot
    Eric O Lebigot
    发布于 2009-08-23
    0 人赞同

    ( del 可以成为你的朋友,因为当没有其他引用的时候,它将对象标记为可删除。现在,CPython解释器通常会保留这些内存供以后使用,所以你的操作系统可能不会看到 "释放 "的内存)。

    也许你一开始就不会遇到任何内存问题,因为你的数据使用了一个更紧凑的结构。 因此,数字列表比标准的 array 模块或第三方的 numpy 模块所使用的格式更省内存。 你可以把顶点放在NumPy的3xN数组中,把三角形放在N元素数组中,从而节省内存。

    是吗?CPython的垃圾收集是基于引用计数的;它不是定期的标记和清扫(就像许多常见的JVM实现),而是在引用计数为零时立即删除。只有循环(Refcounts本来是零的,但由于引用树中的循环而没有)需要定期维护。替换代码0】并没有做任何事情,只是给引用一个对象的所有名字重新分配一个不同的值。
    我明白你的想法:我会相应地更新答案。我理解CPython解释器实际上是以某种中间方式工作的。替换代码0】从Python的角度释放了内存,但通常不会从C运行库或操作系统的角度释放。参考文献。 stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/... .
    同意你的链接内容,但假设OP说的是他们得到的一个错误 来自同一个Python进程的 在这个问题上,将内存释放到进程本地堆和释放到操作系统之间的区别似乎并不重要 (因为释放到堆使得该空间可用于该 Python 进程中的新分配)。对于这一点, del 对退出范围、重新分配等同样有效。
    Lennart Regebro
    Lennart Regebro
    发布于 2009-08-23
    0 人赞同

    你不能明确地释放内存。你需要做的是确保你不保留对对象的引用。然后它们将被垃圾回收,释放内存。

    在你的情况下,当你需要大的列表时,你通常需要重新组织代码,通常使用生成器/迭代器来代替。这样一来,你根本不需要在内存中设置大的列表。

    如果这种方法是可行的,那么它可能值得做。 但需要注意的是,你不能对迭代器进行随机访问,这可能会引起问题。
    这是真的,如果这是必要的,那么随机访问大型数据数据集就可能需要某种数据库。
    你可以轻松地使用一个迭代器来提取另一个迭代器的随机子集。
    没错,但这样你就必须遍历所有的东西来获得子集,这将是非常缓慢的。
    Retzod
    Retzod
    发布于 2009-08-23
    0 人赞同

    我在从文件中读取图形时遇到了类似的问题。处理过程包括计算一个200 000x200 000的浮动矩阵(一次一行),这不适合内存。试图用 gc.collect() 在计算之间释放内存,解决了与内存有关的问题,但它导致了性能问题。我不知道为什么,尽管使用的内存量保持不变,但每一次对 gc.collect() 的调用都比前一次多花一些时间。所以很快,垃圾收集就占用了大部分的计算时间。

    为了解决内存和性能问题,我改用了我在某处读到的多线程技巧(很抱歉,我已经找不到相关帖子了)。之前我在一个大的 for 循环中读取文件的每一行,对其进行处理,并每隔一段时间就运行 gc.collect() 以释放内存空间。现在我调用一个函数,在一个新的线程中读取并处理文件的一个块。一旦线程结束,内存就会自动释放,不会出现奇怪的性能问题。

    实际上,它是这样工作的。

    from dask import delayed  # this module wraps the multithreading
    def f(storage, index, chunk_size):  # the processing function
        # read the chunk of size chunk_size starting at index in the file
        # process it using data in storage if needed
        # append data needed for further computations  to storage 
        return storage
    partial_result = delayed([])  # put into the delayed() the constructor for your data structure
    # I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
    chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
    for index in range(0, len(file), chunk_size):
        # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
        partial_result = delayed(f)(partial_result, index, chunk_size)
        # no computations are done yet !
        # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
        # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
        # it also allows you to use the results of the processing of the previous chunks in the file if needed
    # this launches all the computations
    result = partial_result.compute()
    # one thread is spawned for each "delayed" one at a time to compute its result
    # dask then closes the tread, which solves the memory freeing issue
    # the strange performance issue with gc.collect() is also avoided
        
    我想知道为什么你在Python中使用`//``s而不是#作为注释。
    我把不同的语言搞混了。谢谢你的意见,我更新了语法。
    Jason Baker
    Jason Baker
    发布于 2009-08-23
    0 人赞同

    其他人已经发布了一些方法,你也许能够 "哄骗 "Python 解释器释放内存 (或者避免出现内存问题)。 有可能你应该先试试他们的想法。 然而,我觉得直接回答你的问题很重要。

    其实没有任何方法可以直接告诉 Python 释放内存。 事实是,如果你想要那么低的控制水平,你必须用 C 或 C++ 写一个扩展。

    这就是说,有一些工具可以帮助解决这个问题。

  • cython
  • boost python
  • gc.collect()和del gc.garbage[:]在我使用大量内存的时候工作得很好
    Joril
    Joril
    发布于 2009-08-23
    0 人赞同

    正如其他答案已经说过的,Python可以不向操作系统释放内存,即使它不再被Python代码使用(所以 gc.collect() 不会释放任何东西),特别是在一个长期运行的程序中。总之,如果你在Linux上,你可以尝试通过直接调用libc函数 malloc_trim 来释放内存( 手册页 ). Something like:

    import ctypes