Помимо времени хранения локальных / глобальных переменных, предсказание кода операции делает функцию быстрее.
Как объясняют другие ответы, функция использует 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 выполняет каждый код операции один за другим, отслеживая стек и предварительно формируя другие проверки в кадре стека после выполнения каждого кода операции. Предсказание кода операции означает, что в некоторых случаях Python может перейти непосредственно к следующему коду операции, что позволяет избежать некоторых из этих издержек.
В этом случае каждый раз, когда Python видит FOR_ITER
(верхнюю часть цикла), он «предсказывает», какой STORE_FAST
следующий код операции он должен выполнить. Затем Python просматривает следующий код операции и, если прогноз был верным, он сразу переходит к STORE_FAST
. Это приводит к сжатию двух кодов операций в один код операции.
С другой стороны, STORE_NAME
код операции используется в цикле на глобальном уровне. Python * не * делает подобные прогнозы, когда видит этот код операции. Вместо этого он должен вернуться к началу цикла оценки, что имеет очевидные последствия для скорости выполнения цикла.
Чтобы дать некоторые технические подробности об этой оптимизации, вот цитата из ceval.c
файла («движок» виртуальной машины Python):
Некоторые коды операций имеют тенденцию приходить парами, что позволяет прогнозировать второй код при запуске первого. Например,
GET_ITER
часто сопровождается FOR_ITER
. И FOR_ITER
часто сопровождаетсяSTORE_FAST
или UNPACK_SEQUENCE
.
Проверка прогноза стоит одного высокоскоростного теста переменной регистра с константой. Если спаривание было хорошим, то предикатирование собственной внутренней ветви процессора имеет высокую вероятность успеха, что приводит к почти нулевому переходу на следующий код операции. Успешное предсказание спасает путешествие через цикл eval, включая две его непредсказуемые ветви: HAS_ARG
тест и случай переключения. В сочетании с внутренним предсказанием ветвления процессора успех PREDICT
приводит к тому, что два кода операции запускаются так, как если бы они были одним новым кодом операции с объединенными телами.
Мы можем видеть в исходном коде для FOR_ITER
кода операции именно то, где STORE_FAST
сделан прогноз для :
case FOR_ITER: // the FOR_ITER opcode case
v = TOP();
x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
if (x != NULL) {
PUSH(x); // put x on top of the stack
PREDICT(STORE_FAST); // predict STORE_FAST will follow - success!
PREDICT(UNPACK_SEQUENCE); // this and everything below is skipped
continue;
}
// error-checking and more code for when the iterator ends normally
PREDICT
Функция расширяется, if (*next_instr == op) goto PRED_##op
то есть мы просто перейти к началу прогнозируемого опкода. В этом случае мы прыгаем здесь:
PREDICTED_WITH_ARG(STORE_FAST);
case STORE_FAST:
v = POP(); // pop x back off the stack
SETLOCAL(oparg, v); // set it as the new local variable
goto fast_next_opcode;
Теперь установлена локальная переменная, и следующий код операции готов к выполнению. Python продолжает итерацию до конца, каждый раз делая успешный прогноз.
Вики - странице Python имеет больше информации о том , как работает виртуальная машина CPython в.