Многие из вас знают, что внутри питона есть большой switch-case, который выполняется в цикле, он находит нужный байткод и выполняет его. Выглядит оно примерно как-то так:
#define LOAD_CONST 79
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate,
_PyInterpreterFrame *frame,
int throwflag)
{
DISPATCH_GOTO(); // разворачивается в `goto dispatch_opcode`
dispatch_opcode:
switch (opcode) {
TARGET(LOAD_CONST): // разворачивается в `case 79:`
{
frame->instr_ptr = next_instr;
next_instr += 1;
_PyStackRef value = PyStackRef_FromPyObjectNew(
GETITEM(FRAME_CO_CONSTS, oparg));
// ...
}
// ...
}
opcode = next_instr->op.code;
DISPATCH_GOTO(); // разворачивается в `goto dispatch_opcode;`
exit:
// end of cycle: success or error
}
Но, на самом деле – все не совсем так просто! Данный
switch по сути является самой горячей частью кода во всем интерпретаторе, он выполняется буквально на любое действие. Любое ускорение данного места дает ускорение всему коду на питоне. А значит – такие ускорения были придуманы.Концепт Computed GOTOs
Вводная статья на тему, кто вообще никогда о таком не слышал. Если очень кратко:
- Создаем известную в compile-time таблицу переходов, которая использует лейблы для
goto. Назовем ее opcode_targets- Вместо
switch просто используем goto *opcode_targets[opcode]- Проверяем в configure, что компилятор поддерживает такую фичу (`gcc` поддерживает,
--with-computed-gotos по-умолчанию включено)- Накручиваем DSL для виртуальной машины:
#if USE_COMPUTED_GOTOS
# define TARGET(op) TARGET_##op:
# define DISPATCH_GOTO() goto *opcode_targets[opcode]
#else
# define TARGET(op) case op: TARGET_##op:
# define DISPATCH_GOTO() goto dispatch_opcode
#endif
Итого, используя тот же DSL на макросах, благодаря флагу
USE_COMPUTED_GOTOS (который выставляется в configure) – получаем совсем другой код в _PyEval_EvalFrameDefault:
#define LOAD_CONST 79
PyObject* _Py_HOT_FUNCTION
_PyEval_EvalFrameDefault(PyThreadState *tstate,
_PyInterpreterFrame *frame,
int throwflag)
{
DISPATCH_GOTO(); // // goto *opcode_targets[opcode]
TARGET(LOAD_CONST): // TARGET_79:
{
frame->instr_ptr = next_instr;
next_instr += 1;
_PyStackRef value = PyStackRef_FromPyObjectNew(
GETITEM(FRAME_CO_CONSTS, oparg));
// ...
}
// ...
opcode = next_instr->op.code;
DISPATCH_GOTO(); // goto *opcode_targets[opcode]
exit:
// end of cycle: success or error
}
Данная реализация где-то на 15% быстрее реализации на
switch. Но для простоты все продолжают говорить, что внутри VM switch+case Узнали сегодня что-то новое? :)
| Поддержать | YouTube | GitHub |