Abgesehen von lokalen / globalen variablen Speicherzeiten beschleunigt die Opcode-Vorhersage die Funktion.
Wie die anderen Antworten erklären, verwendet die Funktion den STORE_FAST
Opcode in der Schleife. Hier ist der Bytecode für die Funktionsschleife:
>> 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
Normalerweise führt Python beim Ausführen eines Programms jeden Opcode nacheinander aus, verfolgt den Stapel und führt nach Ausführung jedes Opcodes weitere Überprüfungen des Stapelrahmens durch. Opcode-Vorhersage bedeutet, dass Python in bestimmten Fällen direkt zum nächsten Opcode springen kann, wodurch ein Teil dieses Overheads vermieden wird.
In diesem Fall FOR_ITER
"sagt" Python jedes Mal, wenn es den oberen Rand der Schleife sieht , voraus, dass dies STORE_FAST
der nächste Opcode ist, den es ausführen muss. Python schaut dann auf den nächsten Opcode und springt, wenn die Vorhersage korrekt war, direkt zu STORE_FAST
. Dies hat den Effekt, dass die beiden Opcodes zu einem einzigen Opcode zusammengefasst werden.
Auf der anderen Seite die STORE_NAME
Opcode in der Schleife auf globaler Ebene verwendet. Python macht * keine * ähnlichen Vorhersagen, wenn es diesen Opcode sieht. Stattdessen muss es zum Anfang der Auswertungsschleife zurückkehren, was offensichtliche Auswirkungen auf die Geschwindigkeit hat, mit der die Schleife ausgeführt wird.
Um weitere technische Details zu dieser Optimierung zu erhalten, finden Sie hier ein Zitat aus dem ceval.c
Datei (der "Engine" der virtuellen Maschine von Python):
Einige Opcodes kommen in der Regel paarweise vor, sodass der zweite Code vorhergesagt werden kann, wenn der erste ausgeführt wird. Zum Beispiel
GET_ITER
wird oft gefolgt von FOR_ITER
. Und FOR_ITER
wird oft gefolgt vonSTORE_FAST
oderUNPACK_SEQUENCE
.
Das Überprüfen der Vorhersage kostet einen einzelnen Hochgeschwindigkeitstest einer Registervariablen gegen eine Konstante. Wenn die Paarung gut war, hat die interne Verzweigungsprädikation des Prozessors eine hohe Erfolgswahrscheinlichkeit, was zu einem Übergang von nahezu null Overhead zum nächsten Opcode führt. Eine erfolgreiche Vorhersage erspart eine Reise durch die Bewertungsschleife einschließlich ihrer zwei unvorhersehbaren Zweige, derHAS_ARG
Tests und des Schaltfalls. In Kombination mit der internen Verzweigungsvorhersage des Prozessors führt ein Erfolg PREDICT
dazu, dass die beiden Opcodes so ausgeführt werden, als wären sie ein einziger neuer Opcode mit den kombinierten Körpern.
Wir können im Quellcode für den FOR_ITER
Opcode genau sehen, wo die Vorhersage fürSTORE_FAST
gemacht wird:
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
Die PREDICT
Funktion wird erweitert aufif (*next_instr == op) goto PRED_##op
dh wir springen einfach zum Anfang des vorhergesagten Opcodes. In diesem Fall springen wir hier:
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;
Die lokale Variable ist jetzt gesetzt und der nächste Opcode steht zur Ausführung bereit. Python fährt mit dem Iterable fort, bis es das Ende erreicht, und macht jedes Mal die erfolgreiche Vorhersage.
Auf der Python-Wiki-Seite finden Sie weitere Informationen zur Funktionsweise der virtuellen Maschine von CPython.