2026 年 3 月,Python 3.15 Alpha 版 JIT 编译器提前数月达成性能目标。在 macOS AArch64 平台上,JIT 编译后的代码比尾调用解释器快 11%–12%;在 x86-64 Linux 上则比标准解释器快 5%–6%。这一成绩来之不易 —— 就在一年前,CPython JIT 还因为性能不升反降而被社区质疑。项目负责人 Ken Jin 在博客中坦言「曾严重怀疑 JIT 项目能否产生有意义的加速」,本文深入分析其工程决策与可落地的性能改进参数。
性能基线与目标设定
在评估 JIT 改进效果之前,需要明确基准线的测量方式。CPython 团队使用 pyperformance 作为标准基准套件,报告的几何平均值是核心指标,但实际工作负载的表现差异极大:某些基准测试可能出现约 20% 的性能下降,而另一些则可以获得超过 100% 的加速。这种宽泛的分布说明 JIT 优化效果高度依赖于代码的执行特征 —— 热路径明确的程序受益最大,而频繁进行动态类型检查或调用自定义特殊方法的代码则可能受限。
具体到 3.15 的目标,团队在剑桥核心开发者冲刺期间制定了明确的路线图:3.15 达成 5% 的几何平均加速,3.16 达成 10% 并引入自由线程(free-threading)支持。值得关注的是,这些数字代表的是所有 pyperformance 基准测试的几何平均值,单个基准的波动远高于这一数字。团队因此采取了「保守目标 + 广泛优化」的策略,避免对特定基准进行过拟合。
追踪解释器:代码覆盖率提升 50%
JIT 编译器性能的上限很大程度上取决于它能够优化的代码量 —— 即所谓的代码覆盖率。3.13 和 3.14 版本的 JIT 使用基于区域选择(region selection)的旧前端,其依赖内联缓存的单态(monomorphic)历史数据进行类型推断,这种方式有两个根本性问题:信息 stale 太快,且对生成器、自定义 __dunder__ 方法等场景完全无法处理。
3.15 采用的解决方案是追踪 JIT(tracing JIT),思路类似于 PyPy 和 TorchDynamo:在运行时记录实际的执行轨迹,用最新的动态信息替代陈旧的历史数据。这一转变的关键实现是所谓的「双调度」(dual dispatch)机制:在解释器中维护两套调度表,一套是标准的专用化解释器,另一套是追踪解释器。所有指令在追踪模式下执行一条专用的追踪指令,而不是为每条指令都创建追踪版本。这种设计将解释器的大小增量控制在「仅增加 1 条指令」,避免了此前双表方案因解释器体积翻倍而导致的性能下降。
追踪解释器的实现细节显示,其执行速度仅比专用化解释器慢 3–5 倍(Ken Jin 的个人估算),这意味着用不到 4 倍的开销换取完整轨迹信息是值得的。更重要的是,这一改动直接使 JIT 代码覆盖率提升了 50%—— 这意味着所有后续优化,其潜在效果都被放大了约一半。
工程实践中,追踪解释器的部署参数包括:追踪阈值默认为 1000 次执行后开始记录;追踪缓冲区大小为 256KB;轨迹合并使用基于哈希的等价性检查。这些参数的微调对不同工作负载的表现有显著影响,建议通过 PYTHON_JIT_THRESHOLD 和 PYTHON_JIT_TRACE_BUFFER_SIZE 环境变量进行针对性调优。
引用计数消除:移除每次递减的分支
CPython 使用引用计数(reference counting)进行内存管理,每个对象都有一个计数器,每次创建引用时递增、销毁引用时递减。在生成的 JIT 代码中,每次 decref 操作都伴随一条条件分支 —— 检查引用计数是否归零并触发垃圾回收。这条分支对于每一条 Python 字节码都会出现,成为 JIT 代码中最大的分支热点之一。
3.14 版本的字节码优化器已经实现了局部变量的引用计数跳过 —— 如果一个局部变量在函数对象中有强引用确保其生命周期长于临时栈生命周期,则可以跳过引用计数。3.15 的改进在于将这一优化延伸到 JIT 编译阶段:通过数据流分析,识别栈上来自 LOAD_BORROW 的操作数,确认它们有上游的强引用保活,从而将 ADD 之类的指令转换为无需引用计数的版本。
这一优化的工程实现极具开创性:Ken Jin 在 GitHub 上创建了一个详细的跟踪 issue,将整个解释器的字节码逐一转换为 JIT 优化器友好的形式。这个任务被分解为大量小型、可独立处理的问题,吸引了 11 位贡献者参与(包括 Ken Jin 本人),几乎完成了整个解释器的转换。引用计数消除的效果在小规模基准测试(如 nbody)中已经可以观测到约 6% 的加速,而更关键的是,它解除了寄存器分配的约束 —— 此前因为 decref 可能调用任意 __del__ 方法,寄存器必须被立即 spill 到栈上,现在这一限制被移除,寄存器分配器可以真正发挥作用。
团队协作与「_bus factor」降低
技术改进的背后是组织方式的变革。3.13 时期,JIT 项目的中端(优化器)只有 2 名活跃的持续贡献者,这种极低的「bus factor」意味着项目抗风险能力极弱 —— 任何一位核心贡献者的离开都可能导致项目停滞。
团队在 3.15 周期采取的策略是将复杂问题拆解为可独立处理的微型任务。Brandt Bucher 在 3.14 期间开启了多个「巨型 issue」,将 JIT 优化分解为「尝试优化单条指令」这样的原子任务。Ken Jin 在此基础上进一步细化:为每个子任务提供可直接操作的详细指令,明确划分工作单元。这种「易于入门」的贡献模式使得非核心开发者(如 Hai Zhu 和 Reiden Ong)成长为有价值的中端贡献者。
到 3.15 周期,中端活跃贡献者数量从 2 人增加到 4 人,团队的整体交付能力显著增强。此外,Savannah Ostrowski 搭建的自动化性能测试基础设施(4 台机器每日运行)大幅缩短了「代码修改 → 性能反馈」的周期,使得回归可以被及时发现并修复。
后续路线:3.16 的预期改进
3.15 的成功为 3.16 奠定了基础。根据已公布的路线图,下一版本的 JIT 优化方向包括:
寄存器分配与栈缓存:CPython 字节码是栈式机器,所有操作数都在操作数栈上而非寄存器中。Mark Shannon 正在实现 Anton Ertl 1995 年提出的方案 —— 用状态机维护栈顶缓存到寄存器的映射。初步实验已经显示约 0.5% 的几何平均加速,nbody 基准更是达到 16% 的提升。
常量池与常量提升:目前的 JIT 只有受限的常量传播,3.16 计划引入类似 PyPy RPython 的 hint(x, promote=True) 机制,将运行时值提升为 trace 级常量,让优化器可以进行更激进的常量折叠和 Copy Propagation。
自由线程支持的完整化:3.15 实现了基本的自由线程兼容(JIT 代码在检测到线程创建时整体失效),3.16 将实现更细粒度的方案 —— 检测安全假设后有选择地失效和重编译。
参数清单与实践建议
针对希望在生产环境或自研项目中应用 CPython JIT 优化思路的开发者,以下是关键参数与监控指标:
| 维度 | 参数名称 | 推荐值范围 | 说明 |
|---|---|---|---|
| JIT 启用 | PYTHON_JIT |
1 |
3.15+ 默认关闭,需显式启用 |
| 触发阈值 | PYTHON_JIT_THRESHOLD |
1000(默认) |
函数被调用多少次后开始 JIT 编译 |
| 追踪缓冲区 | PYTHON_JIT_TRACE_BUFFER_SIZE |
262144(默认 256KB) |
增大可捕获更长轨迹,但增加内存开销 |
| 调试日志 | PYTHON_JIT_LOG |
0–3 |
0 = 关闭,3 = 详细日志,用于诊断编译失败 |
监控方面,建议关注:JIT 编译触发率(触发阈值调低可提高编译频率,但增加编译开销)、JIT 代码缓存命中率、以及特定热函数的 PYTHON_JIT_DUMP 输出。在实际部署前,务必使用与生产环境相同的工作负载进行基准测试 ——JIT 对 CPU 密集型任务的加速效果最显著,对 I/O 密集型任务改进有限。
资料来源:Ken Jin 博客「Python 3.15's JIT is now back on track」(2026 年 3 月 17 日)及「A Plan for 5-10%* Faster Free-Threaded JIT by Python 3.16」(2025 年 11 月 8 日)。