为什么这个特定的代码在Python 3.11中运行得更快



我在一个名为benchmark.py的Python文件中有以下代码。

source = """
for i in range(1000):
a = len(str(i)) 
"""
import timeit
print(timeit.timeit(stmt=source, number=100000))

当我尝试使用多个python版本运行时,我看到了巨大的性能差异。

C:UsersUsernameDesktop>py -3.10 benchmark.py
16.79652149998583
C:UsersUsernameDesktop>py -3.11 benchmark.py
10.92280820000451

正如您所看到的,此代码在python 3.11中的运行速度比以前的python版本更快。我试图分解字节码来理解这种行为的原因,但我只能看到操作码名称的差异(CALL_FUNCTIONPRECALLCALL操作码取代(。

我不确定这是否是这次性能变化的原因。因此,我正在寻找一个引用cpython来证明其合理性的答案源代码

python 3.11字节码

0           0 RESUME                   0
2           2 PUSH_NULL
4 LOAD_NAME                0 (range)
6 LOAD_CONST               0 (1000)
8 PRECALL                  1
12 CALL                     1
22 GET_ITER
>>   24 FOR_ITER                22 (to 70)
26 STORE_NAME               1 (i)
3          28 PUSH_NULL
30 LOAD_NAME                2 (len)
32 PUSH_NULL
34 LOAD_NAME                3 (str)
36 LOAD_NAME                1 (i)
38 PRECALL                  1
42 CALL                     1
52 PRECALL                  1
56 CALL                     1
66 STORE_NAME               4 (a)
68 JUMP_BACKWARD           23 (to 24)
2     >>   70 LOAD_CONST               1 (None)
72 RETURN_VALUE

python 3.10字节码

2           0 LOAD_NAME                0 (range)
2 LOAD_CONST               0 (1000)
4 CALL_FUNCTION            1
6 GET_ITER
>>    8 FOR_ITER                 8 (to 26)
10 STORE_NAME               1 (i)
3          12 LOAD_NAME                2 (len)
14 LOAD_NAME                3 (str)
16 LOAD_NAME                1 (i)
18 CALL_FUNCTION            1
20 CALL_FUNCTION            1
22 STORE_NAME               4 (a)
24 JUMP_ABSOLUTE            4 (to 8)
2     >>   26 LOAD_CONST               1 (None)
28 RETURN_VALUE

PS:我知道python 3.11引入了一系列性能改进,但我很好奇是什么优化使此代码在python 3.11中运行得更快

;什么是新的"页面标记为";运行时间更快";。这里加速的最可能原因似乎是PEP 659,这是JIT优化的第一个开始(可能不是JIT编译,但肯定是JIT优化(。

特别是,对于lenstr的查找和调用现在绕过了许多动态机制,在绝大多数常见的情况下,内建没有被遮蔽或覆盖。解析名称的全局和内置dict查找在快速路径中被跳过,lenstr的底层C例程被直接调用,而不是经过通用函数调用处理。

你想要源代码引用,所以这里有一个。str调用将专门用于specialize_class_call:

if (tp->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) {
if (nargs == 1 && kwnames == NULL && oparg == 1) {
if (tp == &PyUnicode_Type) {
_Py_SET_OPCODE(*instr, PRECALL_NO_KW_STR_1);
return 0;
}

其中它检测到该调用是对具有1个位置参数且没有关键字的str内建的调用,并用PRECALL_NO_KW_STR_1替换相应的PRECALL操作码。字节码评估循环中对PRECALL_NO_KW_STR_1操作码的处理如下:

TARGET(PRECALL_NO_KW_STR_1) {
assert(call_shape.kwnames == NULL);
assert(cframe.use_tracing == 0);
assert(oparg == 1);
DEOPT_IF(is_method(stack_pointer, 1), PRECALL);
PyObject *callable = PEEK(2);
DEOPT_IF(callable != (PyObject *)&PyUnicode_Type, PRECALL);
STAT_INC(PRECALL, hit);
SKIP_CALL();
PyObject *arg = TOP();
PyObject *res = PyObject_Str(arg);
Py_DECREF(arg);
Py_DECREF(&PyUnicode_Type);
STACK_SHRINK(2);
SET_TOP(res);
if (res == NULL) {
goto error;
}
CHECK_EVAL_BREAKER();
DISPATCH();
}

其主要由围绕对PyObject_Str的调用(用于在对象上调用str的C例程(的一堆安全预检查和引用篡改组成。

Python 3.11除了上述功能外,还包括许多其他性能增强,包括对堆栈帧创建、方法查找、常见算术运算、解释器启动等的优化。除了I/O绑定的工作负载和大部分时间花在C库代码中的代码(如NumPy(之外,大多数代码现在应该运行得更快。

相关内容

  • 没有找到相关文章

最新更新