Oprócz lokalnych / globalnych czasów przechowywania zmiennych, przewidywanie kodu operacyjnego przyspiesza działanie.
Jak wyjaśniają inne odpowiedzi, funkcja wykorzystuje STORE_FAST
w pętli kod operacji. Oto kod bajtowy dla pętli funkcji:
>> 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
Zwykle po uruchomieniu programu Python wykonuje kolejno każdy kod operacji, śledząc stos i wykonując inne kontrole w ramce stosu po wykonaniu każdego kodu operacji. Prognozowanie kodu operacyjnego oznacza, że w niektórych przypadkach Python może przejść bezpośrednio do następnego kodu operacyjnego, unikając w ten sposób części tego narzutu.
W takim przypadku za każdym razem, gdy Python widzi FOR_ITER
(górna część pętli), „przewiduje”, że STORE_FAST
jest to kolejny kod operacji, który musi wykonać. Python następnie zerknie na następny kod operacji, a jeśli prognoza była poprawna, skacze prosto do STORE_FAST
. To powoduje ściśnięcie dwóch kodów operacyjnych w jeden kod operacji.
Z drugiej strony STORE_NAME
opcode jest używany w pętli na poziomie globalnym. Python * nie * robi podobnych prognoz, gdy widzi ten kod operacji. Zamiast tego musi wrócić do górnej części pętli oceny, co ma oczywiste implikacje dla prędkości, z jaką pętla jest wykonywana.
Aby podać więcej szczegółów technicznych na temat tej optymalizacji, oto cytat z ceval.c
pliku („silnik” maszyny wirtualnej Pythona):
Niektóre kody są zwykle parami, dzięki czemu można przewidzieć drugi kod podczas pierwszego uruchomienia. Na przykład
GET_ITER
często występuje po nim FOR_ITER
. I FOR_ITER
często następuje po nimSTORE_FAST
lub UNPACK_SEQUENCE
.
Weryfikacja prognozy kosztuje pojedynczy szybki test zmiennej rejestru na stałą. Jeśli parowanie było dobre, wówczas predykcja wewnętrznej gałęzi procesora ma duże prawdopodobieństwo powodzenia, co powoduje prawie zerowe przejście do następnego kodu operacji. Udane przewidywanie oszczędza podróż przez pętlę eval, w tym jej dwie nieprzewidywalne gałęzie, HAS_ARG
test i obudowę przełącznika. W połączeniu z przewidywaniem wewnętrznej gałęzi procesora, sukces PREDICT
powoduje, że dwa kody operacyjne działają tak, jakby były pojedynczym nowym kodem operacyjnym z połączonymi ciałami.
Widzimy w kodzie źródłowym FOR_ITER
opcode dokładnie, gdzie dokonano prognozy 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
Funkcja rozszerza się if (*next_instr == op) goto PRED_##op
, czyli po prostu przeskoczyć do początku przewidywanej opcode. W tym przypadku przeskakujemy tutaj:
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;
Zmienna lokalna jest teraz ustawiona i następny kod operacji jest gotowy do wykonania. Python kontynuuje iterowalność aż do końca, dzięki czemu za każdym razem dokonuje udanej prognozy.
Strona wiki Python zawiera więcej informacji na temat działania maszyny wirtualnej CPython.