Python 3.14 and its New JIT Compiler
TL;DR · AI 摘要
Python 3.14 引入了实验性 JIT 编译器,显著提升性能,同时支持跨平台安装。
核心要点
- Python 3.14 的 JIT 编译器通过识别高频代码路径,将字节码转换为本地机器码,提升执行速度。
- JIT 编译器以“copy-and-patch”方式集成到 CPython,无需依赖 LLVM 等复杂编译器后端。
- Python 3.14 的 JIT 编译器已包含在官方安装包中,用户无需手动编译即可使用。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- Python 3.14 的 JIT 编译器
- JIT 编译器的工作原理
- 识别高频代码路径
- 将字节码转换为本地机器码
- JIT 编译器的集成方式
- 采用 copy-and-patch 方式
- 无需依赖 LLVM
- Python 3.14 的改进
- 包含在官方安装包中
- 无需手动编译
金句 / Highlights
值得收藏与分享的关键句。
Python 3.14 的 JIT 编译器通过识别高频代码路径,将字节码转换为本地机器码,提升执行速度。
JIT 编译器采用“copy-and-patch”方式集成到 CPython,无需依赖 LLVM 等复杂编译器后端。
Python 3.14 的 JIT 编译器已包含在官方安装包中,用户无需手动编译即可使用。
Python 3.14 及其新的 JIT 编译器 | Towards Data Science
编程
Python 3.14 及其新的 JIT 编译器
技术概述和一些基准测试
Thomas Reid
2026 年 6 月 19 日
10 分钟阅读
分享
AI 创作的图片
Python 3.14 的发布标志着世界上最受欢迎的编程语言发展过程中的一个重要节点。虽然 Python 一直因其可读性和庞大的生态系统而受到认可,但其执行速度常常是“房间里的大象”。
随着 3.14 的到来,CPython 核心开发团队不仅交付了期待已久的两个功能。
GIL 的终结
我之前已经写过关于这个话题的内容。现在,如果你想要,Python 可以实现真正的并发。如果你想了解更多关于无 GIL 的 Python 的信息,我会在文章末尾留下链接。
即时编译器(JIT)
这个实验性功能现在直接打包在官方安装程序中,这也是我们在这里要重点讨论的内容。它是 Python 核心团队和其他人多年来进行的架构准备工作的成果,旨在使 Python “默认更快”,而不会破坏从数据科学到网络后端的 C 扩展生态系统。
在本文中,我们将揭开新 JIT 的面纱,探讨它与之前优化努力的差异,并通过一些基准测试方法,帮助你决定是否是时候在你的工作负载中尝试使用 JIT。
Python 的新即时编译器(JIT)是什么?
要理解 3.14 的 JIT,我们需要了解 Python 传统上是如何运行的。标准 Python(CPython)是一种解释型语言。当你运行一个脚本时,你的代码会被编译成字节码,这是一组 CPython 虚拟机执行的指令。
JIT 改变了这个流程。JIT 不是简单地逐行解释字节码,而是监控你的代码中哪些部分被执行得最频繁(“热点”路径)。当一个函数或循环被判定为“热点”时,JIT 会将字节码转换为本地机器代码(CPU 能理解的指令)。然后,下一次代码被调用时,就不需要解释了。相反,它直接运行。我们稍后会看到,这可以节省大量时间。
JIT 如何融入 CPython
Python 3.14 的 JIT 并不是一次彻底的重写。它被设计为一个可选组件,与现有的解释器协同工作。它使用一种称为“复制和修补”的技术,这使得 JIT 能够轻量且跨不同 CPU 架构便携,而不需要像 LLVM 这样庞大的复杂编译器后端。
Python 3.14 中的变化
Python 3.13 有一个基本的、实验性的 JIT,但默认是禁用的。如果你想测试它,你必须克隆 CPython 源代码树,并使用特定的实验性标志(如 --enable-experimental-jit)进行编译。
随着 Python 3.14 的发布,一切都发生了变化。它在官方的 .msi(Windows)和 .pkg(macOS)安装程序中提供了 JIT。这意味着你不再需要在机器上安装 C 编译器,就可以体验 JIT 的优势。虽然它仍然是“实验性”的,但包含在官方二进制文件中表明核心团队认为 JIT 已经足够稳定,可以进行广泛的社区测试。
获取 Python 3.14
前往 https://www.python.org/downloads/ ,你将看到一个下载 3.14 的选项。点击它,然后按照说明操作。
或者,如果你已经安装了 UV 工具,你可以输入以下命令。
PS C:\ > uv python install 3.14启用 JIT
默认情况下,JIT 是禁用的。这是一个安全措施;由于它尚处于实验阶段,Python 指导委员会希望确保用户在没有明确选择的情况下,不会遇到稳定性或内存使用方面的意外退化。
要激活 JIT,可以使用环境变量。这会告诉 CPython 运行时在启动时初始化 JIT 引擎。
在 Windows(PowerShell)上:
$env:PYTHON_JIT=1
python my_script.py在 macOS/Linux(Bash/Zsh)上:
PYTHON_JIT=1
python my_script.py一旦启用,CPython 不会立即对所有内容进行 JIT 编译。它使用一个分级系统。基本上,它首先尽可能便宜地运行代码,只在被证明是热点的部分上花费编译/优化的精力。
- 第 0 级:标准解释。
- 第 1 级:专用字节码(在 3.11 中引入)。
- 第 2 级(JIT):为最常使用的路径生成机器代码。
评估 JIT 的影响
在测试 JIT 时,不能简单地在函数周围使用 time.time()。JIT 需要一个预热期。循环的前几次迭代可能比正常情况下更慢,因为 JIT 在分析代码,但后续的迭代可能会显著更快。
测试套件
以下是一个全面的测试套件,旨在测试 JIT 的不同方面,从繁重的数学运算到复杂的对象操作。
文件 1:workloads.py
该文件包含三个不同的 CPU 密集型任务。
1/ Mandelbrot 函数在像素网格上迭代 Mandelbrot 公式,并返回每个像素迭代次数的校验和。
2/ Dijkstra 函数构建一个确定性随机加权图,并从节点 0 运行 Dijkstra 算法,返回已最终化/访问的节点数量。
3/ Levenshtein 函数生成 N 个确定性随机字符串对,并返回它们的 Levenshtein 距离总和。
from __future__ import annotations
import random
import heapq
# 工作负载 1: Mandelbrot (CPU + 数学循环)
def mandelbrot(width: int = 1000, height: int = 1000, iters: int = 500) -> int:
checksum = 0
for y in range(height):
cy = (y / height) * 2.4 - 1.2
for x in range(width):
cx = (x / width) * 3.2 - 2.2
zx, zy, count = 0.0, 0.0, 0
while zx * zx + zy * zy <= 4.0 and count < iters:
zx, zy = zx * zx - zy * zy + cx, 2.0 * zx * zy + cy
count += 1
checksum += count
return checksum
# 工作负载 2: Dijkstra (堆 + 列表 + 逻辑)
def dijkstra(n: int = 10000, edges_per_node: int = 50, seed: int = 123) -> int:
rng = random.Random(seed)
graph = [[] for _ in range(n)]
for u in range(n):
for _ in range(edges_per_node):
v = rng.randrange(n)
if v != u:
graph[u].append((v, rng.randrange(1, 30)))
dist = [10**12] * n
dist[0] = 0
pq = [(0, 0)]
visited = 0
while pq:
d, u = heapq.heappop(pq)
if d != dist[u]:
continue
visited += 1
for v, w in graph[u]:
nd = d + w
if nd < dist[v]:
dist[v] = nd
heapq.heappush(pq, (nd, v))
return visited工作负载 3:Levenshtein 距离(动态规划)
def levenshtein(a: str, b: str) -> int: prev = list(range(len(b) + 1)) for i, ca in enumerate(a, 1): cur = [i] for j, cb in enumerate(b, 1): cur.append(min(cur[j - 1] + 1, prev[j] + 1, prev[j - 1] + (ca != cb))) prev = cur return prev[-1]
def levenshtein_batch(n: int = 10000, seed: int = 7, k: int = 50) -> int: """ 确定性批次:固定 RNG 种子,固定字母表,固定字符串长度。 返回距离总和。 """ rng = random.Random(seed) alphabet = "abc" total = 0 for _ in range(n): a = "".join(rng.choices(alphabet, k=k)) b = "".join(rng.choices(alphabet, k=k)) total += levenshtein(a, b) return total
### 文件 2:benchmark.py
此脚本用于自动比较启用和禁用 JIT 的不同工作负载。
import os import time import json import subprocess from pathlib import Path
PYTHON_EXE = r"C:\Users\thoma\AppData\Local\Programs\Python\Python314\python.exe" PROJECT_DIR = Path(__file__).resolve().parent
原始工作负载(语句打印结果以确保正常)
WORKLOADS = [ ("mandelbrot", 'from workloads import mandelbrot; print(mandelbrot())'), ("dijkstra", 'from workloads import dijkstra; print(dijkstra())'), ("levenshtein_batch", 'from workloads import levenshtein_batch; print(levenshtein_batch())'), ]
N_RUNS = 10 # 所有运行的平均值(可以根据需要设置为 6/10/20) OUTFILE = PROJECT_DIR / "results_avg.json"
def run_once(stmt: str, jit_val: int) -> tuple[float, str]: env = os.environ.copy() env["PYTHON_JIT"] = str(jit_val)
确保 subprocess 中可以导入本地 workloads.py
env["PYTHONPATH"] = str(PROJECT_DIR) + (os.pathsep + env.get("PYTHONPATH", ""))
t0 = time.perf_counter() p = subprocess.run( [PYTHON_EXE, "-c", stmt], env=env, cwd=str(PROJECT_DIR), capture_output=True, text=True, ) t1 = time.perf_counter()
if p.returncode != 0: raise RuntimeError( f"运行失败 (PYTHON_JIT={jit_val})\n\n" f"语句:\n{stmt}\n\n" f"STDOUT:\n{p.stdout}\n\nSTDERR:\n{p.stderr}" )
return (t1 - t0, p.stdout.strip())
def summarize(times: list[float]) -> dict: return { "avg": sum(times) / len(times), "min": min(times), "max": max(times), "runs": times, }
def bench_workload(name: str, stmt: str) -> dict: results = {} outputs = {}
for jit_val in (0, 1): times = [] outs = [] print(f" PYTHON_JIT={jit_val}: 运行 {N_RUNS} 次...") for i in range(1, N_RUNS + 1): dt, out = run_once(stmt, jit_val) times.append(dt) outs.append(out) print(f" 运行 {i}/{N_RUNS}: {dt:.6f}s")
results[jit_val] = summarize(times) outputs[jit_val] = outs
avg0 = results[0]["avg"] avg1 = results[1]["avg"] speedup = avg0 / avg1 if avg1 else float("inf") delta_pct = (avg1 - avg0) / avg0 * 100.0 if avg0 else 0.0
return { "workload": name, "jit0": results[0], "jit1": results[1], "speedup_jit0_over_jit1": speedup, "delta_pct_jit1_vs_jit0": delta_pct, "outputs": outputs, # 正确性检查:应保持稳定 }
def main() -> int: all_results = [] print(f"使用 Python: {PYTHON_EXE}") print(f"项目目录: {PROJECT_DIR}") print(f"每个设置的运行次数(所有运行的平均值): {N_RUNS}\n")
for name, stmt in WORKLOADS: print(f"=== {name} ===") r = bench_workload(name, stmt) all_results.append(r)
print(f"\n 平均值:") print(f" JIT=0 平均值: {r['jit0']['avg']:.6f}s (最小值 {r['jit0']['min']:.6f}, 最大值 {r['jit0']['max']:.6f})") print(f" JIT=1 平均值: {r['jit1']['avg']:.6f}s (最小值 {r['jit1']['min']:.6f}, 最大值 {r['jit1']['max']:.6f})") print(f" 速度提升(JIT=0 / JIT=1): {r['speedup_jit0_over_jit1']:.3f}× (Δ={r['delta_pct_jit1_vs_jit0']:+.2f}%)\n")
可选:如果运行结果不一致(非确定性),发出警告
if len(set(r["outputs"][0])) != 1: print(" !! 警告:JIT=0 的输出在不同运行中不一致(是否为非确定性工作负载?)") if len(set(r["outputs"][1])) != 1: print(" !! 警告:JIT=1 的输出在不同运行中不一致(是否为非确定性工作负载?)")
OUTFILE.write_text(json.dumps(all_results, indent=2), encoding="utf-8") print(f"已写入: {OUTFILE}") return 0
if __name__ == "__main__": raise SystemExit(main())
这是我的结果。
C:\Users\thoma\projects\python_jit>C:\Users\thoma\AppData\Local\Programs\Python\Python314\python.exe benchmark.py 使用 Python: C:\Users\thoma\AppData\Local\Programs\Python\Python314\python.exe 项目目录: C:\Users\thoma\projects\python_jit 每个设置的运行次数(所有运行的平均值): 10
=== mandelbrot === PYTHON_JIT=0: 正在运行 10 次... 第 1/10 次运行: 6.890924s 第 2/10 次运行: 6.950737s 第 3/10 次运行: 7.265357s 第 4/10 次运行: 6.947150s 第 5/10 次运行: 6.932333s 第 6/10 次运行: 6.939378s 第 7/10 次运行: 7.194705s 第 8/10 次运行: 6.995550s 第 9/10 次运行: 6.902696s 第 10/10 次运行: 7.256164s PYTHON_JIT=1: 正在运行 10 次... 第 1/10 次运行: 5.216740s 第 2/10 次运行: 5.241888s 第 3/10 次运行: 5.350822s 第 4/10 次运行: 5.246767s 第 5/10 次运行: 5.294771s 第 6/10 次运行: 5.273295s 第 7/10 次运行: 5.272135s 第 8/10 次运行: 5.617062s 第 9/10 次运行: 5.251656s 第 10/10 次运行: 5.239060s
平均值: JIT=0 平均值: 7.027499s (最小值 6.890924, 最大值 7.265357) JIT=1 平均值: 5.300420s (最小值 5.216740, 最大值 5.617062) 速度提升(JIT=0 / JIT=1): 1.326× (Δ=-24.58%)
=== dijkstra === PYTHON_JIT=0: 正在运行 10 次... 第 1/10 次运行: 0.235401s 第 2/10 次运行: 0.227603s 第 3/10 次运行: 0.244492s 第 4/10 次运行: 0.232971s 第 5/10 次运行: 0.249589s 第 6/10 次运行: 0.232229s 第 7/10 次运行: 0.229422s 第 8/10 次运行: 0.238399s 第 9/10 次运行: 0.230657s 第 10/10 次运行: 0.235772s PYTHON_JIT=1: 正在运行 10 次... 第 1/10 次运行: 0.238862s 第 2/10 次运行: 0.239266s 第 3/10 次运行: 0.240312s 第 4/10 次运行: 0.231413s 第 5/10 次运行: 0.232692s 第 6/10 次运行: 0.233783s 第 7/10 次运行: 0.230016s 第 8/10 次运行: 0.237760s 第 9/10 次运行: 0.240895s 第 10/10 次运行: 0.246033s
平均值: JIT=0 平均值: 0.235653s (最小值 0.227603, 最大值 0.249589) JIT=1 平均值: 0.237103s (最小值 0.230016, 最大值 0.246033) 速度提升(JIT=0 / JIT=1): 0.994× (Δ=+0.62%)
=== levenshtein_batch ===
PYTHON_JIT=0: 运行 10 次...
第 1/10 次运行: 2.176256 秒
第 2/10 次运行: 2.171253 秒
第 3/10 次运行: 2.171834 秒
第 4/10 次运行: 2.170444 秒
第 5/10 次运行: 2.149874 秒
第 6/10 次运行: 2.162820 秒
第 7/10 次运行: 2.171975 秒
第 8/10 次运行: 2.199151 秒
第 9/10 次运行: 2.168398 秒
第 10/10 次运行: 2.167821 秒
PYTHON_JIT=1: 运行 10 次...
第 1/10 次运行: 1.575666 秒
第 2/10 次运行: 1.612615 秒
第 3/10 次运行: 1.571106 秒
第 4/10 次运行: 1.584650 秒
第 5/10 次运行: 1.579948 秒
第 6/10 次运行: 1.582633 秒
第 7/10 次运行: 1.593924 秒
第 8/10 次运行: 1.573608 秒
第 9/10 次运行: 1.581427 秒
第 10/10 次运行: 1.578553 秒
平均值:
JIT=0 平均值: 2.170983 秒 (最小值 2.149874, 最大值 2.199151)
JIT=1 平均值: 1.583413 秒 (最小值 1.571106, 最大值 1.612615)
加速比 (JIT=0 / JIT=1): 1.371× (Δ=-27.06%)
解读结果
正如你所看到的,结果是好坏参半的。对于实验性 JIT 来说,这是正常的。
- 10–30% 的加速:在“纯 Python”循环中很常见(如曼德博集合或莱文斯坦测试),JIT 可以避免字节码分发循环的开销。
- 0% 的改进:在 I/O 密集型任务或大量使用 C 扩展的代码中很常见。迪杰斯特拉代码没有加速,因为其运行时间主要由堆/元组操作和内存密集型、分配驱动的工作主导,而当前的 CPython JIT 并没有显著优化这些部分,因此任何解释器的节省都被噪声掩盖了。
何时使用 Python 3.14 的 JIT
JIT 是一个强大的工具,但它不是一个“万能按钮”。根据我的经验,你应该在以下情况下尝试使用 JIT:
- CPU 密集型逻辑:你的应用程序在纯 Python 中执行大量计算、数据处理或复杂逻辑。
- 长时间运行的进程:运行数小时的 Web 服务器(Gunicorn/Uvicorn)或后台工作器(Celery),让 JIT 有足够的时间预热并优化热点路径。
- 实验性测试:你希望为未来的 Python 版本(3.15+)准备代码库,在这些版本中,JIT 很可能会更加激进。
应避免在以下情况下使用:
- I/O 密集型应用:如果应用程序只是等待数据库查询或 API 响应,JIT 将不会带来帮助。
- 内存受限的环境:小的 Lambda 函数或小型容器可能会因为 JIT 缓存增加的内存占用而受到影响。
- 短生命周期的 CLI 工具:运行时间不到一秒的脚本不需要 JIT。
未来方向:超越 3.14
CPython 核心团队将 3.14 视为“基础年”。未来的版本(Python 3.15 和 3.16)预计将包括:
- 更深入的优化过程:利用运行时收集的类型信息,进行更激进的机器代码生成。
- 更好的启发式方法:在何时编译上做出更智能的决策,减少“预热”惩罚。
- 更低的开销:优化复制和修补机制,以减少内存消耗。
总结
Python 3.14 的 JIT 不仅仅是一个性能补丁。它表明了 Python 的意图。它表明 Python 有志于缩小与 Java 或 Go 等语言的性能差距,同时保持使其著名的“包含电池”的简洁性。
对于大多数开发者来说,JIT 只是另一个值得关注的工具。如果性能在你的项目中很重要,那么值得在你的现有工作负载上测试 Python 3.14。在你最重要的代码路径上进行一些基准测试,可能会揭示你意想不到的性能提升。
这是我之前关于 GIL Free Python 的文章的链接,我在开头提到了它。
Python 3.14 与 GIL 的终结
撰写者
查看 Thomas Reid 的所有文章
编译器
Jit
性能分析
Python
分享本文
- 在 Facebook 上分享
- 在 LinkedIn 上分享
- 在 X 上分享
Towards Data Science 是一份社区出版物。提交你的见解,以触达全球读者,并通过 TDS 作者支付计划获得报酬。
更新 href 为你的实际投稿 URL
为 TDS 撰写文章
✦ 结束 CTA ✦