解释器维护的整数缓存有什么用处?

2024-11-25 08:49:00
admin
原创
184
摘要:问题描述:深入研究 Python 的源代码后,我发现它维护着一个PyInt_Object从int(-5)到int(256)(@src/Objects/intobject.c)的数组一个小实验证明了这一点:>>> a = 1 >>> b = 1 >>> a ...

问题描述:

深入研究 Python 的源代码后,我发现它维护着一个PyInt_Objectint(-5)int(256)(@src/Objects/intobject.c)的数组

一个小实验证明了这一点:

>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False

但是如果我在 py 文件中一起运行这些代码(或用分号连接它们),结果会有所不同:

>>> a = 257; b = 257; a is b
True

我很好奇为什么它们仍然是同一个对象,所以我深入研究了语法树和编译器,我想出了下面列出的调用层次结构:

PyRun_FileExFlags() 
    mod = PyParser_ASTFromFile() 
        node *n = PyParser_ParseFileFlagsEx() //source to cst
            parsetoke() 
                ps = PyParser_New() 
                for (;;)
                    PyTokenizer_Get() 
                    PyParser_AddToken(ps, ...)
        mod = PyAST_FromNode(n, ...)  //cst to ast
    run_mod(mod, ...)
        co = PyAST_Compile(mod, ...) //ast to CFG
            PyFuture_FromAST()
            PySymtable_Build()
            co = compiler_mod()
        PyEval_EvalCode(co, ...)
            PyEval_EvalCodeEx()

PyInt_FromLong然后我在之前/之后添加了一些调试代码PyAST_FromNode,并执行了一个 test.py:

a = 257
b = 257
print "id(a) = %d, id(b) = %d" % (id(a), id(b))

输出如下:

DEBUG: before PyAST_FromNode
name = a
ival = 257, id = 176046536
name = b
ival = 257, id = 176046752
name = a
name = b
DEBUG: after PyAST_FromNode
run_mod
PyAST_Compile ok
id(a) = 176046536, id(b) = 176046536
Eval ok

这意味着在进行转换期间cst,会创建ast两个不同的PyInt_Objects(实际上是在ast_for_atom()函数中执行的),但随后它们会合并。

我发现很难理解PyAST_Compile和中的源代码PyEval_EvalCode,所以我来这里寻求帮助,如果有人能给出提示,我将不胜感激?


解决方案 1:

Python 缓存范围内的整数[-5, 256],因此该范围内的整数通常但并非总是相同。

对于 257,您看到的是 Python 编译器在同一个代码对象中编译时对相同的文字进行优化。

在 Python shell 中输入时,每一行都是完全不同的语句,分别进行解析和编译,因此:

>>> a = 257
>>> b = 257
>>> a is b
False

但如果将相同的代码放入文件中:

$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True

每当编译器有机会一起分析文字时就会发生这种情况,例如在交互式解释器中定义函数时:

>>> def test():
...     a = 257
...     b = 257
...     print a is b
... 
>>> dis.dis(test)
  2           0 LOAD_CONST               1 (257)
              3 STORE_FAST               0 (a)

  3           6 LOAD_CONST               1 (257)
              9 STORE_FAST               1 (b)

  4          12 LOAD_FAST                0 (a)
             15 LOAD_FAST                1 (b)
             18 COMPARE_OP               8 (is)
             21 PRINT_ITEM          
             22 PRINT_NEWLINE       
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        
>>> test()
True
>>> test.func_code.co_consts
(None, 257)

请注意编译后的代码如何包含一个常量257

总而言之,Python 字节码编译器无法执行大量优化(如静态类型语言),但它能做的比你想象的要多。其中之一就是分析文字的使用情况并避免重复。

请注意,这与缓存无关,因为它也适用于没有缓存的浮点数:

>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True

对于更复杂的文字,比如元组,它“不起作用”:

>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False

但元组内的文字是共享的:

>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True

(请注意,即使在错误修复版本之间,常量折叠和窥孔优化器也会改变行为,因此返回哪些示例TrueFalse基本上是任意的,并且将来会发生变化)。


PyInt_Object至于为什么你看到创建了两个,我这样做是为了避免文字比较。例如,数字257可以用多个文字来表示:

>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257

解析器有两个选择:

  • 在创建整数之前将文字转换为某些公共基数,并查看文字是否等效。然后创建一个整数对象。

  • 创建整数对象并查看它们是否相等。如果相等,则仅保留一个值并将其分配给所有文字,否则,您已经有要分配的整数。

Python 解析器可能使用第二种方法,这种方法避免了重写转换代码,也更容易扩展(例如它也适用于浮点数)。


读取Python/ast.c文件,解析所有数字的函数是parsenumber,它调用PyOS_strtoul以获取整数值(对于整数)并最终调用PyLong_FromString

    x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
    if (x < 0 && errno == 0) {
        return PyLong_FromString((char *)s,
                                 (char **)0,
                                 0);
    }

正如您在这里看到的,解析器不会检查它是否已经找到具有给定值的整数,所以这解释了为什么您看到创建了两个 int 对象,这也意味着我的猜测是正确的:解析器首先创建常量,然后才优化字节码以对相等的常量使用相同的对象。

执行此检查的代码必须位于Python/compile.c或 中的某个位置Python/peephole.c,因为这些是将 AST 转换为字节码的文件。

具体来说,该compiler_add_o函数似乎就是执行此操作的函数。 中有这样的注释compiler_lambda

/* Make None the first constant, so the lambda can't have a
   docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
    return 0;

因此,它似乎compiler_add_o用于为函数/lambda 等插入常量。该compiler_add_o函数将常量存储到一个dict对象中,由此立即可以得出,相等的常量将落入同一个位置,从而导致最终字节码中只有一个常量。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3838  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2707  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   29  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   23  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   26  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用