A parte i tempi di memorizzazione variabili locali / globali, la previsione del codice operativo rende la funzione più veloce.
Come spiegano le altre risposte, la funzione utilizza il STORE_FAST
codice operativo nel ciclo. Ecco il bytecode per il loop della funzione:
>> 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
Normalmente quando viene eseguito un programma, Python esegue ogni codice operativo uno dopo l'altro, tenendo traccia dello stack e preformando altri controlli sul frame dello stack dopo l'esecuzione di ciascun codice operativo. La previsione di Opcode significa che in alcuni casi Python è in grado di passare direttamente al successivo opcode, evitando così un certo sovraccarico.
In questo caso, ogni volta che Python vede FOR_ITER
(la parte superiore del ciclo), "prevede" STORE_FAST
il prossimo opcode che deve eseguire. Python quindi dà un'occhiata al successivo codice operativo e, se la previsione era corretta, passa direttamente a STORE_FAST
. Ciò ha l'effetto di spremere i due codici operativi in un unico codice operativo.
D'altra parte, il STORE_NAME
codice operativo viene utilizzato nel ciclo a livello globale. Python * non * fa previsioni simili quando vede questo codice operativo. Invece, deve tornare all'inizio del ciclo di valutazione che ha ovvie implicazioni per la velocità con cui il ciclo viene eseguito.
Per fornire ulteriori dettagli tecnici su questa ottimizzazione, ecco una citazione dal ceval.c
file (il "motore" della macchina virtuale di Python):
Alcuni codici operativi tendono a venire in coppia, rendendo così possibile prevedere il secondo codice quando viene eseguito il primo. Ad esempio,
GET_ITER
è spesso seguito da FOR_ITER
. Ed FOR_ITER
è spesso seguito daSTORE_FAST
o UNPACK_SEQUENCE
.
La verifica della previsione costa un singolo test ad alta velocità di una variabile di registro rispetto a una costante. Se l'accoppiamento è andato a buon fine, la previsione della diramazione interna del processore ha un'elevata probabilità di successo, con una transizione quasi zero al successivo opcode. Una previsione riuscita salva un viaggio attraverso il circuito di valutazione inclusi i suoi due rami imprevedibili, il HAS_ARG
test e la custodia. In combinazione con la previsione del ramo interno del processore, un successo PREDICT
ha l'effetto di far funzionare i due codici operativi come se fossero un unico nuovo codice operativo con i corpi combinati.
Possiamo vedere nel codice sorgente per il codice FOR_ITER
operativo esattamente dove STORE_FAST
viene fatta la previsione :
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
La PREDICT
funzione si espande, if (*next_instr == op) goto PRED_##op
ovvero passiamo all'inizio dell'opcode previsto. In questo caso, saltiamo qui:
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;
La variabile locale è ora impostata e il successivo opcode è pronto per l'esecuzione. Python continua attraverso l'iterabile fino a raggiungere la fine, facendo ogni volta la previsione riuscita.
La pagina wiki di Python contiene ulteriori informazioni su come funziona la macchina virtuale di CPython.