“del”到底是做啥的?

Posted

技术标签:

【中文标题】“del”到底是做啥的?【英文标题】:What does "del" do exactly?“del”到底是做什么的? 【发布时间】:2014-01-29 23:04:06 【问题描述】:

这是我的代码:

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。当我检查docs时,我发现了以下语句:

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

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

【问题讨论】:

del huge_listhuge_list = None [大致] 等价于讨论对象可达性。 您是否真的遇到过程序最终空间不足并引发MemoryError 的问题,或者您的计算机是否会陷入交换颠簸的地狱?如果没有可见的问题,实际上可能没有值得担心的问题。 @abarnert:是的,它只是为了“提高我对 python 的理解”的目的。 152.6 MIB 几乎正好是每个列表元素 8 个字节。似乎在理性的范围内。我更想知道是什么占用了其他 469 MiB。 余数是每个元素 24 字节加上一点 slop,而 24 字节恰好是 64 位 CPython 2.7 的默认构建中的 PyInt 标头的大小,所以……这是可能的PyInt 的大部分或全部内存都位于某个级别或另一个级别的空闲列表中,而 PyList 的内部存储缓冲区(价值 152MiB 的指向那些 PyInt 对象的指针)被回收,因为它是一个巨大的分配(甚至可能直接在单个 mmapVirtualAlloc 调用中分配)而不是一堆小东西。 【参考方案1】:

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

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

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

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


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

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

(最重要的是,我在上面所说的“操作系统”实际上是一个由多个级别组成的抽象,从malloc 库到核心 C 库到内核/分页器,至少有一个这些级别通常都有自己的空闲列表。)

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


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

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

【讨论】:

无法访问?参考?您总是可以尝试*通过 id 恢复流放的变量! ***.com/a/15012814/194586(*可能会或可能不会导致段错误) @NickT 这些变量并没有“死”..在任何情况下,“可能导致段错误”这一事实意味着对象可以被回收(并且因此,无法通过 GC 根访问;该帖子仅显示可以通过 不透明标识符 获取对象(如果它们仍然存在)。 @user2864740:没错。这被记录为不起作用;事实上,它有时会起作用,有时会发生段错误,有时会在半小时后导致段错误,因为你破坏了某些东西,有时只是默默地给你错误的值,只有当你真的扩展定义时才算作“工作”......跨度> @AshwiniChaudhary:在 2.x 中,除了 small bit that's relevant to writing C extensions,它仅记录在源代码中,主要在 obmalloc.c 中。 @abarnert:谢谢。 gc 模块确实有帮助。执行 gc.collect() 释放了剩余的内存:)

以上是关于“del”到底是做啥的?的主要内容,如果未能解决你的问题,请参考以下文章

FragmentManager 和 FragmentTransaction 到底是做啥的?

selenium 中的 ime() 到底是做啥的?

DrawShadow 中的 Elevation 到底是做啥的?

.join() 方法到底是做啥的?

.join() 方法到底是做啥的?

Perl 的“祝福”到底是做啥的?