我怎样才能在Python中明确释放内存?

Nat*_*man 338 python memory memory-management

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

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

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

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

Hav*_*ard 379

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

import gc
gc.collect()
Run Code Online (Sandbox Code Playgroud)

  • 实际上,在循环结束时自己调用`gc.collect()`有助于避免内存碎片,从而有助于提高性能.我已经看到这有很大的不同(约20%运行时IIRC) (146认同)
  • 更好的方法是创建更小的函数,这样每个变量在创建之间的生命周期都会缩短,并且在函数出口处删除命名空间时会被取消引用. (31认同)
  • 我正在使用python 3.6.从hdf5(500k行)加载pandas数据帧后调用`gc.collect()`可将内存使用量从1.7GB减少到500MB (26认同)
  • 通常,应避免使用gc.collect().垃圾收集器知道如何完成它的工作.也就是说,如果OP处于他突然释放对象(如数百万)的情况下,gc.collect可能会证明是有用的. (20认同)
  • 无论如何,事情都经常被垃圾收集,除非在一些不寻常的情况下,所以我认为这不会有太大帮助. (14认同)
  • 也许不够频繁.他可以用毫米数创造这些"百万件物品". (8认同)
  • 我需要在具有32GB内存的系统中加载和处理几个25GB的numpy数组.在处理数组之后使用`del my_array`后跟`gc.collect()`是实际释放内存的唯一方法,我的进程可以继续加载下一个数组. (7认同)
  • 是的,但是每创建一个对象时,垃圾收集器就会运行一次,因此在这几毫秒内它会运行数千次. (2认同)
  • @David,只要`my_array`超出范围,或者将其他值或对象分配给“范围内”,调用`gc.collect()`而没有`del`的行为完全相同。事先命名。而且,如果您将代码更改为不再具有引用循环,则即使没有`gc.collect()`,也将立即收回内存(在CPython中)。 (2认同)

Ale*_*lli 106

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

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

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

  • 我当然希望看到一个简单的例子. (29认同)
  • @AaronHall Trivial示例[现在可用](http://stackoverflow.com/a/24126616/1600898),使用`multiprocessing.Manager`而不是文件来实现共享状态. (17认同)
  • 认真.@AaronHall说的是什么. (3认同)

Aid*_*ell 42

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

我听说有人在Linux和Unix类型的系统上分支python进程做一些工作,得到结果然后杀死它.

本文介绍了Python垃圾收集器,但我认为内存控制不足是内存管理的缺点


Ned*_*der 29

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

biglist = [blah,blah,blah]
#...
del biglist
Run Code Online (Sandbox Code Playgroud)

  • 这是事实并非如此.虽然减小列表的大小允许回收内存,但不能保证何时会发生这种情况. (13认同)
  • biglist = [ ] 会释放内存吗? (5认同)
  • 是的,如果旧列表没有被其他任何东西引用。 (4认同)
  • 不,但通常会有所帮助.但是,正如我在这里理解的那样,问题是他必须拥有如此多的对象,以便在处理所有内容之前耗尽内存,如果他将它们读入列表中.在完成处理之前删除列表不太可能是有用的解决方案.;) (3认同)
  • 另请注意,del不保证将删除对象.如果对该对象有其他引用,则不会释放它. (2认同)
  • 低内存/内存不足的情况不会触发垃圾收集器的"紧急运行"吗? (2认同)

Len*_*bro 22

你无法明确释放内存.您需要做的是确保不保留对象的引用.然后他们将被垃圾收集,释放内存.

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

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

  • 如果这种方法可行,那么它可能值得做。但是需要注意的是,不能对迭代器进行随机访问,这可能会导致问题。 (2认同)
  • 确实如此,如果有必要,那么随机访问大型数据集可能需要某种数据库。 (2认同)

Eri*_*got 19

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

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


Jor*_*ril 13

正如其他答案已经说过的那样,即使 Python 代码不再使用内存(因此gc.collect()不会释放任何内容),Python 也可以避免向操作系统释放内存,尤其是在长时间运行的程序中。malloc_trim无论如何,如果您使用的是 Linux,您可以尝试通过直接调用 libc 函数(手册页)来释放内存。就像是:

import ctypes
libc = ctypes.CDLL("libc.so.6")
libc.malloc_trim(0)
Run Code Online (Sandbox Code Playgroud)


Ret*_*zod 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
Run Code Online (Sandbox Code Playgroud)

  • 我想知道为什么在 Python 中使用 `//`` 而不是 # 作为注释。 (2认同)
  • 我混淆了不同的语言。谢谢您的评论,我更新了语法。 (2认同)

Jas*_*ker 9

其他人已经发布了一些方法,您可以"哄骗"Python解释器释放内存(或以其他方式避免出现内存问题).你有可能首先尝试他们的想法.但是,我觉得重要的是直接回答你的问题.

没有任何方法可以直接告诉Python释放内存.事实上,如果你想要低级别的控制,你将不得不用C或C++编写扩展.

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

  • 当我使用大量内存时,gc.collect()和del gc.garbage [:]可以正常工作 (2认同)