我在一个名为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_FUNCTION
被PRECALL
和CALL
操作码取代(。
我不确定这是否是这次性能变化的原因。因此,我正在寻找一个引用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优化(。
特别是,对于len
和str
的查找和调用现在绕过了许多动态机制,在绝大多数常见的情况下,内建没有被遮蔽或覆盖。解析名称的全局和内置dict查找在快速路径中被跳过,len
和str
的底层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(之外,大多数代码现在应该运行得更快。