菜单

字节码解释器与优化

相关源文件

本文档解释了 CPython 的分层字节码解释器和优化系统——Python 代码执行的核心。它涵盖了字节码执行流水线、专用字节码以及提高性能的优化机制。有关生成字节码的编译过程的信息,请参阅编译流水线

字节码执行流水线概述

Python 的执行模型通过多个阶段处理代码,字节码解释器是其核心。当 Python 代码执行时,它首先经过解析器和编译器,生成字节码指令。然后,这些字节码指令由复杂的、分层的解释器系统执行。

CPython 中的字节码解释器采用分层方法

  1. 层 1(基础解释器):通过优化的 switch-dispatch 机制处理所有字节码指令。
  2. 层 2(基于跟踪的优化器):使用复杂的微操作系统来优化频繁执行的代码路径(热路径)。

来源:Python/ceval.c1-506 Python/bytecodes.c1-66

层 1:基础字节码解释器

层 1 解释器是处理所有 Python 字节码指令的默认执行引擎。它使用基于 switch 的分派机制逐条执行字节码指令。

指令分派

层 1 解释器使用计算跳转分派机制(可用时)或 switch 语句处理字节码指令。每条指令的处理如下:

  1. 获取下一条字节码指令。
  2. 解码操作码和操作数。
  3. 分派到相应的指令处理程序。
  4. 执行指令。
  5. 移至下一条指令。

解释器使用 TARGET 宏框架为每条字节码指令定义处理程序。

来源:Python/generated_cases.c.h21-82 Python/ceval.c507-536 Python/ceval_macros.h1-46

字节码专用化

层 1 解释器中的一项关键优化技术是字节码专用化。解释器可以将通用字节码指令替换为针对特定数据类型或常见模式进行了优化的专用版本。

通用字节码专用变体
BINARY_OPBINARY_OP_ADD_INT, BINARY_OP_MULTIPLY_FLOAT 等。
LOAD_CONSTLOAD_CONST_IMMORTAL, LOAD_CONST_MORTAL
LOAD_ATTRLOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_SLOT 等。
STORE_ATTRSTORE_ATTR_INSTANCE_VALUE, STORE_ATTR_SLOT
FOR_ITERFOR_ITER_LIST, FOR_ITER_RANGE, FOR_ITER_TUPLE

专用化在执行期间动态发生。当通用字节码指令频繁以相同类型出现时,它会被替换为专用版本。

专用化机制使用附加在字节码指令上的内联缓存。这些缓存存储类型信息、计数器和其他元数据,帮助解释器做出专用化决策。

来源:Python/bytecodes.c584-597 Python/generated_cases.c.h142-260

层 2:基于跟踪的优化器

当层 1 解释器识别出频繁执行的代码路径(热路径)时,它会触发层 2 优化器。这个高级优化系统会创建高效的执行单元,称为“执行器”,可以显著提高性能。

微操作(UOps)

层 2 优化器使用微操作(UOps),这些操作比字节码更底层。UOps 提供了更精细化的粒度,并实现了更复杂的优化。

  1. 字节码指令被分解为 UOps 序列。
  2. UOps 代表可以分析和优化的原子操作。
  3. 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 优化器采用基于跟踪的优化。

  1. 跟踪收集:记录频繁一起执行的字节码指令序列。
  2. 数据流分析:分析值如何在跟踪的代码中流动。
  3. 类型推断:根据观察到的行为确定变量的类型。
  4. 优化机会:识别常量值、冗余操作和类型专用化机会。

分析阶段构建依赖图,并应用各种优化技术来提高性能。

来源:Python/optimizer.c487-530 Python/optimizer_analysis.c1-90

执行器对象

优化过程的结果是一个“执行器”对象,它是一个优化的代码单元,用于替换一系列字节码指令。

执行器被安装在代码对象中,并通过特殊的 ENTER_EXECUTOR 字节码指令激活。执行时,执行器直接运行其优化的 UOps,绕过常规的字节码解释过程。

来源:Python/optimizer.c77-99 Include/internal/pycore_optimizer.h16-22

动态失效

执行器会对它们优化的代码做出假设,例如变量的类型或函数属性的状态。当这些假设发生变化时,执行器必须被失效。

  1. 类型监视器会监视执行器所依赖的类型的变化。
  2. 字典监视器会跟踪全局字典和内建项的变化。
  3. 当依赖项发生变化时,所有受影响的执行器都会被失效。
  4. 失效的执行器会被替换为原始字节码执行。

这确保了正确性,同时在可能的情况下保持性能。

来源:Python/optimizer.c183-206 Python/optimizer_analysis.c56-83

优化技术

字节码解释器在两个层级中都采用了多种优化技术。

1. 类型专用化

类型专用化为特定数据类型优化操作。

这避免了执行期间昂贵的类型检查和分派。

来源:Python/bytecodes.c584-741

2. 常量折叠

常量折叠在优化时计算具有常量值的表达式。

优化器识别出所有操作数都是常量的表达式,并提前计算它们。

来源:Python/optimizer_bytecodes.c338-363

3. 保护消除

当优化器可以证明值的类型时,保护消除会移除冗余的类型检查。

# 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

4. 内联缓存

内联缓存存储属性查找、方法解析和其他操作的结果,以避免重复查找。

# 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)

这大大加快了属性访问和方法调用。

来源:Python/bytecodes.c292-333

与其他 CPython 组件的交互

字节码解释器与 CPython 运行时的其他几个核心组件进行交互。

全局解释器锁 (GIL)

解释器在执行字节码时获取和释放 GIL。在标准构建中,GIL 确保同一时间只有一个线程执行 Python 字节码。自由线程模式(禁用的 GIL)引入了额外的机制来处理线程安全。

来源:Python/ceval.c100-138

内存管理

解释器与 CPython 的内存管理系统交互,用于对象分配、垃圾回收和引用计数。

  1. 增加和减少对象的引用计数。
  2. 为新对象分配内存。
  3. 在需要时触发垃圾回收。

来源: Python/ceval.c67-99

帧和栈管理

解释器管理执行帧和值栈

  1. 帧创建和激活
  2. 栈操作(压栈、弹栈)
  3. 局部变量访问
  4. 异常处理

每个帧都有自己的值栈用于操作数和结果。

来源: Python/bytecodes.c266-291 Python/ceval.c141-189

结论

CPython 的字节码解释器是一个复杂的系统,它结合了可靠的基线解释器和先进的优化技术。这种两层方法允许 Python 代码立即开始执行,同时仍然可以从对频繁执行的代码路径的优化中获益。

专门的字节码、基于跟踪的优化和微操作系统协同工作,以提高性能,同时保持 Python 的灵活性和动态性。这种架构代表了即时执行(如传统解释器中)和优化性能(如即时编译器中)之间的平衡。

来源: Python/ceval.c1-60 Python/optimizer.c1-20