Адаптивный интерпретатор Python
Смотрим как работает и что дает Specializing Adaptive Interpreter в Python
В версии Python 3.11 среди прочего был представлен механизм специализированного адаптивного интерпретатора. Звучит сложно, но на самом деле все довольно понятно.
Bytecode
В процессе исполнения Python код преобразуется в так называемый байт-код - некоторое промежуточное представление между понятным нам кодом, и непонятным нам машинным кодом.
Python предоставляет удобный инструмент dis
, позволяющий посмотреть как исходный Python код будет преобразован в байт-код. Например, возьмем следующий Python код:
def add(x: float, y: float) -> float:
z = x + y
return z
Мы можем посмотреть его байт-код представление выполнив следующие команды:
import dis
dis.dis(add, adaptive=True)
Получим следующий вывод:
4 0 RESUME 0
5 2 LOAD_FAST__LOAD_FAST 0 (x)
4 LOAD_FAST 1 (y)
6 BINARY_OP 0 (+)
10 STORE_FAST__LOAD_FAST 2 (z)
6 12 LOAD_FAST 2 (z)
14 RETURN_VALUE
Сосредоточимся на одном конкретном блоке этого байт-кода:
6 BINARY_OP 0 (+)
Объясняя простым языком, BINARY_OP 0 (+)
берет два числа и складывает их.
Оптимизация "горячего" кода
Теперь, когда мы немного разобрались с байт-кодом, поговорим наконец об оптимизациях, которые были представлены в Python 3.11 в рамках специализированного адаптивного интерпретатора. Новые оптимизации нацелены на ускорение так называемого "горячего" кода - то есть участков кода, которые выполняются относительно часто.
Суть оптимизаций заключается в самом названии нового подхода: "специализированного" и "адаптивного". Под этими словами подразумевается что инструкции байт-кода способны адаптироваться согласно тому как и на каких типах они используются, подстраиваясь под конкретные сценарии работы.
Рассмотрим на примере нашей функции add
и в частности оператора сложения, BINARY_OP 0 (+)
. Если мы N раз вызовем нашу функцию с аргументами типа float
и потом посмотрим на вывод байт-кода через dis.dis
с флагом adaptive=True
, то увидим что инструкция BINARY_OP
превратилась в BINARY_OP_ADD_FLOAT
- специализированную функцию, которая оптимизирована конкретно для сложения двух чисел типа float
:
import dis
def add(x: float, y: float) -> float:
z = x + y
return z
for _ in range(7):
add(10.0, 10.2)
dis.dis(add, adaptive=True)
# 4 0 RESUME 0
# 5 2 LOAD_FAST__LOAD_FAST 0 (x)
# 4 LOAD_FAST 1 (y)
# 6 BINARY_OP_ADD_FLOAT 0 (+)
# 10 STORE_FAST__LOAD_FAST 2 (z)
# 6 12 LOAD_FAST 2 (z)
# 14 RETURN_VALUE
Почитать подробнее об этих оптимизациях можно в документе PEP 659.
Также, очень рекомендую к просмотру доклад Talks - Brandt Bucher: Inside CPython 3.11's new specializing, adaptive interpreter.