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

How can I explicitly free memory in Python?

我编写了一个python程序,它作用于一个大的输入文件,创建数百万个表示三角形的对象。算法是:

  • 读取输入文件
  • 处理文件并创建三角形列表,由其顶点表示
  • 以关闭格式输出顶点:顶点列表,后跟三角形列表。三角形由顶点列表中的索引表示。
  • off的要求是在打印出三角形之前打印出完整的顶点列表,这意味着在将输出写入文件之前,必须在内存中保存三角形列表。同时,由于列表的大小,我得到了内存错误。

    告诉python我不再需要某些数据,并且可以释放这些数据,最好的方法是什么?


    根据python官方文档,您可以使用gc.collect()强制垃圾收集器释放未引用的内存。例子:

    1
    2
    import gc
    gc.collect()


    不幸的是(取决于您的版本和python的发行版),某些类型的对象使用"空闲列表",这是一种整洁的本地优化,但可能会导致内存碎片化,特别是通过使越来越多的内存"专用于"特定类型的对象,从而不可用于"一般资金"。

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

    在您的用例中,子流程积累一些结果并确保这些结果对主流程可用的最好方法似乎是使用半临时文件(我的意思是,半临时文件,不是关闭时自动消失的文件,而是在完成所有操作后显式删除的普通文件他们)


    del语句可能有用,但iirc不能保证释放内存。医生在这里…还有一个为什么不发布的原因在这里。

    我听说Linux和Unix类型的系统上有人用一个python进程来做一些工作,获取结果,然后杀死它。

    本文对Python垃圾收集器有一些说明,但我认为缺乏内存控制是托管内存的缺点。


    python是垃圾收集的,因此如果减少列表的大小,它将回收内存。您还可以使用"del"语句完全消除变量:

    1
    2
    3
    biglist = [blah,blah,blah]
    #...
    del biglist


    您不能显式释放内存。您需要做的是确保不保留对对象的引用。然后它们将被垃圾收集,释放内存。

    在您的例子中,当您需要大列表时,通常需要重新组织代码,通常使用生成器/迭代器。这样你就不需要在内存中有大的列表了。

    http://www.prasanatech.net/2009/07/introduction-python-generators.html


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

    也许您一开始不会遇到任何内存问题,因为您的数据使用了更紧凑的结构。因此,与标准array模块或第三方numpy模块使用的格式相比,数字列表的内存效率要低得多。通过将顶点放在一个numpy 3xn数组中,将三角形放在一个n元素数组中,可以节省内存。


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

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

    也就是说,有一些工具可以帮助实现这一点:

    • 赛隆
    • 斯威格
    • 增强Python


    我在从文件中读取图表时遇到了类似的问题。处理过程包括计算一个不适合内存的200 000×200 000浮点矩阵(一次一行)。尝试在使用gc.collect()的计算之间释放内存修复了问题与内存相关的方面,但它导致了性能问题:我不知道为什么,但即使已用内存量保持不变,每次对gc.collect()的新调用都比前一次花费了更多的时间。所以垃圾收集占用了大部分的计算时间。

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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


    如果不关心顶点重用,可以有两个输出文件——一个用于顶点,一个用于三角形。完成后,将三角形文件附加到顶点文件。