Глобальный код в два раза медленнее локального
Почему код на уровне модуля исполняется в два раза медленнее такого же кода в функции?
global.py |
local.py |
|---|---|
|
|
| Десять секунд: | Против пяти: |
|
|
Проверено на CPython 3.7, 3.10, 3.12.
Ответы (1 шт):
Сравним байт коды. Я оставил только то что относится к циклу. Тела циклов начинаются инструкцией FOR_ITER и завершаются JUMP_BACKWARD. В телах выделены отличающиеся инструкции:
global.py |
local.py |
|---|---|
|
|
|
|
В глобальном коде используются инструкции LOAD_NAME/STORE_NAME, в локальном LOAD_FAST/STORE_FAST. Других отличий в теле цикла нет.
Глобальная переменная видна в других модулях и может быть изменена из других модулей. Всякий раз когда интерпретатор читает её он обращается к словарю globals (он содержит все символы данного модуля) и получает значение переменной по её имени. При записи тоже надо отыскать переменную по имени и обновить её значение.
Напротив, локальная переменная не видна нигде кроме своей родной функции. Она не может быть изменена другим кодом по имени. Интерпретатор оптимизирует доступ к ней: он хранит все локальные переменные в массиве и обращается к ним по индексам.
Пара LOAD_NAME/STORE_NAME работает со словарём globals. Пара LOAD_FAST/STORE_FAST работает с массивом, который условно можно назвать locals. Обращения к массиву быстрее, локальные инструкции быстрее, что делает локальный код в данном случае в два раза быстрее.
P.S. В других языках доступ к глобальным переменным оптимизируется аналогично доступу к локальным. Для этого нужно что набор глобальных имён был бы неизменным во время работы программы. Тогда их можно перечислить при компиляции, каждому имени сопоставить индекс и работать с массивом вместо словаря. Но в Питоне модули позволяют добавлять и удалять символы во время работы программы, а это затрудняет оптимизацию. С другой стороны в функциях состав переменных постоянен, что позволяет работать быстро.