26.字节码优化

字节码优化是代码对象准备好执行之前的最后清理和细化阶段。

早期的编译器阶段决定程序的含义并发出指令。优化试图使这些指令更小、更简单或更快,同时保留 Python 语义。

例如:python id="u88pli" x = 1 + 2 可以像编写一样编译:```python id="k8v2ef" x = 3


但这:```python id="lbwhpl"
x = a + b
```无法折叠,因为`a``b`是运行时值。它们的类型可以定义任意行为`+`

## 26.1 在编译管道中的位置

优化发生在字节码生成之后或期间,具体取决于优化的类型。```text id="olp5um"
source
    
tokenization
    
parsing
    
AST
    
symbol table
    
instruction generation
    
optimization
    
assembly
    
code object
```一些优化发生在 AST 上。有些发生在控制流图或指令流上。有些是在运行时通过专业化稍后发生的。

不要将“优化”视为一次完成。 CPython 在不同层使用多种机制。

## 26.2 优化必须保留语义

Python 是高度动态的。

这限制了 CPython 可以安全优化的内容。

例子:```python id="wozly6"
x = 1 + 2
```可以安全折叠。

例子:```python id="i4bt05"
x = "a" + "b"
```可以安全折叠。

例子:```python id="ue1h3y"
x = a + b
```折叠不安全。

该操作可能会调用:```python id="fk1geq"
a.__add__(b)
```或者:```python id="h80anr"
b.__radd__(a)
```这些方法可以运行任意 Python 代码。

优化规则:```text id="o2q4tx"
Only remove or precompute work when the observable behavior remains the same.
```可观察到的行为包括:```text id="g5n65q"
return values
exceptions
side effects
evaluation order
name lookup behavior
attribute lookup behavior
object identity when relevant
traceback positions
debugging behavior where specified
```## 26.3 常量折叠

常量折叠预先计算仅由常量组成的表达式。

示例:```python id="wpyjbl"
1 + 2
2 * 3
"py" + "thon"
(1, 2) + (3, 4)
```这些可以成为常数。

例子:```python id="8yvbyh"
def f():
    return 1 + 2
```可能的字节码:```text id="gjktcx"
LOAD_CONST 3
RETURN_VALUE
```而不是:```text id="rs6njs"
LOAD_CONST 1
LOAD_CONST 2
BINARY_OP +
RETURN_VALUE
```编译器将折叠值存储在`co_consts`

## 26.4 保守折叠

CPython 避免在编译时折叠可能太大或太昂贵的表达式。

例子:```python id="lwt9ja"
x = "a" * 10
```可以折叠。

但大量的重复不应在编译期间产生巨大的常数。```python id="k9mjrp"
x = "a" * 10_000_000_000
```盲目折叠它的编译器会在程序开始运行之前消耗大量内存。

优化必须是有界的。

CPython 的优化器很实用。它避免了将编译变成意外的资源攻击。

## 26.5 元组和容器折叠

仅包含常量的元组可以折叠。

例子:```python id="i0us1v"
x = (1, 2, 3)
```可以加载单个元组常量。

但列表和集合是可变的,因此它们通常不能成为常量。```python id="g3apua"
x = [1, 2, 3]
```必须在运行时创建一个新列表。

为什么?

每次执行都需要一个新的可变对象。

每次调用此函数必须返回不同的列表对象:```python id="ku6rt2"
def f():
    return [1, 2, 3]
```如果编译器存储一个共享列表常量,则突变会在调用之间泄漏。

## 26.6 会员测试优化

对于成员资格测试,可变文字有时可以替换为不可变常量。

例子:```python id="yku3da"
def is_small(x):
    return x in {1, 2, 3}
```集合文字仅用于成员资格测试。 CPython 可以将其替换为`frozenset`持续的。

从概念上讲:```text id="nibldo"
LOAD_FAST x
LOAD_CONST frozenset({1, 2, 3})
CONTAINS_OP
RETURN_VALUE
```这可以避免每次函数运行时重建集合。

该优化是有效的,因为设置对象本身不会暴露给用户代码。

## 26.7 无条件退出后的死代码

无条件退出后,某些代码将变得无法访问。

例子:```python id="oss0kd"
def f():
    return 1
    x = 2
```分配无法执行。

编译器可能会删除或跳过无法到达的指令序列。

无条件退出包括:```text id="u7yxa5"
return
raise
break
continue
```在正确的控制流上下文中。

编译器仍必须在需要时保留行信息和诊断。如果优化以可见的方式改变了调试或语法相关的行为,那么它就不能随意删除所有内容。

## 26.8 跳转优化

控制流经常会产生跳转。

形状示例:```text id="941xx7"
JUMP_FORWARD label_a

label_a:
JUMP_FORWARD label_b

label_b:
...
```优化器可以将第一个跳转直接重定向到`label_b````text id="np02i8"
JUMP_FORWARD label_b

label_b:
...
```这减少了指令数量并避免了不必要的调度。

跳转优化通常是安全的,因为它保留相同的控制流目的地。

## 26.9 基本块清理

编译器在内部构建或使用基本块。

基本块是具有一个入口和一个出口的直线指令序列。

优化可以删除空块、合并相邻块或简化边缘。

例子:```text id="o49sgv"
block A:
    jump block B

block B:
    jump block C

block C:
    useful work
```可以变成:```text id="ke9fl3"
block A:
    jump block C

block C:
    useful work
```这种清理发生在最终组装之前,此时标签仍然具有象征意义。

## 26.10 堆栈效应保存

每个优化都必须保持堆栈的正确性。

例子:```text id="3fooh0"
LOAD_CONST 1
POP_TOP
```如果加载的常量没有副作用并且其值被丢弃,则可以删除该对。

但这:```text id="s5gd6m"
LOAD_NAME x
POP_TOP
```一般无法去除。

加载中`x`可以提高`NameError`

同样地:```text id="hng0do"
LOAD_ATTR attr
POP_TOP
```可以调用动态属性查找并引发异常。

优化器必须知道哪些操作可以安全删除。

## 26.11 评估顺序约束

Python 的求值顺序是可以观察到的。

例子:```python id="bp4w5f"
f() + g()

f()必须先运行g()

即使结果f()在某些转换后的形式中似乎未使用,该调用不能被跳过或重新排序。

例子:```python id="7e0ozh" items[key()] = value()


优化不能将 Python 表达式视为纯数学表达式。

## 26.12 名称查找约束

名称查找是动态的。

例子:```python id="pipmww"
len([1, 2, 3])
```CPython 通常不能将其替换为`3`

为什么?

在模块级别,`len`可能会反弹:```python id="sksxhh"
len = lambda x: 999
```在函数内部,`len`可以在内置函数之前通过全局变量来解析。

编译器不假设内置`len`是不变的。

这是 Python 优化和静态链接语言优化之间的一个关键区别。

## 26.13 属性查找约束

属性查找是动态的。

例子:```python id="o09etj"
obj.x
```这可以调用:```text id="jvlw4h"
__getattribute__
__getattr__
descriptors
properties
module-level __getattr__
custom metaclass logic
```编译器无法用缓存值替换重复的属性访问,除非它可以证明行为未更改。

运行时专门化可以推测性地优化常见情况,但这受到运行时检查的保护。编译时优化仍然保守。

## 26.14`assert`优化`assert`语句受优化模式影响。

例子:```python id="ixz6ew"
assert x > 0, "x must be positive"
```通常编译大致如下:```text id="96ihbr"
if not (x > 0):
    raise AssertionError("x must be positive")
```Python在启用优化的情况下运行时,例如`python -O`assert 语句被删除。

这有意地改变了行为。它是 Python 记录的优化模式的一部分。

实用规则:```text id="w3wpdm"
Do not use assert for required runtime validation.
```对必须始终运行的检查使用显式异常。

## 26.15`__debug__`特殊常数`__debug__`与优化模式相关。

通常情况下:```python id="erm4nr"
__debug__ == True
```在下面`python -O`:

```python id="my7j8j"
__debug__ == False
```编译器可以根据以下情况优化分支`__debug__`

例子:```python id="vd8gwm"
if __debug__:
    check()
```在优化模式下,该分支可以省略。`__debug__`很特别。普通名称不会受到这种待遇。

## 26.16 文档字符串处理

优化级别会影响文档字符串。

例子:```python id="prb94t"
def f():
    """Function docs."""
    return 1
```通常,函数体中的第一个字符串文字成为函数文档字符串。

具有更高的优化,例如`python -OO`,文档字符串可能会被删除。

这会影响:```python id="abqsw1"
f.__doc__
```编译器和运行时会特别对待文档字符串,因为它们作为对象元数据存储,而不是像普通表达式语句一样执行。

## 26.17 窥孔式优化

较早的 CPython 描述经常提到窥孔优化器。

窥孔优化器查看一小部分指令并替换低效模式。

想法示例:```text id="gqvx1f"
LOAD_CONST 1
LOAD_CONST 2
BUILD_TUPLE 2
```可以变成:```text id="ipc5mv"
LOAD_CONST (1, 2)
```现代 CPython 已将大量优化转移到 AST 和控制流/指令阶段,而不是仅仅依赖于简单的窥视孔传递。

这个概念仍然有用:一些优化是对小指令模式的本地重写。

## 26.18 AST 级优化

在字节码存在之前,某些优化会更容易。

例子:```python id="qsilq6"
x = 1 + 2
```AST 包含:```text id="nvue0h"
BinOp(Constant(1), Add, Constant(2))
```AST 优化器可以将该节点替换为:```text id="iz4wys"
Constant(3)
```然后字节码生成只需加载`3`

AST 级优化看到了高级结构,这比从字节码重建含义更容易。

## 26.19 指令级优化

指令生成后,其他优化会更容易。

示例:```text id="qz307a"
remove unreachable blocks
redirect jumps
merge blocks
compute final stack sizes
shorten control flow
```指令级优化可以看到实际的控制流和堆栈效果。

它更接近最终的字节码布局。

## 26.20 运行时专业化是独立的

现代 CPython 也有运行时专门化。

这与编译时优化不同。

编译器生成通用字节码。在执行过程中,CPython 可能会专门化经常使用的指令。

操作示例:```text id="4c4nxd"
LOAD_ATTR
LOAD_GLOBAL
BINARY_OP
CALL
STORE_ATTR
``` CPython 观察到稳定的运行时行为后,通用属性访问可能会成为更快的专用路径。

编译时优化:```text id="p7xt6t"
happens before execution
must be safe without runtime knowledge
```运行时专业化:```text id="qo5hr8"
happens during execution
uses observed runtime types and cache guards
can fall back if assumptions fail
```这种分离允许 CPython 优化动态代码,而无需做出不安全的编译时假设。

## 26.21 内联缓存

运行时专门化在字节码指令附近使用内联缓存。

从概念上讲:```text id="yc3mav"
LOAD_ATTR name
CACHE
CACHE
```缓存存储运行时信息,例如:```text id="ymdvl9"
observed type
dictionary version
descriptor information
resolved offset
specialized handler state
```编译器为缓存准备空间。解释器稍后会填充并使用它们。

这就是为什么反汇编可能会在请求时显示缓存条目。

## 26.22 优化和回溯

优化必须保留有用的源位置。

例子:```python id="ett31m"
x = 1 / 0
```即使优化了周围的代码,`ZeroDivisionError`应该指向一个有意义的源位置。

回溯依赖于字节码到源的映射。

优化器必须更新或保留:```text id="wtwgmh"
line information
column information
exception table ranges
instruction offsets
```损坏的元数据会产生不良的调试行为。

## 26.23 优化和异常

有些表达式看起来很简单,但可能会引发异常。

例子:```python id="k3r2bn"
1 / 0
```在正常编译期间,编译器不应将其折叠到编译时异常中。

异常必须在代码执行时发生,而不是在编译时发生。

例子:```python id="9o0rsd"
def f():
    return 1 / 0
```定义`f`应该成功。呼唤`f()`应该提高`ZeroDivisionError`

因此,常量折叠必须避免将异常从运行时移动到编译时的转换。

## 26.24 优化和对象标识

可以通过以下方式观察对象身份`is`

例子:```python id="q46djy"
x is y
```编译器必须避免改变身份敏感行为的转换。

对于不可变常量,CPython 可以重用对象、内部字符串或以实现行为允许的方式折叠常量。但代码不应依赖于所有恒定的身份详细信息。

例子:```python id="7izgy0"
(1, 2) is (1, 2)
```结果可能取决于编译和实现选择。

优化必须保留语言语义,而常量的某些标识行为仍然特定于实现。

## 26.25 优化和可变默认值

函数默认值在函数定义时评估。

例子:```python id="er8lwd"
def f(xs=[]):
    xs.append(1)
    return xs
```列表默认是存储在函数对象上的运行时对象。

编译器无法在每次调用时用新列表替换它,因为这会改变语义。

这不是优化错误。这是Python的函数定义行为。

优化必须尊重对象的创建时间。

## 26.26 优化条件常数

恒定条件可以简化控制流程。

例子:```python id="epgm4i"
if False:
    x = 1
else:
    x = 2
```编译器可能会忽略不可达分支。

相似地:```python id="c85x72"
while False:
    work()
```身体无法执行。

但优化器必须小心源元数据以及影响范围或语法的构造。

例子:```python id="xt3ful"
if False:
    import never_loaded
```导入不会执行,但无法访问的块中存在的赋值仍然会影响局部变量分类,因为符号表分析发生在优化之前。

## 26.27 优化前确定范围

在优化删除无法访问的代码之前,符号表分析可以看到整个 AST

例子:```python id="udvaey"
x = 1

def f():
    if False:
        x = 2
    return x
```这还是可以让`x`本地到`f`,导致未绑定的本地行为`return x`执行。

优化器不能简单地删除分配然后重新分类`x`为全局的,除非整个编译模型允许这样的语义更改。

范围分类是 Python 语义的一部分,发生在许多优化之前。

## 26.28 优化级别

CPython 支持优化级别。

常见模式:

|模式|意义|
| ------ | ---------------------------------------------------------------- |
|正常 |默认编译 |
|`-O`|删除断言语句,设置`__debug__``False` |
| `-OO`|同时删除文档字符串 |

Python级别:```python id="nqc269"
import sys

print(sys.flags.optimize)
```编译 API 还可以接收优化设置。

例子:```python id="ea0hid"
compile("assert False", "<input>", "exec", optimize=1)
```优化级别是编译行为的一部分。

## 26.29 编译时优化的限制

CPython 有意避免激进的编译时优化。

它通常不执行:```text id="g0a8os"
function inlining
global constant propagation
type-specialized compilation
loop unrolling
escape analysis
whole-program optimization
automatic vectorization
```理由:```text id="kgi5lw"
dynamic name lookup
dynamic attribute access
monkey patching
import-time side effects
debuggability
compile-time cost
implementation simplicity
semantic risk
```CPython 相反依赖于:```text id="nam80y"
simple safe compile-time optimizations
specialized C implementations of built-in types
runtime adaptive specialization
extension modules
external JIT implementations in other runtimes
```## 26.30 重要的 CPython 源代码区域

重要的源文件包括:```text id="9n21ql"
Python/ast_opt.c
Python/compile.c
Python/flowgraph.c
Python/assemble.c
Python/bytecodes.c
Lib/dis.py
Lib/test/test_peepholer.py
Lib/test/test_compile.py
```概念角色:

|面积 |角色 |
| ------------- | -------------------------------------- |
|`ast_opt.c`| AST级优化|
|`compile.c`| AST 到指令的生成 |
|`flowgraph.c`|控制流图工作 |
|`assemble.c`|最终代码汇编 |
|`bytecodes.c`|操作码定义和运行时支持 |
|`dis.py`|字节码检查 |
|测试|保护优化器行为 |

## 26.31 最小心智模型

使用这个模型:```text id="dz2gpc"
CPython optimization is conservative.
It folds safe constants.
It simplifies some containers and membership tests.
It removes or redirects some unreachable or redundant control flow.
It respects evaluation order, exceptions, dynamic lookup, scope rules, and source positions.
Compile-time optimization differs from runtime specialization.
Runtime specialization can use observed types and guarded inline caches.
```字节码优化改进了生成的代码,同时保留了 Python 的动态语义。