Почему Python-код в теле функций работает быстрее?

Наверняка вам приходилось слышать, что локальные переменные в Python работают быстрее, чем глобальные. Это особенность реализации CPython.

1. Локальные переменные хранятся в фиксированном массиве

Как известно, CPython компилирует исходный код в байт-код, который выполняет интерпретатор. Когда функция компилируется, локальные переменные сохраняются в массиве фиксированного размера, в котором переменным соответствуют индексы. По этой причине программист не может динамически добавлять локальные переменные в функции.

Таким образом, операция извлечения локальной переменной сводится к поиску указателя в списке и увеличению счетчика ссылок (RefCount) на PyObject (объект Python на уровне языка Си; любое значение, с которым вы можете работать в Python — PyObject в языке Си).

2. Глобальные переменные извлекаются из словаря

Сравните сложность извлечения локальной переменной с глобальным поиском (LOAD_GLOBAL), который предполагает поиск по словарю с участием хэша. Кстати, поэтому программистам необходимо указывать инструкцию global, если требуется изменить переменные, находящиеся на верхнем уровне модуля: если присвоить значение глобальной переменной внутри области видимости, компилятор выдаст ошибку STORE_FAST при попытке обратиться к ней, если вы явно не укажете, что этого делать не нужно.

Тайминги различных способов доступа к переменным

Py3.2   Py2.7   Операция
-----   -----   -----------------------------------------
0.108   0.182   чтение локальной переменной 
0.149   0.242   чтение нелокальной переменной
0.216   0.310   чтение глобальной переменной
0.364   0.450   чтение встроенной переменной
0.534   0.629   чтение переменной класса
0.682   0.808   чтение переменной экземпляра класса
    -   0.903   чтение несвязанного метода
0.779   0.904   чтение связанного метода

0.128   0.178   присваивание локальной переменной 
0.192       -   присваивание нелокальной переменной
0.442   0.576   присваивание глобальной переменной
2.134   2.414   присваивание переменной класса
0.967   1.177   присваивание переменной экземпляра класса

Примечания

  1. Тайминги, связанные с переменными и методами класса, даны для классов нового стиля.
  2. Тестирование проводилось с использованием версий Python 3.2.1 и Python 2.7.2. Оба интерпретатора — 64-разрядные Darwin-сборки. Были запущены на процессоре Intel Core 2 Duo 2,4 ГГц.