TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #892 10.4K
Находки в опенсорсе Как работает диспатчеризация байткода внутри VM? Computed GOTOs Многие из вас знают, что внутри питона есть большой switch-case, который выполняется в цикле, он находит нужный байткод и выполняет его. Выглядит оно примерно как-то так: #define LOAD_CONST…
Как работает диспатчеризация байткода внутри VM? Tail call dispatch

(перед прочтением – советую прочитать пост ^ про computed goto)

https://github.com/python/cpython/pull/128718

В CPython новая оптимизация, которая дает где-то 5% производительности. Я уже рассказывал, что такое computed goto, но теперь есть еще более прикольная и быстрая штука для диспатчеризации байткода.

То есть: вызов следующего опкода в Python коде будет быстрее, а значит – все программы просто бесплатно станут быстрее.

(не путать с tail call оптимизацией для рекурсии)

Как работает?

Сначала делаем два макроса, которые будут устанавливать нужные атрибуты для компилятора.
Пока только [[clang::musttail]], про поддержку компиляторов будет ниже. Зачем нужен preserve_none – можно прочитать тут.


#ifdef Py_TAIL_CALL_INTERP
// Note: [[clang::musttail]] works for GCC 15, but not __attribute__((musttail)) at the moment.
# define Py_MUSTTAIL [[clang::musttail]]
# define Py_PRESERVE_NONE_CC __attribute__((preserve_none))

// Для простоты еще два макроса, просто слишком часто повторяется код:
#define TAIL_CALL_PARAMS _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, _Py_CODEUNIT *next_instr, int oparg
#define TAIL_CALL_ARGS frame, stack_pointer, tstate, next_instr, oparg


Далее, создаем новый тип колбеков для "tail-call функций":


Py_PRESERVE_NONE_CC typedef PyObject* (*py_tail_call_funcptr)(TAIL_CALL_PARAMS);


Важный шаг: меняем дефиницию макросов TARGET и DISPATCH_GOTO по аналогии с computed gotos.
Теперь тут будет:


# define TARGET(op) Py_PRESERVE_NONE_CC PyObject *_TAIL_CALL_##op(TAIL_CALL_PARAMS)
# define DISPATCH_GOTO() \
do { \
Py_MUSTTAIL return (INSTRUCTION_TABLE[opcode])(TAIL_CALL_ARGS); \
} while (0)


То есть теперь по факту – все TARGET макросы будут разворачиваться в отдельные функции:


Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);


В теле такой функции будет очень мало кода – только обработка ее логики. Пример для BINARY_OP.
Вот они, для каждого опкода:


Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LIST_APPEND(TAIL_CALL_PARAMS);
// ...


И мы так же ищем следующий опкод в INSTRUCTION_TABLE[opcode], но теперь мы вызываем функцию, которая там лежит в DISPATCH_GOTO. То есть теперь – у нас теперь есть буквально:


callbacks = {
'BINARY_OP': lambda *args, **kwargs: ...
'LIST_APPEND': lambda *args, **kwargs: ...
}

callbacks[opcode](*args, **kwargs)


И во время конфигурации сборки питона – проверяем, поддерживает ли наш компилятор такое.

Так почему быстрее?

Теперь – все функции маленькие, их удобно оптимизировать. Вот тут уточнение из комментов.

Потому что для [[mustail]] не создается дополнительный стекфрейм, asm получается более оптимальным. Я подготовил для вас пример: https://godbolt.org/z/T3Eqnd33e (для таких простых случаев -O2 более чем работает, но все равно)

Для вызова функции foo(int a) было:


mov edi, dword ptr [rbp - 4]
call foo(int)@PLT
add rsp, 16
pop rbp
ret


Стало:


mov edi, dword ptr [rbp - 4]
pop rbp
jmp foo(int)@PLT


call -> jmp!

Статья по теме от автора __attribute__((musttail))

Ограничения

Пока что данное поведение скрыто за флагом --with-tail-call-interp, по-умолчанию в 3.14 оно работать не будет. В следующих версиях – включат по-умолчанию для всех.

Есть еще и техническое ограничение. Пока что такой __attribute__ компилятора поддерживает только clang в llvm>=19 на x86-64 и AArch64. В следующем релизе gcc, вроде бы, завезут поддержку

Ну и последнее: пока проверили только перформанс с Profile Guided Optimization (pgo), сколько будет без него – еще не мерили. Сначала вообще заявили прирост на 15%, но потом нашли баг в llvm, который замедлял код без такой фичи.

Да, у нас тут с вами душный канал, где нет ярких заголовков :(

Обсуждение: чего ждете от 3.14 больше всего?

| Поддержать | YouTube | GitHub | Чат |
  • 👍 62
  • 🤯 8
  • ❤ 5
  • 🤡 2
  • 🔥 1
  • 👏 1
More from @opensource_findings
  1. Sep 25, 2026Монадические выражения в Python. Наконец-то! Продолжаем разговор про интересные PEPы. Пого…
  2. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  3. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  4. Sep 1, 2026Post #985
  5. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
  6. Aug 18, 2026Как AWS у опенсорсера пакеты отжимал Нерегулярная рубрика "посмотрите, что творится!". Дан…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →