Python 3.11在10月24日发布。它是Python最新版本,运行速度更快且更佳友好。在经过17个月的研发,终于到了可以使用的黄金时期。
和每个发布版本一样,Python 3.11做了大量的变更及提高。你可以通过查看文档来了解大部分内容。这里,我们会阐述最酷且最有影响力的新特性。
教程里将涵盖如下内容:
如果你想运行本教程所提供的示例文件,你需要先安装Python 3.11.
Python广为所知是一个执行性能较慢的编程语言。例如,在Python中一个普通循环要比C中循环执行要慢得多。有多种解决该缺陷的方式。对于大部分程序员来说,生产力要比代码执行时间更重要。
Python容易包装其它更快语言作为库来使用。例如,NumPy计算能力要远高于原生Python实现计算工作。配备容易开发的能力,使得Python在数据科学有强大的竞争空间。
Python仍然做了很多努力来加速Python。在2020秋天,Mark Shannon提供了几个改善Python性能的建议。该提议,称之为Shannon Plan,广为所知。旨在几个发布版本中将Python提高五倍性能。
Microsoft加入了该计划,并且资助了一个开发团队——包括Mark Shannon和Python老爹,Guido van Rossum来专门承担Faster CPython项目的工作。Python 3.11基于Faster CPython项目获得了许多性能改善。本章,我们会学习 specializing adaptive interpreter。后面章节,我们还会学习faster starup times和zero-cost exceptions。
PEP 659对specializing adaptive interpreter做了技术上的阐述。主要想法是对经常执行代码通过加速指令来加速代码。类似just-in-time(JIT)编译,但是它并不是编译期间工作,它在执行期间通过替换字节码方式来实现。
在字节码生成期间增加了一个称为quickening步骤。它将在运行时对指令进行adaptive。会对不同指令查看是否进行specialize。
quickening操作会在函数在指定调用次数才会引发。在CPython 3.11设定为8次(暂时不知道这个次数依据,有可能是通过对不同次数进行适配优化得到的值)。我们可以通过使用dis()方法并传递daptive参数来观察解释器对字节码做adapt操作过程。首先我们定义一个函数,通过执行7次并传递一个浮点值作为参数。
>>> 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()*字节码:
>>> 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
这里没什么特别支出,这里字节码内容还扔未进行adaptive操作。只在*feet_to_meters()*第8次执行时才生效:
>>> 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
现在原始的字节码进行了specialized操作。例如BINARY_OP被替换为了BINARY_OP_MULTIPLY_FLOAT,它能更快的对两个浮点数相乘。
虽然_feet_to_meters()_已经针对float参数类型进行了优化,它仍然可以在针对其它类型时进行回退原始字节码进行工作。内部操作变更了,但你的代码还是能获得之前一样的结果。
specialized指令仍然是adaptive,我们执行52次带整型参数:
>>> 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解释器仍然是否还有希望进行两个float类型操作。如果我们再多执行一次带来整型参数的feet_to_meters(),它将放弃adaptive指定,转换回unspecialized。
>>> feet_to_meters(52)
15.8496
>>> 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_ADAPTIVE 5 (*)
10 RETURN_VALUE
本例子中,字节码变更为BINARY_OP_ADAPTIVE,而非BINARY_OP_MULTIPLY_INT,是因为始终包含操作数浮点值0.3048
对整形和浮点数相乘比相同类型相乘优化来更困难,目前还没有针对该情况的specialized指令。
该例子提供了一个内部视角来观察adaptive specializing interpreter工作机制。通常我们并不需要对已经编写的代码变更就能享受该机制所带来的性能提升。
同样,也有一些参考可以通过重构代码使得更加高效specialized。Brand Bucher的specialist是一个图形化工具解释器如何分析代码。教程提供了一些优化代码示例。你也可以通过[Talk Python to Me]广播(https://talkpython.fm/episodes/show/381/python-perf-specializing-adaptive-interpreter)来了解更多
Faster CPython项目有两点重要方针:
根据基准测试,“CPython 3.11相比CPython 3.10有平均25%的速度提升”。
Faster CPython仍然做更多的努力,比并且已经在2023年10月要发布的Python3.12指定了一些优化方案。你可以follow项目的Github。
Faster CPython是一个大型工程。adaptive specializing interpreter仅仅是冰山一角,后面教程,我们还学习到两个其它优化内容:快速启动(faster startup)和零开销异常(zero-cost exceptions)