Python 3.13 预发布版本添加了实验性 JIT 编译器。也就是说,它可能会在秋季成为完整版本的一部分。据说它使代码运行得更快。
但是,如果我理解正确的话,JIT编译器可以通过不同的方式实现,因为Python 有几个非官方编译器:pypy / numba。因此,他们可以通过不同的方式或在不同的地方加速应用程序。
问题出现了 - JIT 编译器在 Python 中到底优化了什么,相应地,它们可以用于什么类型的任务?
Python 3.13 预发布版本添加了实验性 JIT 编译器。也就是说,它可能会在秋季成为完整版本的一部分。据说它使代码运行得更快。
但是,如果我理解正确的话,JIT编译器可以通过不同的方式实现,因为Python 有几个非官方编译器:pypy / numba。因此,他们可以通过不同的方式或在不同的地方加速应用程序。
问题出现了 - JIT 编译器在 Python 中到底优化了什么,相应地,它们可以用于什么类型的任务?
您不应期望官方 JIT 在不久的将来对 Python 应用程序产生任何显着加速(正如@insolor指出的那样)。这源自PEP-744 -“JIT 编译”,到目前为止,该平台仅谈论其中一个平台的 5% 加速。
不过,未来当开发者添加更多的优化机制时,性能提升将会更大。
使用 JIT 编译器加速工作的一些选项
替换慢指令
虽然这种优化方法被归类为 JIT 编译器,但在 Python 3.11 版本中,优化被引入到解释器中(PEP-659 - “专用自适应解释器”) - 它用于优化字节码。
在具有动态类型的语言(如Python)中,函数事先并不知道可能会进入其中的数据:
这个函数可以轻松处理数字和字符串 - 最主要的是类型匹配:
默认情况下,将使用操作码
BINARY_OP
- 使用它,将花费时间根据传递的参数(“+”)选择适当的命令:如果我们使用一个循环来调用
add()
带有整数的函数,那么根据先前调用的信息,我们可以用更快的专用指令替换缓慢的通用指令BINARY_OP_ADD_INT
:但是,如果将非数字传递给某些调用,则会发生去优化- 返回到指令的基本执行。
优化多个指令协同工作
延续优化单个命令的想法,我们可以一次跟踪多个字节码的使用情况,并为它们的执行创建更快的路径。为此,首先执行跟踪(搜索经常使用的代码部分以进行优化),然后可以从字节码中删除一些微操作(
micro-ops
),并且只能使用剩余的微操作,而不是原始的完整字节码。考虑相同的示例,其中有一个循环,
range(100)
其中 2 个整数相加。因此,每个字节码命令都以相同的微操作开始——
_CHECK_VALIDITY_AND_SET_IP
这确保了生成代码的正确性,因为用户代码可以抛出异常、更改局部变量的值、更正内置函数等。但是,如果从收集的信息中我们知道这是对一系列数字进行简单迭代,将两个整数相加,那么我们可以将其删除。此外,在FOR_ITER_RANGE循环命令的代码中还有
_ITER_CHECK_RANGE
.它确保被迭代的对象仍然是一个范围。但在这种特殊情况下,我们知道它始终是一个范围,因此也可以删除保护性微操作。同样,BINARY_OP_ADD_INT 的微操作以
_GUARD_BOTH_INT
- 一个微操作开始,该微操作可防止操作数类型发生更改,并在发生这种情况时采用正常的缓慢路径。但是,类型不会改变,因此可以取消保护。因此,您可以不使用字节码,而是使用缩短的微操作(按照加法周期的示例,它们从 19 块减少到 10 块 - 几乎减少了 2 倍)。
然而,执行这样的优化本身只会减慢程序的执行速度,因为创建和使用 10 个微操作的开销高于 7 个完整字节码。因此,它必须与JIT编译器结合使用,JIT编译器可以在编译过程中使用优化的执行路径。
复制和补丁编译
该版本的JIT编译计划从3.13开始在官方Python中使用(以及多指令优化方法)。在这里您可以阅读一篇描述此版本 JIT 编译的一般概念的文章。
主要思想
.o
:使用 LLVM,以 ELF 格式预组装目标文件(模板),其中保留“孔”——在程序执行期间向其中插入数据的位置。 JIT 将程序解释期间生成的字节码指令(以上述方式优化)替换为机器代码的表示形式,同时将计算所需的数据替换为留下的“漏洞”。也就是说,程序将具有预先编写的机器代码片段,通过插入内存地址、寄存器地址、常量和其他参数来修补这些片段,从而加快工作速度。
Copy-and-Patch编译没有任何特定的用法。因此,在本文中,研究人员优化了网络和数据库的工作。
根据 Python 中 JIT 的可用信息,加速应用程序的关键是创建行为可预测且变量数据类型保持不变的函数。长周期运行的应用程序尤其受益。
进一步的优化路径尚未公布。
例如,IBM列出了 Java JIT 编译器中的 19 项优化。
Python官方的优化方法和JIT编译的信息来源:
Python 的其他 JIT 示例及其优化方式
吡啶
为了加快工作速度,PyPy 不仅使用跟踪,还使用
метатрассировку
.前缀“meta”意味着跟踪是在解释器运行时完成的,而不是在程序运行时完成的。 PyPy解释器可以监控自身的操作并优化代码执行路径。优化良好的解释器遵循机器代码的特定路径 - 这些代码已经可以在最终编译中使用。然而,这需要相当多的函数调用(大约需要执行该函数 3000 次才能更快地开始工作)。
此外,由于 PyPy 本身本质上是用 Python(更准确地说
RPython
)编写的,因此它需要创建额外的层来执行用 C 编写的函数,这最终会减慢应用程序的速度。如果满足以下条件,使用 PyPy 可以加快代码速度:
包含有关 PyPy 信息的文章:
努巴
Numba 通过向代码添加装饰器来将 JIT 添加到带注释的 Python 代码中(无需更改解释器)。
Numba 读取形式化函数的 Python 字节码,并将其与有关函数输入参数类型的信息组合起来。接下来,对代码进行分析和优化,然后使用 LLVM 编译器库生成函数的机器代码,以适应所用处理器的功能。每次调用该函数时都会使用此编译版本。
然而,这些优化仅限于数学运算和 NumPy 库的运算。SciPy有一个单独的扩展。 Numba 不再理解其他库(特别是 pandas)。
如果您的代码是面向数字的(大量数学)、经常使用 NumPy 和/或有大量循环,那么 Numba 将是一个不错的选择。
有关 Numba 的信息的文章: