python是一种执行速度较慢的编程语言,这好像已经是业内对python的共同观点。例如,python语言中的循环比类似的C语言写成的循环,执行速度慢不止一个数量级。但一般来说,编程语言的开发效率比代码的执行速度更重要,python能使用很多扩展工具包,而这些工具包都是用执行速度比较快的语言开发的,如科学计算工具包Numpy就比python本身内置的计算方法快很多,也因为学习python编写代码更容易,使得python在数据科学领域有长足的应用。但python官方并没有忽略python代码执行速度慢的缺点,于2020年提出了改进性能的 Shannon 计划,按这份计划的要求,python代码未来的执行速度要比当年发布的版本提高5倍以上,并且确定在更快地 CPython的基础上进行改进。

在官方声明文件 PEP 659中,描述了一个个性化的自适应解释器,其主要思想就是在提高代码执行速度的同时,对代码中经常出现的一些操作进行优化,且转换后的python二进制代码可以即时做出适应或改变,这和JIT(Just-in-time)编译的理念类似。众所周知,python代码在运行之前要被解释器编译为二进制代码,而二进制代码是由更多的基本指令组成,这些指令要比规范的python代码更多,因此每一行python代码都会转换为几个二进制代码语句。针对这个问题,看看下面的示例:
>>> def feet_to_meters(feet):
... return 0.3048 * feet
...
上面这个函数,可以使用 dis 对该函数做反汇编处理,
>>> import dis
>>> dis.dis(feet_to_meters)
1 0 RESUME 0
2 2 LOAD_CONST 1 (0.3048)
4 LOAD_FAST 0 (feet)
6 BINARY_OP 5 (*)
10 RETURN_VALUE
上面是运行 dis.dis 函数后的结果, 可以看出每一行都是一个二进制代码指令,指令信息由五列,包括行号、字节地址、操作代码名称、操作参数和圆括号中的参数解释。对于编写python代码来说,不需要了解这样的细节,这里拿出来只是为了说明python运行的内在机制。提高python代码的执行速度所在的环节就在python代码转换并生成二进制代码这一步,获得了代码运行过程中可以被优化的指令,就可以用自适应指令来替代。当一个函数被调用的次数确定了,加速机制即刻实施。
可以进一步探究一下解释器如何通过调用 dis() 函数和 设置 adaptive 参数,来实现与二进制代码的自适应。看看下面的例子,定义一个函数,调用几次,其参数是一个浮点数:
>>> def feet_to_meters(feet):
... return 0.3048 * feet
...
>>> feet_to_meters(1.1)
0.33528
>>> feet_to_meters(2.2)
0.67056
>>> feet_to_meters(3.3)
1.00584
>>> feet_to_meters(4.4)
1.34112
>>> feet_to_meters(5.5)
1.6764000000000001
>>> feet_to_meters(6.6)
2.01168
>>> feet_to_meters(7.7)
2.34696
接下来,看看 函数 feet_to_meters()运行7次后,该函数的二进制代码指令信息是什么样,
>>> import dis
>>> dis.dis(feet_to_meters, adaptive=True)
1 0 RESUME 0
2 2 LOAD_CONST 1 (0.3048)
4 LOAD_FAST 0 (feet)
6 BINARY_OP 5 (*)
10 RETURN_VALUE
从上面的运行结果中,看出有什么特别之处,函数feet_to_meters带参数和前面示例中不带参数的情况下,二进制代码的指令信息相同。接下来再运行一次,这是第8次运行该函数,看看该函数的二进制代码的指令信息发生了什么变化:
>>> feet_to_meters(8.8)
2.68224
>>> dis.dis(feet_to_meters, adaptive=True)
1 0 RESUME_QUICK 0
2 2 LOAD_CONST__LOAD_FAST 1 (0.3048)
4 LOAD_FAST 0 (feet)
6 BINARY_OP_MULTIPLY_FLOAT 5 (*)
10 RETURN_VALUE
可以看出,函数feet_to_meters() 被运行8次之后,原来的二进制指令已经被个性化的指令替代,如 BINARY_OP 被 BINARY_OP_MULTIPLY_FLOAT 替代,这能更快地计算两个浮点数相乘。即使参数 feet 是浮点数的情况下,feet_to_meters()函数被实施了优化,但是该函数在调用其他类型的参数时依然执行替换后的二进制指令;尽管内在操作发生了改变,但python代码的执行和python 3.11之前的版本完全一样。
再看看下面的例子,连续调用该函数多次,参数feet 用整数:
>>> for feet in range(52):
... feet_to_meters(feet)
...
>>> dis.dis(feet_to_meters, adaptive=True)
1 0 RESUME_QUICK 0
2 2 LOAD_CONST__LOAD_FAST 1 (0.3048)
4 LOAD_FAST 0 (feet)
6 BINARY_OP_MULTIPLY_FLOAT 5 (*)
10 RETURN_VALUE
python解释器还是意在完成两个浮点数相乘这一操作,源代码转换为二进制代码指令集都是相同的。以上示例通过不同的参数类型表明,python 3.11在改变已有代码时无需考虑代码的执行速度。
python 3.11是在更快的CPython项目的基础上改进代码执行速度,但CPython有两个重要的原则:
- 该项目的任何重大改变都不会引入到python,即python可以基于CPython改善执行速度,但不是说CPython的所有重大改进都会被引入到python
- CPython的绝大部分代码都将会改进
这一点上有一个标准:CPython 3.11 比 CPython 3.0 平均快25%。因此,使用python 3.11应该更关注如何改进代码本身,而不是CPython 3.11 和 python 3.11 的标准之别。
CPython项目一直都在持续改进,其中有几个方面的优化将在2023年10月发布的Python 3.12中引入,该项目非常庞大,将涉及到python的所有方面。个性化的自适应解释器只是改进的内容之一。
发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/76267
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!