本文档解释了 CPython 的分层字节码解释器和优化系统——Python 代码执行的核心。它涵盖了字节码执行流水线、专用字节码以及提高性能的优化机制。有关生成字节码的编译过程的信息,请参阅编译流水线。
Python 的执行模型通过多个阶段处理代码,字节码解释器是其核心。当 Python 代码执行时,它首先经过解析器和编译器,生成字节码指令。然后,这些字节码指令由复杂的、分层的解释器系统执行。
CPython 中的字节码解释器采用分层方法
来源:Python/ceval.c1-506 Python/bytecodes.c1-66
层 1 解释器是处理所有 Python 字节码指令的默认执行引擎。它使用基于 switch 的分派机制逐条执行字节码指令。
层 1 解释器使用计算跳转分派机制(可用时)或 switch 语句处理字节码指令。每条指令的处理如下:
解释器使用 TARGET 宏框架为每条字节码指令定义处理程序。
来源:Python/generated_cases.c.h21-82 Python/ceval.c507-536 Python/ceval_macros.h1-46
层 1 解释器中的一项关键优化技术是字节码专用化。解释器可以将通用字节码指令替换为针对特定数据类型或常见模式进行了优化的专用版本。
| 通用字节码 | 专用变体 |
|---|---|
| BINARY_OP | BINARY_OP_ADD_INT, BINARY_OP_MULTIPLY_FLOAT 等。 |
| LOAD_CONST | LOAD_CONST_IMMORTAL, LOAD_CONST_MORTAL |
| LOAD_ATTR | LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_SLOT 等。 |
| STORE_ATTR | STORE_ATTR_INSTANCE_VALUE, STORE_ATTR_SLOT |
| FOR_ITER | FOR_ITER_LIST, FOR_ITER_RANGE, FOR_ITER_TUPLE |
专用化在执行期间动态发生。当通用字节码指令频繁以相同类型出现时,它会被替换为专用版本。
专用化机制使用附加在字节码指令上的内联缓存。这些缓存存储类型信息、计数器和其他元数据,帮助解释器做出专用化决策。
来源:Python/bytecodes.c584-597 Python/generated_cases.c.h142-260
当层 1 解释器识别出频繁执行的代码路径(热路径)时,它会触发层 2 优化器。这个高级优化系统会创建高效的执行单元,称为“执行器”,可以显著提高性能。
层 2 优化器使用微操作(UOps),这些操作比字节码更底层。UOps 提供了更精细化的粒度,并实现了更复杂的优化。
例如,BINARY_OP_ADD_INT 字节码可能被分解为 _GUARD_TOS_INT、_GUARD_NOS_INT 和 _BINARY_OP_ADD_INT 等 UOps。
来源:Python/optimizer.c202-262 Include/internal/pycore_uop_ids.h1-55
层 2 优化器采用基于跟踪的优化。
分析阶段构建依赖图,并应用各种优化技术来提高性能。
来源:Python/optimizer.c487-530 Python/optimizer_analysis.c1-90
优化过程的结果是一个“执行器”对象,它是一个优化的代码单元,用于替换一系列字节码指令。
执行器被安装在代码对象中,并通过特殊的 ENTER_EXECUTOR 字节码指令激活。执行时,执行器直接运行其优化的 UOps,绕过常规的字节码解释过程。
来源:Python/optimizer.c77-99 Include/internal/pycore_optimizer.h16-22
执行器会对它们优化的代码做出假设,例如变量的类型或函数属性的状态。当这些假设发生变化时,执行器必须被失效。
这确保了正确性,同时在可能的情况下保持性能。
来源:Python/optimizer.c183-206 Python/optimizer_analysis.c56-83
字节码解释器在两个层级中都采用了多种优化技术。
类型专用化为特定数据类型优化操作。
这避免了执行期间昂贵的类型检查和分派。
常量折叠在优化时计算具有常量值的表达式。
优化器识别出所有操作数都是常量的表达式,并提前计算它们。
来源:Python/optimizer_bytecodes.c338-363
当优化器可以证明值的类型时,保护消除会移除冗余的类型检查。
# Original UOps
_GUARD_TOS_INT
_GUARD_NOS_INT
_BINARY_OP_ADD_INT
# After guard elimination (if types are known)
_BINARY_OP_ADD_INT
这减少了重复类型检查的开销。
来源:Python/optimizer_bytecodes.c107-118 Python/executor_cases.c.h157-175
内联缓存存储属性查找、方法解析和其他操作的结果,以避免重复查找。
# First execution: Slow path, stores result in cache
LOAD_ATTR (resolves attribute and caches location)
# Subsequent executions: Fast path using cached information
LOAD_ATTR_INSTANCE_VALUE (uses cached offset)
这大大加快了属性访问和方法调用。
字节码解释器与 CPython 运行时的其他几个核心组件进行交互。
解释器在执行字节码时获取和释放 GIL。在标准构建中,GIL 确保同一时间只有一个线程执行 Python 字节码。自由线程模式(禁用的 GIL)引入了额外的机制来处理线程安全。
解释器与 CPython 的内存管理系统交互,用于对象分配、垃圾回收和引用计数。
解释器管理执行帧和值栈
每个帧都有自己的值栈用于操作数和结果。
来源: Python/bytecodes.c266-291 Python/ceval.c141-189
CPython 的字节码解释器是一个复杂的系统,它结合了可靠的基线解释器和先进的优化技术。这种两层方法允许 Python 代码立即开始执行,同时仍然可以从对频繁执行的代码路径的优化中获益。
专门的字节码、基于跟踪的优化和微操作系统协同工作,以提高性能,同时保持 Python 的灵活性和动态性。这种架构代表了即时执行(如传统解释器中)和优化性能(如即时编译器中)之间的平衡。