Окрім локальних / глобальних змінних часом зберігання, прогнозування опкоду робить функцію швидшою.
Як пояснюються інші відповіді, функція використовує 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.