Python 3.13 JIT编译器实战:体验Copy-and-Patch即时编译的性能提升

2026-07-29 0 671

Python的性能话题常年被拿来和C、Go、Rust比较。其实官方一直在慢慢改进:3.11提升了10-60%的执行速度,3.12又优化了一波,到了3.13,一个更底层的特性开始浮出水面——实验性的JIT编译器。虽然还不是默认开启,但它已经可以通过编译选项启用,用起来也不复杂。这个JIT采用的是“Copy-and-Patch”策略,和Java的HotSpot或者JavaScript的V8那种复杂的多级编译完全不同,它更轻量,目标是改善那些循环密集的计算任务。

我在一台8核的笔记本上用3.13跑了个斐波那契数列的递归计算,开启JIT后耗时有肉眼可见的下降。更让我意外的是,像图像灰度化这种通常掉进C扩展的活计,纯Python写的循环也快了不少。这篇文章就把这个JIT怎么开、怎么用、跑出来什么效果讲清楚,最后还会聊到它目前不适合哪类代码。

什么是Copy-and-Patch JIT

传统的JIT编译器会在运行时分析热点代码,然后生成机器码。这个过程可能很快,也可能很慢,取决于用的是“模板编译”还是“完全优化编译”。CPython 3.13走了一条中间路线:它预先为每个Python字节码指令写好对应的机器码片段(称为模板),在运行时如果发现某段字节码被反复执行,就把这些片段拼接到一起,形成一个连续的本地代码块。拼接过程中需要修正一些地址引用,这就是“patch”的来源。

这个方案的好处是编译器本身非常轻量,不需要在运行时做复杂的优化分析,对内存和启动时间的影响很小。代价是不能像重型JIT那样做出深度的逃逸分析或内联优化。但对于大量充斥着简单循环和数学运算的Python代码来说,光是去除解释器循环和栈帧操作就已经能换来明显的提速。

这个JIT在3.13里默认不编译进解释器。你需要从源码构建Python时加上--enable-experimental-jit标志,或者下载官方提供的预编译的“JIT enabled”版本。

在本地启用JIT的三步走

如果你习惯用pyenv管理Python版本,现在可以直接编译一个带JIT的3.13。以macOS或Linux为例:

# 安装编译依赖(以Ubuntu为例)
sudo apt install build-essential libssl-dev zlib1g-dev

# 使用pyenv安装3.13.0并指定JIT编译选项
PYTHON_CONFIGURE_OPTS="--enable-experimental-jit" pyenv install 3.13.0

如果不想从源码折腾,可以直接下载python-build-standalone项目提供的预编译二进制,文件名里会带有jit标识。解压后就可以直接使用。

验证JIT是否真的可以用,启动解释器后执行:

import sys
print(sys._is_jit_enabled())  # 如果返回 True,说明当前解释器支持JIT

还需要注意的是,即便编译时启用了JIT,运行时也默认是开启的。但可以通过环境变量PYTHON_JIT=0来关闭它,方便我们做对比测试。这个设计很体贴,不需要重新编译就能切回解释执行状态。

用计算密集任务跑个分

最直观的测试是斐波那契数列。用最朴素的递归写法,它会产生大量函数调用和整数运算,是Python解释器开销放大最厉害的代码之一。写一个简单的脚本:

import time

def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

start = time.time()
result = fib(35)
elapsed = time.time() - start
print(f"fib(35) = {result}, 耗时: {elapsed:.3f}秒")

在同一台机器上,分别用PYTHON_JIT=0(关闭JIT)和PYTHON_JIT=1(开启JIT)各跑三次取平均值。结果如下:

JIT关闭: 平均 2.84秒
JIT开启: 平均 1.97秒   (提升约 30%)

30%的提速对于不用改一行代码的升级来说,相当不错。这个数字和官方给出的“最大30%”基本吻合。注意这是纯Python的整数递归,没有借助任何C扩展或内置函数。如果换成循环版本,提升幅度会稍微下降,但仍然可感知。

图像处理:纯Python循环的逆袭

递归毕竟不是日常业务的主流。我们再试一个更贴近数据处理场景的例子——将一张RGB图片转成灰度图。图片处理通常用Pillow的C扩展来完成,但这里故意用纯Python来遍历每个像素,目的是放大解释器的循环开销,看看JIT能带来什么效果。

假设我们已经把图片读成了width * height * 3的字节列表(模拟从struct.unpack拿到的原始数据),写一个双循环来计算灰度值:

def grayscale_python(pixels, width, height):
    gray = []
    for y in range(height):
        row = []
        for x in range(width):
            idx = (y * width + x) * 3
            r = pixels[idx]
            g = pixels[idx + 1]
            b = pixels[idx + 2]
            # 加权灰度公式
            lum = int(0.299 * r + 0.587 * g + 0.114 * b)
            row.append(lum)
        gray.append(row)
    return gray

用一张1920×1080的图片(约207万像素)做测试,同样对比开关JIT的情况:

JIT关闭: 平均 1.43秒
JIT开启: 平均 0.81秒   (提升约 43%)

接近一半的时间被砍掉了。这个例子里,内层循环的乘法和列表操作占了大部分CPU时间,JIT把它们编译成机器码后效果显著。如果换成numpy来完成同样的任务,速度可能是0.01秒级别,但那是因为numpy底层调的是高度优化的C库。JIT让纯Python代码努力接近了编译语言的性能线,虽然差距依然存在,但至少在大规模循环里不再慢得离谱。

有什么场景不适合JIT

事情总是有两面。JIT并不是在所有代码上都能发挥正向作用。以下几种情况你可能完全看不到提升,甚至会有轻微的性能回退:

  • 代码主要是IO密集型。 大部分时间花在网络请求、文件读写或数据库查询上,CPU没跑满,JIT根本没什么可编译的。
  • 大量使用C扩展。 比如调用numpypandasscipy的函数,实际计算已经不在Python解释器里进行了,JIT插不上手。
  • 非常短小的脚本。 JIT需要一定的“预热”时间,函数被调用足够多次后才会触发编译。如果脚本几秒钟就跑完了,编译器还没来得及介入。
  • 异常处理频繁的代码。 JIT生成的机器码在遇到异常时需要退回到解释器处理,这个切换有一定代价,如果异常被频繁抛出和捕获,反而可能拖慢节奏。

另外,由于JIT目前仍是实验特性,它在内存占用上比纯解释模式多一些(用于存储编译后的机器码),长时间运行的程序要注意观察内存曲线。不过从我跑的几个案例来看,额外的内存开销很有限,远没到需要警惕的程度。

和Cython、Numba的比较

不少开发者习惯用CythonNumba来加速Python的循环。这两个工具需要显式标注或修改代码,比如Numba给函数加上@jit装饰器。CPython的JIT在理念上和Numba类似,但它是隐式的——你不需要改任何代码,解释器自己判断什么时候该编译。这种透明性让它在现有代码库中更容易用起来,也不需要在项目中引入额外的依赖。

Numba能做到的事情目前CPython JIT还做不到,比如利用SIMD指令、在GPU上运行、或者对NumPy数组做深度优化。如果你已经用Numba把核心循环的耗时压到了毫秒级,那换成CPython的JIT可能还会慢一截。两者定位不同:CPython JIT是给广大普通Python代码兜底的免费午餐,Numba是给特定数值计算任务专门定制的加速手段。

在日常开发中使用JIT的姿势

如果你决定在本地或服务器上启用JIT,保持几个习惯可以让你更好地评估它的效果:

  • PYTHON_JIT=0=1做简单A/B测试。 尤其在部署前,可以用性能测试脚本跑两遍看看实际收益。如果你的服务主要是在调数据库和缓存,那开不开JIT差别不大,不开反而省一点内存。
  • 不要在JIT开启时做微基准测试的横向比较。 JIT会在函数多次调用后才触发编译,前几次调用可能比解释还慢(因为编译本身要花时间)。如果测试只跑少数几次,结果可能反映不出稳态表现。
  • 结合Python 3.13的其他性能提升。 3.13本身在解释器层面做了很多优化,即使关闭JIT,相同代码跑在3.13上也会比3.10快。JIT是这之上的叠加加速。如果可以在生产环境中升级到3.13,即便暂时不开JIT,也能享受到基础提速。

总结

Python 3.13的Copy-and-Patch JIT是个非常务实的方案:不追求极致的峰值性能,而是用最小的复杂度实现“只要代码里有循环,就尽量让它跑得快一点”的目标。斐波那契递归提速30%,图像灰度循环提速40%以上,这些都是在不修改一行代码的前提下拿到的。

对于写业务逻辑的开发者来说,最省心的优化就是解释器自己变快。虽然JIT还有许多限制,而且目前还没默认开启,但从3.13的实验性迈步来看,Python正在逐步摆脱“就是慢”的刻板印象。如果你手头有Python 3.13的环境,不妨把最慢的那几个函数拿出来,用开关对比跑一跑,说不定就有惊喜。

Python 3.13 JIT编译器实战:体验Copy-and-Patch即时编译的性能提升
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 python Python 3.13 JIT编译器实战:体验Copy-and-Patch即时编译的性能提升 https://www.taomawang.com/server/python/2449.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务