【问题标题】:Python: is the garbage collector run before a MemoryError is raised?Python:垃圾收集器是否在引发 MemoryError 之前运行?
【发布时间】:2014-04-21 20:02:35
【问题描述】:

在一个 Python 代码中,该代码迭代了一系列涉及内存和 CPU 密集型数值计算的 30 个问题,我观察到 Python 进程的内存消耗随着 30 次迭代中的每一次迭代的开始而增长约 800MB,最后在第 8 次迭代中引发MemoryError(实际上系统内存已耗尽)。但是,如果我 import gc 并让 gc.collect() 在每次迭代后运行,则内存消耗保持在 ~2.5GB 不变,并且 Python 代码在解决所有 30 个问题后很好地终止。该代码只使用了连续2个问题的数据,没有引用循环(否则手动垃圾收集也无法降低内存消耗)。

问题

如果 Python 在引发 MemoryError 之前尝试运行垃圾收集器,此行为会引发问题。在我看来,这将是一件非常理智的事情,但也许有反对这样做的理由?

在此处进行了与上述类似的观察:https://stackoverflow.com/a/4319539/1219479

【问题讨论】:

标签: python memory numpy garbage-collection out-of-memory


【解决方案1】:

实际上,存在 个引用循环,这也是手动 gc.collect() 调用完全能够回收内存的唯一原因。

在 Python 中(我在这里假设 CPython),垃圾收集器的唯一目的是打破引用循环。当没有对象存在时,对象将被销毁,并在最后一次对它们的引用丢失时回收它们的内存。

至于垃圾收集器何时运行,完整文档在这里:http://docs.python.org/2/library/gc.html

它的 TLDR 是 Python 维护一个对象分配和释放的内部计数器。每当(allocations - deallocations) 达到 700(阈值 0)时,就会运行垃圾回收并重置两个计数器。

每次发生收集(自动或使用gc.collect() 手动运行)时,都会收集第 0 代(尚未在收集中幸存的所有对象)(即遍历没有可访问引用的对象,寻找引用循环——如果找到,循环被破坏,可能导致对象被破坏,因为没有剩下的引用)。在该集合之后剩余的所有对象都将移至第 1 代。

每 10 个集合(阈值 1),第 1 代也被收集,并且在第 1 代中存活的所有对象 被移动到第 2 代。第 1 代的每 10 个集合(即每个100 个集合——阈值 2),第 2 代也被收集。留在第 2 代中的幸存对象 - 没有第 3 代。

这 3 个阈值可以通过调用gc.set_threshold(threshold0, threshold1, threshold2) 由用户设置。

这对你的程序意味着什么:

  1. GC 不是 CPython 用来回收内存的机制(引用计数是)。 GC 会中断“死”对象中的引用循环,这可能会导致其中一些被销毁。
  2. 不,不能保证 GC 会在 MemoryError 引发之前运行。
  3. 您有参考周期。尝试摆脱它们。

【讨论】:

  • 是的,使用了 CPython。所以参考周期显然不是正确的术语。我的意思是i+1st 迭代中没有引用第一次i-1 迭代的数据。
  • 引用循环意味着,不知何故,对象 a 引用了对象 b,通过一些任意长的引用链,引用了对象 a。这意味着当您的 program 不再有权访问对象 a 时,它不会立即被释放。只有当 GC 运行时,它才会(可能,实际上)。如果ith 迭代的对象之间有引用循环,则在开始i+1st 迭代时它们不会被销毁。
  • 他的回答回答了你的问题。 CPython 仅每 700 次分配运行一次 GC。它不检查它是否可以执行分配,如果失败则像 Java 的 GC 系统那样运行分配。我想这是某种形式的深奥性能优化。
  • 感谢@max-noel 的解释,您的摘要中的第 2 号因此回答了我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-19
  • 2020-02-11
  • 2021-05-25
  • 2011-05-06
  • 1970-01-01
相关资源
最近更新 更多