Dlaczego Kod Pythona działa szybciej w funkcji?
def main():
for i in xrange(10**8):
pass
main()
Ten fragment kodu w Pythonie działa w (Uwaga: czas jest wykonywany za pomocą funkcji czasu w BASH w Linuksie.)
real 0m1.841s
user 0m1.828s
sys 0m0.012s
Jeśli jednak pętla for nie jest umieszczona wewnątrz funkcji,
for i in xrange(10**8):
pass
Potem działa przez znacznie dłuższy czas:
real 0m4.543s
user 0m4.524s
sys 0m0.012s
Dlaczego tak jest? 3 answers
Możesz zapytać dlaczego przechowywanie zmiennych lokalnych jest szybsze niż globalne. To jest szczegóły implementacji CPython.
Pamiętaj, że CPython jest kompilowany do kodu bajtowego, który jest uruchamiany przez interpreter. Gdy funkcja jest kompilowana, zmienne lokalne są przechowywane w tablicy o stałym rozmiarze ( Nie A dict
), a nazwy zmiennych są przypisywane do indeksów. Jest to możliwe, ponieważ nie można dynamicznie dodawać zmiennych lokalnych do funkcji. Następnie pobranie zmiennej lokalnej jest dosłownie wyszukiwanie wskaźnika do listy i zwiększenie liczby refcount na PyObject
, co jest trywialne.
Porównujemy to z globalnym wyszukiwaniem (LOAD_GLOBAL
), które jest prawdziwym wyszukiwaniem dict
zawierającym hash i tak dalej. Nawiasem mówiąc, dlatego musisz określić global i
, Jeśli chcesz, aby była globalna: jeśli kiedykolwiek przypiszesz zmienną wewnątrz zakresu, kompilator wyda STORE_FAST
s dla jej dostępu, chyba że każesz jej tego nie robić.
Przy okazji, globalne poszukiwania są nadal dość zoptymalizowane. Atrybut lookups foo.bar
to naprawdę powolne!
Oto mała ilustracja {[23] } na temat lokalnej zmiennej wydajności.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 14:00:19
Wewnątrz funkcji kod bajtowy to
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
Na najwyższym poziomie kod bajtowy to
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
Różnica polega na tym, że STORE_FAST
jest szybszy (!) niż STORE_NAME
. Dzieje się tak dlatego, że w funkcji i
jest lokalna, ale na najwyższym poziomie jest globalna.
Aby sprawdzić bajt kodu, użyj dis
Moduł . Udało mi się zdemontować funkcję bezpośrednio, ale aby zdemontować kod toplevel musiałem użyć compile
builtin .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-28 09:29:12
Oprócz czasu przechowywania zmiennych lokalnych/globalnych, przewidywanie kodu opcode sprawia, że funkcja jest szybsza.
Jak wyjaśniają Pozostałe odpowiedzi, funkcja wykorzystuje kod opcode STORE_FAST
W pętli. Oto kod bajtowy 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
Normalnie, gdy program jest uruchomiony, Python wykonuje każdy kod opcode jeden po drugim, śledząc stos a i preformując inne kontrole na ramce stosu po wykonaniu każdego kodu opcode. Opcode prediction oznacza, że w niektórych przypadki Python jest w stanie przeskoczyć bezpośrednio do następnego kodu, unikając w ten sposób niektórych kosztów.
W Tym Przypadku, za każdym razem, gdy Python zobaczy FOR_ITER
(górną część pętli), "przewidzi", że STORE_FAST
jest następnym kodem, który musi wykonać. Python następnie zagląda na następny kod opcode i, jeśli przewidywanie było poprawne, przeskakuje prosto do STORE_FAST
. Powoduje to ściśnięcie dwóch kodów opcodes w jeden kod opcodes.
Z drugiej strony, kod STORE_NAME
jest używany w pętli na globalnym poziom. Python *nie* tworzy podobne prognozy, gdy widzi ten kod opcode. Zamiast tego musi wrócić na szczyt pętli ewaluacyjnej, co ma oczywiste konsekwencje 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
plik ("silnik" wirtualnej maszyny Pythona):
Niektóre opcody mają tendencję do łączenia się w pary, dzięki czemu możliwe jest przewiduj drugi kod kiedy pierwszy jest uruchomiony. Na przykład,
GET_ITER
jest często poFOR_ITER
. OrazFOR_ITER
jest często następnieSTORE_FAST
lubUNPACK_SEQUENCE
.Weryfikacja prognozy kosztuje pojedynczy szybki test rejestru zmienna względem stałej. Jeśli parowanie było dobre, to własnej gałęzi wewnętrznej procesora ma duże prawdopodobieństwo sukces, co skutkuje niemal zerowym przejściem na następny kod. Pomyślna prognoza oszczędza podróż przez eval-loop w tym jego dwie nieprzewidywalne gałęzie, test
HAS_ARG
i przełącznik. W połączeniu z wewnętrzną gałęzią procesora, pomyślnePREDICT
powoduje, że oba opcody działają tak, jakby były one pojedynczym nowym kodem opcode z połączonymi ciałami.
Możemy zobaczyć w kodzie źródłowym dla FOR_ITER
opcode dokladnie gdzie PREDYKCJA dla STORE_FAST
jest wykonana:
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
Funkcja PREDICT
rozszerza się do if (*next_instr == op) goto PRED_##op
tzn. po prostu przeskakujemy na początek przewidywanego kodu 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 jest gotowy do wykonania. Python kontynuuje przez iterable aż do końca, dokonując pomyślnego przewidywania za każdym razem.
Strona wiki Pythona zawiera więcej informacji o tym, jak działa wirtualna maszyna Cpythona.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-28 09:16:31