为什么Python for循环中的临时变量计算会占用这么多内存? [复制]

2024-01-07

下面两段代码是等价的,但是第一个代码占用了大约700M内存,后一个代码只占用了大约100M内存(通过Windows任务管理器)。这里会发生什么?

def a():
    lst = []
    for i in range(10**7):
        t = "a"
        t = t * 2
        lst.append(t)
    return lst

_ = a()
def a():
    lst = []
    for i in range(10**7):
        t = "a" * 2
        lst.append(t)
    return lst

_ = a()

@vurmux 提出了不同内存使用的正确原因:字符串驻留,但似乎缺少一些重要的细节。

CPython 实现在编译期间实习一些字符串,例如"a"*2- 有关如何/为什么的更多信息"a"*2被实习看到这个SO-post https://stackoverflow.com/q/55643933/5769463.

澄清:正如 @MartijnPieters 在他的评论中正确指出的那样:重要的是编译器是否进行常量折叠(例如计算两个常量的乘法)"a"*2) 或不。如果完成常量折叠,则将使用生成的常量,并且列表中的所有元素将引用同一对象,否则不会。即使所有字符串常量都被实习(因此执行常量折叠 => 字符串实习) - 谈论实习仍然很草率:常量折叠是这里的关键,因为它也解释了根本没有实习的类型的行为,例如浮点数(如果我们使用t=42*2.0).

是否发生了常量折叠,可以很容易地验证dis-module(我称你的第二个版本a2()):

>>> import dis
>>> dis.dis(a2)
  ...
  4          18 LOAD_CONST               2 ('aa')
             20 STORE_FAST               2 (t)
  ...

正如我们所看到的,在运行时不执行乘法,而是直接加载乘法的结果(在编译器期间计算) - 结果列表包含对同一对象的引用(用18 LOAD_CONST 2):

>>> len({id(s) for s in a2()})
1

在那里,每个引用只需要 8 个字节,这意味着大约80Mb(+列表的过度分配+解释器所需的内存)所需的内存。

在Python3.7中,如果结果字符串超过4096个字符,则不会执行常量折叠,因此替换"a"*2 with "a"*4097导致以下字节码:

 >>> dis.dis(a1)
 ...
  4          18 LOAD_CONST               2 ('a')
             20 LOAD_CONST               3 (4097)
             22 BINARY_MULTIPLY
             24 STORE_FAST               2 (t)
 ...

现在,乘法不是预先计算的,结果字符串中的引用将是不同对象的。

优化器还不够聪明,无法识别出t实际上是"a" in t=t*2,否则它将能够执行常量折叠,但现在第一个版本的字节码(我称之为a2()):

... 5 22 LOAD_CONST 3 (2) 24 LOAD_FAST 2 (t) 26 二进制乘法 28 STORE_FAST 2 (t) ...

它会返回一个列表10^7里面有不同的对象(但所有对象都是相等的):

>>> len({id(s) for s in a1()})
10000000

即每个字符串需要大约 56 个字节(sys.getsizeof返回 51,但因为 pymalloc-内存分配器是 8 字节对齐的,所以会浪费 5 个字节)+每个引用 8 个字节(假设 64 位 CPython 版本),因此大约610Mb(+列表的过度分配+解释器所需的内存)。


您可以通过以下方式强制字符串的驻留sys.intern https://docs.python.org/3/library/sys.html#sys.intern:

import sys
def a1_interned():
    lst = []
    for i in range(10**7):
        t = "a"
        t = t * 2
        # here ensure, that the string-object gets interned
        # returned value is the interned version
        t = sys.intern(t) 
        lst.append(t)
    return lst

实际上,我们现在不仅可以看到需要更少的内存,而且列表还引用了同一对象(在线查看稍小的大小(10^5) here https://ideone.com/0mqYyv):

>>> len({id(s) for s in a1_interned()})
1
>>> all((s=="aa" for s in a1_interned())
True

字符串驻留可以节省大量内存,但有时很难理解字符串是否/为什么被驻留。呼唤sys.intern明确消除了这种不确定性。


存在引用的附加临时对象t不是问题:CPython 使用引用计数进行内存管理,因此一旦没有对对象的引用,对象就会被删除 - 无需与垃圾收集器进行任何交互,垃圾收集器在 CPython 中仅用于分解循环(即与 Java 的 GC 不同,因为 Java 不使用引用计数)。因此,临时变量实际上是临时的 - 这些对象无法累积以对内存使用产生任何影响。

临时变量的问题t只是它阻止了编译过程中的窥视孔优化,这是为"a"*2但不是为了t*2.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么Python for循环中的临时变量计算会占用这么多内存? [复制] 的相关文章

随机推荐

  • Eclipse 的“Google Maps API v3 for GWT”项目示例

    Google 在此发布了 GWT 的官方地图 v3 APIhttps groups google com forum topic gwt google apis 6SO5kCDqb k https groups google com for
  • 识别最近的网格点

    我有三个数组 lat 15 15 25 15 75 16 30 long 91 91 25 91 75 92 102 data array 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99 9 99 9 99 9
  • 我的 pdf 文件是否采用 UTF-8 编码?

    我想知道 pdf 文件是否以 UTF 8 编码 如何检查pdf文件中使用了哪种字符编码 PDF 是二进制文件 而不是文本文件 像 UTF 8 这样的字符编码仅在文本文件 txt html xml csv 的上下文中才有意义 因此 PDF 绝
  • 为什么 MongoDB 配置服务器必须只有一个或三个?

    在阅读了 MongoDB 分片架构的官方文档后 我还没有找到为什么需要一到三个配置服务器 而不是其他数量 The 有关配置服务器的 MongoDB 文档 https docs mongodb org v3 0 core sharded cl
  • 栈帧和作用域之间有什么关系?

    最近我正在学习Python 中的范围界定 我了解什么是堆栈框架 但我对堆栈框架和作用域之间的关系和区别感到困惑 我通过 Python计算与编程简介 这本书来学习Python 它没有具体阐明这两个术语 范围只是 LEGB 之一 本地 封闭 全
  • PowerMockito 在尝试存根私有重载方法时抛出 NullPointerException

    我 仍在 尝试检查是否bar Alpha Baz called bar Xray Baz 使用 PowerMockito 如bar Xray Baz is private 考虑到我的 MCVE 课程 实际上没有调用后者Foo以下 我上过同一
  • ARM 汇编器中的寄存器操作数是如何编码的?

    我反编译了一些ARM ELF文件并阅读了汇编代码 但是 我不明白一些代码是如何翻译成助记符的 例如我得到这样的代码 hex code mnemonic binary 0xb480 push r7 1011 0100 1000 0000 0x
  • 有没有办法正确模拟重新选择选择器以进行单元测试?

    我的项目中有一个非常复杂的选择器结构 某些选择器可能最多有 5 层嵌套 因此其中一些很难通过传递输入状态进行测试 我想改为模拟输入选择器 然而我发现这实际上是不可能的 这是最简单的例子 selectors1 js export const
  • PHP 的 create_function() 与仅使用 eval()

    在 PHP 中 您有 create function 函数 它创建一个唯一的命名 lambda 函数 如下所示 myFunction create function foo return foo myFunction bar Returns
  • Dapper 批量插入返回序列 ID

    我正在尝试使用 Dapper 通过 Npgsql 执行批量插入 这会返回新插入行的 id 我的两个示例中都使用了以下插入语句 var query INSERT INTO MyTable Value VALUES Value RETURNIN
  • Java 数组索引越界异常

    当我需要将 5 个用户输入的值存储到一个数组中 将其发送到一个方法 并查找并显示最低值时 我一直在研究这个基本的 java 程序 该程序很简单 并且可以运行 但是当我输入最后一个数字时 出现错误 线程 main 中的异常 java lang
  • 调用 setCenter 后 OpenLayers,地图仍处于 0,0 位置

    我尝试通过 setCenter 方法设置地图中心 但仍然不起作用 地图不动 我尝试使用从投影到地图投影的变换 但没有成功 这是代码的一部分 谢谢
  • Spark Streaming中如何处理旧数据并删除处理后的数据

    我们正在运行一个 Spark 流作业 从目录中检索文件 使用 textFileStream 我们担心的一个问题是作业已停止但文件仍在添加到目录中的情况 一旦作业再次启动 这些文件就不会被拾取 因为它们在作业运行时不是新的或已更改 但我们希望
  • 如果中间缺少数字,则获取数字列的范围(最小/最大)

    如果数字在某处结束然后再次以更高的数字开始 我将如何查询数字列的范围 如果我有一个像这样的专栏 Number 1 2 3 4 5 11 12 13 我怎样才能返回这样的结果 Min Max 1 5 11 13 WITH CTE AS SEL
  • 使用“mailto:”方案通过意图发送电子邮件附件

    我正在使用此代码附加文件 final Intent emailIntent new Intent android content Intent ACTION SENDTO String uriText Uri file Uri fromFi
  • MVC 将方法添加到 jquery.validate.unobtrusive.js 中

    我最近有一个问题使复选框验证工作 https stackoverflow com questions 6923430 mvc unobtrusive validation on checkbox not working 6931490 69
  • 如何使用比较器定义自定义排序顺序?

    我想开发一个汽车列表排序演示 我正在使用数据表来显示汽车列表 现在实际上我想按汽车颜色对列表进行排序 这里不按字母顺序排序 我想使用我的自定义排序顺序 例如首先是红色汽车 然后是蓝色汽车 等等 为此我尝试使用JavaComparator h
  • 仅要求和测试命名参数

    我有一个旨在接受命名参数的脚本 我想在未命名或命名错误的参数的情况下提供一些错误检查 并且我看到了一些奇怪的情况 该脚本以参数块开头 如下所示 param string Alias s sets string Alias l locatio
  • C# 中的树形数据结构

    我正在寻找 C 中的树或图数据结构 但我想没有提供 使用 C 2 0 对数据结构进行广泛检查 http msdn microsoft com en us library ms379574 aspx关于为什么 是否有一个常用的方便的库来提供此
  • 为什么Python for循环中的临时变量计算会占用这么多内存? [复制]

    这个问题在这里已经有答案了 下面两段代码是等价的 但是第一个代码占用了大约700M内存 后一个代码只占用了大约100M内存 通过Windows任务管理器 这里会发生什么 def a lst for i in range 10 7 t a t