当前位置: 首页 > 后端技术 > Python

Python优化机制:常量折叠

时间:2023-03-26 19:29:53 Python

英文:https://arpitbhayani.me/blogs...作者:arprit出于交流学习目的,基于CCBY-NC-SA4.0许可协议。为便于阅读,内容略有修改。每种编程语言都需要大量的编译器级优化才能表现良好并获得出色的性能。一个著名的优化技术是“常量折叠”(ConstantFolding):在编译过程中,编译器会尝试识别常量表达式,对它们进行求值,然后用求值结果替换该表达式,从而使运行时更加精简。在这篇文章中,我们深入了解什么是常量折叠,看看它是如何融入Python世界的,最后解密Python的源码(即CPython),分析Python是如何优雅地实现它的。常量折叠所谓常量折叠是指在编译时查找和计算常量表达式,而不是在运行时计算它们,这样运行时会更精简、更快。>>>day_sec=24*60*60当编译器遇到常量表达式时,如上所述,它将计算表达式并进行替换。通常,表达式被“抽象语法树”(AST)中的计算值代替,但这完全取决于语言实现。因此,上面的表达式可以等价执行为:>>>day_sec=86400Python中的常量折叠过程。当使用dis模块反汇编上述常量表达式时,我们得到以下字节码:>>>importdis>>>dis.dis("day_sec=24*60*60")0LOAD_CONST0(86400)2STORE_NAME0(day_sec)4LOAD_CONST1(None)6RETURN_VALUE从字节码可以看出它只有一个LOAD_CONST和一个计算值86400。这表明CPython解释器将常量表达式246060折叠并替换为计算值86400在解析和构建抽象语法树期间。常量折叠的范围Python会尝试折叠每一个常量表达式,但在某些情况下,即使表达式是常量,Python也不会折叠它。例如,Python不会折叠x=4**64,但会折叠x=2**64。除了算术表达式,Python还会对涉及字符串和元组的表达式进行折叠,其中对长度最大为4096的字符串常量表达式进行折叠。>>>a="-"*4096#folded>>>a="-"*4097#notfolded>>>a="--"*4096#notfolded常量折叠的内部细节现在,我们将转移关注内部实现细节,即CPython在何处以及如何实现常量折叠。所有AST优化(包括常量折叠)都可以在ast_opt.c文件中找到。基本的起始函数是astfold_expr,它折叠Python源代码中包含的所有表达式。此函数递归遍历AST并尝试折叠每个常量表达式,如以下代码片段所示:操作委托给特定的表达式折叠函数。特定于操作的fold函数计算表达式并返回计算出的常量,然后将其放入AST中。例如,每当astfold_expr遇到二元运算时,它都会调用fold_binop,它会递归地计算两个子操作数(表达式)。fold_binop函数返回计算出的常量值,如以下代码片段所示:fold_binop函数通过检查当前运算符的类型,然后调用其相应的处理程序来折叠二元运算。例如,如果当前操作是加法,它会在其左右操作数上调用PyNumber_Add以计算最终值。有多优雅?为了高效地折叠某些模式或类型的常量表达式,CPython不编写特殊逻辑,而是调用相同的泛型代码。例如,折叠时,它会调用通用PyNumber_Add函数,就像它进行常规加法一样。因此,CPython消除了编写特殊函数来处理常量折叠的需要,方法是确保其通用代码/求值过程可以处理常量表达式的求值。参考资料常量折叠(https://en.wikipedia.org/wiki...CPython优化(https://stummjr.org/post/cpyt...Pythondis模块和常量折叠(https://yasoob.me/2019/02/26/...CPython的常量折叠简单方法(https://utcc.utoronto.ca/~cks...AST的常量折叠优化过程(https://bugs.python.org/issue...