33

这是我的代码:

from memory_profiler import profile

@profile
def mess_with_memory():
    huge_list = range(20000000)
    del huge_list
    print "why this kolaveri di?"

当我从解释器运行它时,这就是输出:

Line # Mem 使用增量行内容

 3      7.0 MiB      0.0 MiB   @profile
 4                             def mess_with_memory():
 5                             
 6    628.5 MiB    621.5 MiB       huge_list = range(20000000)
 7    476.0 MiB   -152.6 MiB       del huge_list
 8    476.0 MiB      0.0 MiB       print "why this kolaveri di"

如果您注意到输出,创建巨大的列表消耗了 621.5 MB,而删除它只释放了 152.6 MB。当我检查文档时,我发现了以下语句:

the statement del x removes the binding of x from the namespace referenced by the local scope

所以我猜,它并没有删除对象本身,而是取消绑定它。但是,它释放了这么多空间(152.6 MB)在解除绑定时做了什么。有人可以痛苦地向我解释这里发生了什么吗?

4

1 回答 1

49

Python 是一种垃圾收集语言。如果某个值不再从您的代码中“可访问”,它最终将被删除。

del如您所见,该语句删除了变量的绑定。变量不是值,它们只是值的名称。

如果该变量是任何地方对该值的唯一引用,则该值最终将被删除。特别是在 CPython 中,垃圾收集器建立在引用计数之上。所以,“最终”意味着“立即”。*在其他实现中,通常是“很快”。

但是,如果存在对相同值的其他引用,则仅删除其中一个引用(无论是 by del xx = None、退出x存在的范围等)不会清除任何内容。**


这里还有一个问题。我不知道memory_profiler模块(大概是这个)实际测量的是什么,但描述(谈论使用psutil)听起来像是从“外部”测量你的内存使用情况。

当 Python 释放存储空间时,它并不总是——甚至通常——将其返回给操作系统。它在多个级别上保留“空闲列表”,因此它可以更快地重新使用内存,而不是必须一路回到操作系统来要求更多。在现代系统上,这几乎不是问题——如果你再次需要存储,你有它很好;如果你不这样做,它会在其他人需要它时立即被分页,并且永远不会被分页,所以几乎没有伤害。

(最重要的是,我在上面所说的“操作系统”实际上是一个由多个级别组成的抽象,从malloc库到核心 C 库到内核/寻呼机,并且这些级别中的至少一个通常具有它的自己的免费列表。)

如果您想从内部角度跟踪内存使用情况……嗯,这很难。多亏了新tracemalloc模块,它在 Python 3.4 中变得容易多了。有各种第三方模块(例如heapy/ guppyPymplermeliae)试图获取与早期版本相同的信息,但这很困难,因为从各种分配器获取信息并将该信息绑定到垃圾收集器非常困难在PEP 445之前很难。


* 在某些情况下,存在对该值引用……但仅来自其他本身无法访问的引用,可能在一个循环中。就垃圾收集器而言,这仍然算作“无法访问”,但就引用计数而言,这不算。因此,CPython 也有一个“循环检测器”,它每隔一段时间就会运行一次,并找到相互可到达但无法从其他任何人访问的值的循环并清理它们。

** 如果您在交互式控制台中进行测试,可能存在对您的值的隐藏引用,这些引用很难跟踪,因此您可能认为您已经摆脱了最后一个引用,而实际上您还没有。在脚本中,即使不容易,也应该总是可以解决问题。该模块可以提供帮助,调试器也可以提供帮助。但当然,它们都您提供了添加其他隐藏引用的新方法。gc

于 2014-01-10T20:03:43.867 回答