🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/
提升 Python 代码性能至接近 C 语言速度,无需修改源代码。遵循 Python 之父吉多・范罗苏姆的建议:“如果你想让你的代码神奇地运行得更快,你应该试试用 PyPy。”
YouTube 视频链接:https://www.youtube.com/watch?v=2wDvzy6Hgxg&t=1012s
研究人员需要快速编写代码来验证想法的可行性,这对于他们的工作至关重要。Python 是一个理想的选择,它能帮助人们专注于想法本身,而无需过多关注代码格式等琐碎事务。然而,Python 的一个主要缺点是其运行速度相较于 C 或 C++ 等编译型语言明显较慢。
那么,当通过构建 Python 原型验证了想法后,如何将其转化为一个快速且高效的工具呢?通常情况下,人们需要进行额外的步骤,即手动将 Python 代码转换为 C 语言代码。但如果 Python 原型本身就能实现快速运行,那么转换代码的时间便可用于更具价值的工作。
PyPy 恰好能够解决这一问题,它能够显著提升 Python 代码的运行速度,甚至可以与 C 语言相媲美。为了验证 PyPy 的性能优势,我们进行了以下实验:分别使用默认的 Python 解释器和 PyPy 来运行一段代码,该代码执行一个从整数 0 加到 100,000,000 的 for 循环,并打印出运行时间。以下是实验结果:
# 导入了 Python 的 time 模块。time 模块提供了各种与时间相关的函数,可以用来测量时间、处理日期时间等。
import time
# termcolor 是一个第三方库,用于在终端输出彩色文本。colored 函数可以根据指定的颜色来格式化文本。
from termcolor import colored
# 调用了 time 模块中的 time() 函数,并将返回值(当前时间的时间戳,单位为秒)赋值给变量 start。这里记录下开始计算之前的时间点,以便后续计算总耗时。
start = time.time()
# 初始化变量 number 为 0。这个变量将用来累加从 0 到 99,999,999 的所有整数。
number = 0
for i in range(100000000):
# 在循环体内,每次循环都将变量 i 的值累加到变量 number 上。这样,在循环结束时,number 将包含从 0 到 99,999,999 所有整数的和。
number += i
print(colored("FINISHED", "green"))
# 再次调用 time.time() 获取当前时间戳,并与之前记录的开始时间戳(存储在变量 start 中)相减,得到执行计算所花费的总时间(单位为秒)。然后,使用 f-string 格式化字符串,将计算出的耗时插入到字符串中并打印出来。这样用户就可以看到程序执行所需的时间。
print(f"Ellapsed time: {time.time() - start} s")
这不是学术评估,但结果令人惊叹。与默认的 Python 解释器(大约需要 10 秒)相比,PyPy 仅用 0.22 秒就完成了执行。更令人惊讶的是,无需任何修改,Python 代码即可直接在 PyPy 上运行。而在同一台计算机上,等效的 C 语言实现需要 0.32 秒。PyPy 甚至超过了最快的 C 语言实现。
为什么 PyPy 这么快?
尽管代码看起来完全相同,但其执行方式却大相径庭。PyPy 提升执行速度的关键在于采用 “即时编译”(Just-In-Time Compilation),也就是 JIT 编译技术。
C、C++、Swift、Haskell、Rust 等编程语言通常采用提前编译(AOT 编译)的方式。这意味着,使用这些语言编写的代码,在程序运行之前,编译器会将其源代码转换为特定计算机架构可执行的机器码。因此,当程序执行时,实际运行的是机器码,而非原始的源代码。
不同于 C 语言等上述语言,Python、JavaScript、PHP 等语言采用的是另一种方法 —— 解释器。与将源代码转换为机器码不同,解释过程中源代码保持不变。每次运行程序时,解释器都会逐行查看代码并执行。例如,每个 Web 浏览器都内置了 JavaScript 解释器。
PyPy 利用即时编译技术来执行 Python 代码,与传统解释器不同,它不会逐行运行代码,而是在程序执行前先将部分代码编译成机器码。即时编译结合了提前编译和解释的优点。如图所示,PyPy 采用的即时编译融合了这两种方法,通过提前编译提升性能,同时保持解释型语言的灵活性和跨平台可用性。
AOT 优点:
-
在程序运行前进行编译,可以避免运行时的编译性能消耗和内存消耗。
-
程序在运行初期即可达到最高性能。
-
显著加快程序启动速度。
AOT 缺点:
-
程序运行前的编译增加了程序安装的时间。无运行时性能加成,不能根据程序运行情况做进一步的优化。
-
提前编译的内容保存会占用更多的外部存储空间。
JIT 优点:
-
在程序运行时,根据算法计算出热点代码,然后进行 JIT 实时编译,这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等。
-
可以根据当前程序的运行情况生成最优的机器指令序列,从而提高程序的执行效率。
-
当程序需要支持动态链接时,JIT 是一个优先的选择。
-
JIT 可以根据进程中内存的实际情况调整代码,从而更充分地利用内存资源。
JIT 缺点:
-
编译过程会占用运行时资源,这可能导致进程卡顿,影响用户体验。
-
相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制。
-
编译时间需要占用运行时间,并且某些代码的编译优化可能无法实现,因此我们需要考虑程序流畅性和编译时间的问题。
-
在编译准备和识别频繁使用的方法时,需要消耗一定的时间,这使得初始编译可能无法达到最高性能。因此,JIT 需要在运行时不断优化代码,以提高程序的执行效率。
PyPy 在我们的程序已经很快或者大部分运行时间都用于调用非 Python 库时,效果较差。然而,如果我们有一个运行缓慢的程序,其中大部分时间都花在执行 Python 代码和密集的计算上,PyPy 可以发挥奇效。
总结:编译型编程语言会将源代码提前转化为机器代码,而解释型编程语言则由解释器逐行运行。即时编译技术结合了提前编译和解释的优点,实现边运行边编译。JIT 与 AOT 的主要区别在于编译时间:JIT 在运行时编译,而 AOT 则在程序运行前进行编译。PyPy 采用即时编译技术,显著提高了 Python 代码的运行速度。即时编译不仅提升了程序执行效率,还保持了解释型语言的灵活性和跨平台可用性。
📚️ 相关链接:
-
TDS - Run Your Python Code as Fast as C
-
AOT、JIT的区别,各自的优缺点,混合编译
-
解释执行?编译执行?即时编译?轻松让你分清前期编译与后期编译
-
Taichi 是一门开源的、嵌入在 Python 中的并行编程语言