【问题标题】:Is it more memory-efficient to set variables to `None` in python?在 python 中将变量设置为“无”是否更节省内存?
【发布时间】:2014-09-25 19:49:50
【问题描述】:

这是一个简单的问题,但由于我没有找到任何答案,我认为答案是否定的。但是,为了确保,我问它:

在函数中处理完变量后,将变量设置为 None 是否会使 Python 代码更高效?

举个例子:

def foo(fname):
    temp_1, temp_2 = load_file_data(fname)

    # do some processing on temp_1, temp_2

    temp_1 = None
    temp_2 = None

    # continue with the rest of the function

如果我们在函数末尾执行此操作,答案是否会改变(因为我假设 python 本身会在此时执行此操作)?

【问题讨论】:

  • 你在函数中做了多少其他工作?您要删除的对象有多大?顺便说一句,你可以在这里使用del temp_1, temp_2
  • 但总的来说:听起来像是过早的优化。函数完成后,无论如何都会清除局部变量。
  • @SterlingArcher:非法的是None = something,而不是something = None
  • Martijn 的两个 cmets 都是正确的:del temp_1 几乎总是比 temp_1 = None 好(它表达了你正在尝试更好的东西),这很可能是一个过早的优化——另外,即使不是,您也可以通过将函数重构为单独的部分来做得更好,这样这些本地变量在范围内的时间就不会超过必要的时间……但是,是的,这会起作用。
  • @oxtay:那为什么不创建一个单独的函数来处理大数据呢?或者重构使用迭代器,而不是一开始就在内存中构建整个东西。

标签: python memory


【解决方案1】:

这取决于你所说的“更高效”是什么意思。

将变量设置为None,假设它们是对其值的唯一引用,将允许垃圾收集器收集它们。而在 CPython(其垃圾收集器使用 ref 计数)中,它甚至会立即这样做。

但另一方面,您也在向必须由解释器执行的函数添加更多字节码,这使得代码对象更难保存在缓存中,等等。

请记住,释放内存几乎从不意味着实际上将内存释放给操作系统。大多数 Python 实现都有多个级别的空闲列表,并且它通常位于类似 malloc 的顶部。因此,如果您要分配足够的额外内存来增加峰值内存大小,那么在空闲列表中有很多东西可能会阻止这种情况;如果您已经达到了顶峰,那么发布值不太可能产生任何影响。 (假设内存使用峰值对您的应用很重要——仅仅因为它是迄今为止最容易衡量的东西,并不意味着它与每个问题最相关。)

在几乎所有现实生活中的代码中,这两种方式都不太可能产生任何影响。如果是这样,您需要进行测试,并了解内存压力和缓存位置等因素如何影响您的应用程序。您可能正在使您的代码更好,您可能会使它变得更糟(至少假设某些特定的内存测量不是您唯一关心优化的事情),很可能您没有任何效果,只是让它更长,因此更少可读。这是“过早优化是万恶之源”这句格言的完美例证。


如果我们在函数末尾执行此操作,答案是否会改变(因为我假设 python 本身会在此时执行此操作)?

你说得对,Python 在函数返回时释放了局部变量。所以是的,在这种情况下,你仍然会得到几乎所有的否定,而几乎没有得到肯定,这可能会改变答案。


但是,除了所有这些注意事项之外,在某些情况下,这可能会有所改善。* 因此,如果您已经分析了您的应用并发现持有该内存的时间过长会导致真正的问题,那么一定要修复它!

不过,请注意,del temp_1 将产生您正在寻找的相同效果,而且您​​正在做什么以及为什么要更明确。而且在大多数情况下,最好将您的代码重构为更小的函数,这样temp_1 和朋友在您完成它们之后就自然而然地离开范围,而无需任何额外的工作。

* 例如,假设函数的其余部分只是前半部分的精确副本,具有三个新值。在空闲列表的顶部拥有一组完美的候选者可能比必须更深入地搜索空闲列表要好——而且绝对比必须分配更多内存并可能触发交换要好……

【讨论】:

  • “如果是这样,您需要进行测试,并了解内存压力和缓存位置等因素如何影响您的应用程序。” → 这是错误的。请向我展示一个示例(任何示例),其中应用程序的速度受到明显影响(超过 ~1%)。您唯一应该关心的是内存使用情况,这不需要任何特殊分析。我只能相信你想多了。
  • @Veedrac 很抱歉,但我不明白你为什么在评论中提到速度……引用没有提到速度。
  • @Veedrac:写def load_file_data(_): return 0, 0,然后运行上面的代码。在我的笔记本电脑上使用 64 位 CPython 3.4.1,%timeit 每个循环给我 339ns。注释掉 = None 行,现在它给了我 271ns。这是 20% 的改进。
  • @SethMMorton 如果不是为了速度,你为什么还要关心缓存位置?
  • @SethMMorton:不,他在这方面是对的。除非您在 32 位平台上或没有 VM,否则关心内存使用的主要原因是它会导致交换抖动、页表流失等问题,即速度缓慢。 任何人关心缓存位置的唯一原因是速度。
【解决方案2】:

我不同意它会更快,除非您遇到内存不足的情况。

在正常的应用程序中,只要函数中的变量离开作用域,它们就会被标记为不再使用、已释放或任何特定的 Python 解释器所做的事情。设置为 None 意味着 python 需要做更多的工作,因为这将允许你的变量指向的内存被释放,而不是变量本身。

此外,一般而言,python 使用引用计数,而不是垃圾收集,因此一旦引用计数降至零,对象就会被释放。

【讨论】:

  • -1。 OP 询问对内存的影响,而您的问题涉及执行速度。所以你还没有回答这个问题。此外,您所说的“python 使用引用计数,而不是垃圾收集”是一个没有区别的区别,因为引用计数是垃圾收集的一种策略。
  • “一般python使用引用计数,而不是垃圾收集”不正确; PyPy、Iron 和 Jython 不使用引用计数(除了可能作为 PyPy 的一些实验性替代收集器中的一个更大方案中的一个组件,也许?)。这也是一种误导,因为引用计数垃圾回收的一种形式。
  • CPython 使用引用计数,它是主要的 Python 解释器,因此是“一般”的声明。引用计数是广义 GC 的一种非常不同的形式,因为它严格控制释放事物的时间和地点。引用计数并不意味着 GC,尽管它可能是 GC 的一种形式。
  • 我不知道你认为“一般”是什么意思。这就像说,“一般来说,操作系统提供其 API 的 UTF-16 版本”只是因为 Windows 是最主要的操作系统。
  • 另外,引用计数确实意味着 GC。还有什么意思? (好吧,我想您可能正在计算对可能成为垃圾的对象以外的其他东西的引用,但是标记和清除收集器可以很容易地标记那些其他东西……)当然,引用计数是与其他形式非常不同的 GC 形式.而抄袭收藏家与非抄袭收藏家有很大不同。那又怎样?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-29
  • 2011-06-08
  • 1970-01-01
相关资源
最近更新 更多