背景
2020 年 11 月 Python 之父(Guido)加入微软,按他个人的说法是自己的退休生活太无聊了。站在现在的这个时间点,一年半的时间过去了。Guido 的工作中对开发者来说感知最强的应该数 Cpython 解释器的性能优化了。
技术路线
这次的优化版本会与 Python-3.11 一起推出,也就是说想要体验更好的性能,我们要升级一下解释器。
整个 Python-3.11 的优化思路是解释器识别出热点代码块,根据识别出来的内容生成逻辑上一致的,但是执行效率更高的专用代码,用新的代码替换掉老的代码。
通过“识别”、“生成”、“替换” 这三步曲来提升执行性能,由于“识别”就是基于场景的,也就是说并不是所有的 Python 指令在 Python-3.11 上都得到了性能提升。
各个场景下的提升情况
根据官方的预发布文档,各个不同场景下的优化成果如下。
优化项 | 提升百分比 | 语法样例 |
---|---|---|
二元运算符 | 10% | 1 + 1 |
索引读取 | 10% ~ 25% | names[0] |
索引赋值 | 10% ~ 25% | names[0] = 'tom' |
函数调用 | 20% | print("abc") |
对象方法加载 | 10% ~ 20% | o.meth() |
对象属性赋值 | 2% | o.attr = 'abc' |
拆包 | 8% | *seq |
从官方给的优化项目来看,最高的也就 25% ,是不是说我的解释器升级到 Python-3.11 之后最多也就能提升 25% 呢?由于程序在各个步骤之间会依赖关系所以最终表现在概率上是用乘法来算的。优化前可能是 1 * 1 * 1 ,所以都按提升 20% 来计算优化后就是 0.8 * 0.8 * 08 ~=
0.512 。
下面我们看一个只优化函数调用就能提升性能 100% 的例子。
函数调用场景为例子
既然是要验证官方对函数调用的优化,那递归就是函数调用最好的例子,那我就写一个经典的递归算法吧。
#!/usr/bin/env python3
from time import time
from dis import dis
def fib(index:1):
"""
计算斐波那契数列的第 n 位。
index:
-------
int
return:
-------
int
"""
if index == 1 or index == 2:
return 1
else:
return fib(index - 1) + fib(index - 2)
if __name__ == "__main__":
# 统计耗时
start_at = time()
fib(34)
stop_at = time()
print(f"total cost {stop_at - start_at}")
# 打印 fib 函数的字节码
print("-" * 48)
print(dis(fib))
print("-" * 48)
Python-3.x 各个版本的耗时情况
先上结论!!!
版本 | 耗时 |
---|---|
python-3.10 | 1.67s |
python-3.11 | 0.75s |
用 Python-3.10 这个解释器执行代码观察平均耗时
python3.10 main.py
total cost 1.672095012664795
------------------------------------------------
19 0 LOAD_FAST 0 (target)
2 LOAD_CONST 1 (1)
4 COMPARE_OP 2 (==)
6 POP_JUMP_IF_TRUE 8 (to 16)
8 LOAD_FAST 0 (target)
10 LOAD_CONST 2 (2)
12 COMPARE_OP 2 (==)
14 POP_JUMP_IF_FALSE 10 (to 20)
20 >> 16 LOAD_CONST 1 (1)
18 RETURN_VALUE
22 >> 20 LOAD_GLOBAL 0 (fib)
22 LOAD_FAST 0 (target)
24 LOAD_CONST 1 (1)
26 BINARY_SUBTRACT
28 CALL_FUNCTION 1
30 LOAD_GLOBAL 0 (fib)
32 LOAD_FAST 0 (target)
34 LOAD_CONST 2 (2)
36 BINARY_SUBTRACT
38 CALL_FUNCTION 1
40 BINARY_ADD
42 RETURN_VALUE
用 Python-3.11 这个解释器执行代码观察平均耗时
python3.11 main.py
total cost 0.7522010803222656
------------------------------------------------
6 0 RESUME 0
19 2 LOAD_FAST 0 (index)
4 LOAD_CONST 1 (1)
6 COMPARE_OP 2 (==)
12 POP_JUMP_FORWARD_IF_TRUE 6 (to 26)
14 LOAD_FAST 0 (index)
16 LOAD_CONST 2 (2)
18 COMPARE_OP 2 (==)
24 POP_JUMP_FORWARD_IF_FALSE 2 (to 30)
20 >> 26 LOAD_CONST 1 (1)
28 RETURN_VALUE
22 >> 30 LOAD_GLOBAL 1 (NULL + fib)
42 LOAD_FAST 0 (index)
44 LOAD_CONST 1 (1)
46 BINARY_OP 10 (-)
50 PRECALL 1
54 CALL 1
64 LOAD_GLOBAL 1 (NULL + fib)
76 LOAD_FAST 0 (index)
78 LOAD_CONST 2 (2)
80 BINARY_OP 10 (-)
84 PRECALL 1
88 CALL 1
98 BINARY_OP 0 (+)
102 RETURN_VALUE
分析函数调用场景的优化方式
为什么换了个解释器之后突然就变快了?不同的解决器生成的字节码不一样,Python-3.11 识别到整个 fib 函数都是热点,对其做了内联化处理。从字节码上来就是由之前传统的
CALL_FUNCTION
优化成了
PRECALL
CALL
参考文档
https://docs.python.org/3.11/whatsnew/3.11.html
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有