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


387

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

  1. 读取输入文件
  2. 处理文件并创建一个三角形列表,以其顶点表示
  3. 以OFF格式输出顶点:顶点列表,后跟三角形列表。三角形由顶点列表中的索引表示

在打印出三角形之前先打印出完整的顶点列表的OFF要求意味着在将输出写入文件之前,必须将三角形的列表保留在内存中。同时,由于列表的大小,我遇到了内存错误。

告诉Python我不再需要某些数据并且可以释放它们的最佳方法是什么?


11
为什么不将三角形打印到中间文件中,并在需要时重新读回它们?
爱丽丝·珀赛尔

2
这个问题可能是关于两个截然不同的事情。这些错误是来自同一Python进程,在这种情况下,我们关心将内存释放给Python进程的堆,还是从系统上的不同进程释放,而在这种情况下,我们关心将内存释放给OS?
查尔斯·达菲

Answers:


453

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

import gc
gc.collect()

19
无论如何,都是经常收集垃圾的东西,除非在一些特殊情况下,所以我认为这不会有太大帮助。
Lennart Regebro

24
通常,应避免使用gc.collect()。垃圾收集器知道如何完成其​​工作。也就是说,如果OP处于突然释放许多对象(例如数百万个对象)的情况下,则gc.collect可能会有用。
杰森·贝克

164
实际上gc.collect(),在循环结束时调用您自己可以避免内存碎片,从而有助于提高性能。我已经看到这有很大的不同(运行时IIRC约为20%)
RobM

38
我正在使用python 3.6。gc.collect()从hdf5(50万行)加载熊猫数据帧后调用将内存使用从1.7GB减少到500MB
John John

15
我需要在具有32GB内存的系统中加载和处理25GB的多个numpy数组。在处理完数组之后使用,del my_array然后再使用,gc.collect()这是实际释放内存的唯一方法,而我的进程可以继续加载下一个数组。
戴维(David)

113

不幸的是(取决于您的Python版本和版本),某些类型的对象使用“空闲列表”,这是一种整洁的局部优化,但可能会导致内存碎片,特别是通过为特定类型的对象设置越来越多的“专用”内存来实现。因此无法使用“普通基金”。

确保大量但临时使用内存的唯一真正可靠的方法是在完成后将所有资源都返还给系统,这是让使用发生在子进程中,该进程需要大量内存,然后终止。在这种情况下,操作系统将完成其工作,并乐意回收子进程可能吞没的所有资源。幸运的是,该multiprocessing模块使这种操作(过去很痛苦)在现代版本的Python中还不错。

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


31
我肯定希望看到一个简单的例子。
亚伦·霍尔

3
说真的 @AaronHall说了什么。
Noob Saibot 2014年

17
@AaronHall普通示例现在可用,使用multiprocessing.Manager而不是文件来实现共享状态。
user4815162342 2014年

48

del语句可能有用,但是IIRC 不能保证释放内存。该文档是在这里 ...和为什么它没有被释放是在这里

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

本文对Python垃圾收集器进行了说明,但我认为缺乏内存控制是托管内存的缺点


IronPython和Jython是否可以避免该问题?
EstebanKüber09年

@voyager:不,不会。真的,任何其他语言都不会。问题在于他将大量数据读入列表,并且数据对于内存而言太大。
Lennart Regebro 09年

1
在IronPython或Jython下可能会更糟。在这些环境中,即使没有其他东西保持引用,您甚至不能保证会释放内存。
杰森·贝克

@voyager,是的,因为Java虚拟机在全局范围内寻找可用内存。对于JVM,Jython没什么特别的。另一方面,JVM有其自身的缺点,例如,您必须事先声明它可以使用的堆大小。
Falken教授的合同违反了

32

Python是垃圾回收的,因此,如果减小列表的大小,它将回收内存。您还可以使用“ del”语句完全摆脱变量:

biglist = [blah,blah,blah]
#...
del biglist

18
这是事实,也不是事实。虽然减小列表的大小可以回收内存,但是不能保证何时发生。
2009年

3
不,但是通常会有所帮助。但是,据我所知,这里的问题是,如果他将所有对象读入一个列表中,那么他必须拥有太多的对象,以致于在处理所有对象之前内存不足。在处理完毕之前删除列表可能不是一个有用的解决方案。;)
Lennart Regebro 09年

3
内存不足/内存不足的情况是否会触发垃圾收集器的“紧急运行”?
杰里米·弗里斯纳

4
biglist = []会释放内存吗?
neouyghur

3
是的,如果旧列表没有其他引用。
Ned Batchelder

22

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

对于您的情况,当您需要大型列表时,通常需要重新组织代码,通常使用生成器/迭代器。这样,您根本就不需要在内存中存储大型列表。

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


1
如果这种方法可行,那么可能值得这样做。但应注意,您不能对迭代器进行随机访问,这可能会导致问题。
杰森·贝克

是的,如果有必要,那么随机访问大数据集可能需要某种数据库。
Lennart Regebro 09年

您可以轻松地使用迭代器来提取另一个迭代器的随机子集。
S.Lott

是的,但是然后您必须遍历所有内容以获得子集,这将非常慢。
Lennart Regebro 09年

21

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

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


嗯 CPython的垃圾回收基于refcounting。它不是定期的标记和清除(对于许多常见的JVM实现而言),而是在引用计数为零时立即删除某些内容。仅周期(refcounts为零,但不是由于引用树中的循环而导致周期)需要定期维护。del只是将所有不同的值重新分配给引用对象的所有名称都不会。
查尔斯·达菲

我知道您来自哪里:我将相应地更新答案。我了解CPython解释器实际上以某种中间方式工作:del从Python的角度释放内存,但通常从C运行时库或OS的角度释放内存。参考文献:stackoverflow.com/a/32167625/4297effbot.org/pyfaq/...
Eric O Lebigot

同意链接的内容,但是假设OP谈论的是它们是从同一个Python进程得到的错误,则将内存释放到本地进程堆和OS中似乎没有什么区别(因为释放到堆使得该空间可用于该Python进程中的新分配)。就因为这一点,del是出口,从-范围,重新分配等同样有效
查尔斯·达菲

11

从文件读取图形时,我遇到了类似的问题。该处理包括计算不适合内存的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

1
我想知道为什么您在Python中使用`//``s而不是#来进行注释。
罗卡蒙德

我在语言之间混在一起。谢谢您的评论,我更新了语法。
Retzod

9

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

实际上并没有直接告诉Python释放内存的方法。这件事的事实是,如果您想要较低的控制级别,则必须使用C或C ++编写扩展。

也就是说,有一些工具可以帮助您:


3
当我使用大量内存时,gc.collect()和del gc.garbage [:]可以正常工作
Andrew Scott Evans

3

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


1
我认为我只能将顶点保存在内存中,然后将三角形打印到文件中,然后仅在最后打印出顶点。但是,将三角形写入文件的行为会消耗大量的性能。有没有什么方法可以吗?
Nathan Fellman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.