Почему код Python работает быстрее в функции?
def main():
    for i in xrange(10**8):
        pass
main()
этот фрагмент кода в Python работает (Примечание: синхронизация выполняется с помощью функции времени в BASH в Linux.)
real    0m1.841s
user    0m1.828s
sys     0m0.012s
однако, если цикл for не помещается в функцию,
for i in xrange(10**8):
    pass
затем он работает в течение гораздо более длительного времени:
real    0m4.543s
user    0m4.524s
sys     0m0.012s
Почему это?
3 ответа:
вы можете спросить почему хранить локальные переменные быстрее, чем глобальные. Это деталь реализации CPython.
помните, что CPython компилируется в байт-код, который запускает интерпретатор. При компиляции функции локальные переменные хранятся в массиве фиксированного размера (не a
dict) и имена переменных присваиваются индексы. Это возможно потому, что вы не можете динамически добавлять локальные переменные функции. После получения местных переменная-это буквально поиск указателя в списке и увеличение количества ссылок наPyObjectчто тривиально.сравните это с глобальным поиском (
LOAD_GLOBAL), который является истиннымdictпоиск по хэш и так далее. Кстати, именно поэтому вам нужно указатьglobal iесли вы хотите, чтобы он был глобальным: если вы когда-либо назначаете переменную внутри области, компилятор выдастSTORE_FASTs для его доступа, если вы не скажете ему не делать.кстати, глобальные поиски все еще довольно оптимизировано. Поиск атрибутов
foo.barэто действительно медленные!вот маленький иллюстрации о локальной переменной эффективности.
внутри функции байт-код
2 0 SETUP_LOOP 20 (to 23) 3 LOAD_GLOBAL 0 (xrange) 6 LOAD_CONST 3 (100000000) 9 CALL_FUNCTION 1 12 GET_ITER >> 13 FOR_ITER 6 (to 22) 16 STORE_FAST 0 (i) 3 19 JUMP_ABSOLUTE 13 >> 22 POP_BLOCK >> 23 LOAD_CONST 0 (None) 26 RETURN_VALUEна верхнем уровне байт-код
1 0 SETUP_LOOP 20 (to 23) 3 LOAD_NAME 0 (xrange) 6 LOAD_CONST 3 (100000000) 9 CALL_FUNCTION 1 12 GET_ITER >> 13 FOR_ITER 6 (to 22) 16 STORE_NAME 1 (i) 2 19 JUMP_ABSOLUTE 13 >> 22 POP_BLOCK >> 23 LOAD_CONST 2 (None) 26 RETURN_VALUEразница в том, что
STORE_FASTбыстрее (!), чемSTORE_NAME. Это потому, что в функцииiявляется локальным, но на верхнем уровне это глобальный.чтобы проверить байт-код, используйте
disмодуль. Я смог разобрать функцию напрямую, но для разборки кода верхнего уровня мне пришлось использоватьcompilebuiltin.
помимо локальных / глобальных переменных времени хранения,прогнозирование в коде делает функцию быстрее.
как объясняют другие ответы, функция использует
STORE_FASTоперации в цикле. Вот байт-код для цикла функции:>> 13 FOR_ITER 6 (to 22) # get next value from iterator 16 STORE_FAST 0 (x) # set local variable 19 JUMP_ABSOLUTE 13 # back to FOR_ITERобычно при запуске программы Python выполняет каждый код операции один за другим, отслеживая стек a и предварительно формируя другие проверки на кадре стека после выполнения каждого кода операции. Код операции предсказание означает, что в некоторых случаях Python может перейти непосредственно к следующему коду операции, тем самым избегая некоторых из этих накладных расходов.
в этом случае, каждый раз, когда Python видит
FOR_ITER(в верхней части цикла), он будет "предсказать", чтоSTORE_FAST- Это следующий код операции, который он должен выполнить. Затем Python заглядывает в следующий код операции и, если прогноз был правильным, он переходит прямо кSTORE_FAST. Это приводит к сжатию двух кодов операций в один код операции.С другой стороны, элемент
STORE_NAMEкод операции используется в цикле на глобальном уровне. Питон делает *не* сделать аналогичные прогнозы, когда он видит этот код операции. Вместо этого он должен вернуться к началу цикла оценки, который имеет очевидные последствия для скорости, с которой выполняется цикл.чтобы дать более подробную техническую информацию об этой оптимизации, вот цитата из
ceval.cфайл ("движок" виртуальной машины Python):некоторые опкоды, как правило, приходят в парах, что позволяет предсказать второй код при запуске первого. Например,
GET_ITERчасто сопровождаетсяFOR_ITER. ИFOR_ITERчасто далее следуетSTORE_FASTилиUNPACK_SEQUENCE.проверка прогноза стоит одного высокоскоростного теста регистра переменный против постоянного. Если спаривание было хорошим, то предикация собственной внутренней ветви процессора имеет высокую вероятность успех, в результате почти нулевыми затратами, переход к следующий код операции. Успешное предсказание сохраняет поездку через eval-цикл включая его две непредсказуемые ветви,