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

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被替换为PRECALLCALL操作码)。

我很不确定这是否是性能变化的原因。所以我正在寻找一个参考 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 中运行得更快

use*_*ica 6

“新增内容”页面中有一个很大的部分标记为“更快的运行时间”。看起来这里加速最可能的原因是PEP 659,这是 JIT 优化的第一步(也许不完全是 JIT编译,但绝对是 JIT优化)。

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

您想要来源参考,所以这里有一个。该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)中的代码。

  • 为了好玩,[此处](https://i.stack.imgur.com/gQWJV.png) 是[`specialist`](https://github.com/brandtbucher/specialist) 为此脚本显示的内容。 (4认同)