Abd*_*P M 6 python performance cpython python-3.x python-3.11
我在一个名为 的 Python 文件中有以下代码benchmark.py:
source = """
for i in range(1000):
a = len(str(i))
"""
import timeit
print(timeit.timeit(stmt=source, number=100000))
Run Code Online (Sandbox Code Playgroud)
当我尝试运行多个 python 版本时,我发现性能存在巨大差异。
C:\Users\Username\Desktop>py -3.10 benchmark.py
16.79652149998583
C:\Users\Username\Desktop>py -3.11 benchmark.py
10.92280820000451
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这段代码在 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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
PS:我知道python 3.11引入了一系列性能改进,但我很想了解哪些优化使该代码在 python 3.11 中运行得更快
“新增内容”页面中有一个很大的部分标记为“更快的运行时间”。看起来这里加速最可能的原因是PEP 659,这是 JIT 优化的第一步(也许不完全是 JIT编译,但绝对是 JIT优化)。
特别是,在内置函数未被隐藏或覆盖的绝大多数常见情况下,查找和调用len现在str绕过了许多动态机制。用于解析名称的全局和内置字典查找在快速路径中被跳过,并且直接调用 和 的底层 C 例程len,str而不是经过通用函数调用处理。
您想要来源参考,所以这里有一个。该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;
}
Run Code Online (Sandbox Code Playgroud)
它检测到该调用是对str带有 1 个位置参数且没有关键字的内置函数的调用,并将相应的PRECALL操作码替换为PRECALL_NO_KW_STR_1. 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();
}
Run Code Online (Sandbox Code Playgroud)
它主要由一堆安全预检查和围绕对 的调用(用于调用对象的PyObject_StrC 例程)的引用调整组成。str
除上述之外,Python 3.11 还包括许多其他性能增强,包括对堆栈帧创建、方法查找、常见算术运算、解释器启动等的优化。大多数代码现在应该运行得更快,除非 I/O 密集型工作负载和大部分时间都花在 C 库代码(如 NumPy)中的代码。