Además de los tiempos de almacenamiento de variables locales / globales, la predicción de código de operación hace que la función sea más rápida.
Como explican las otras respuestas, la función usa el STORE_FAST
código de operación en el bucle. Aquí está el código de bytes para el bucle de la función:
>> 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, cuando se ejecuta un programa, Python ejecuta cada código de operación uno tras otro, haciendo un seguimiento de la pila y realizando otras comprobaciones en el marco de la pila después de ejecutar cada código de operación. La predicción del código de operación significa que, en ciertos casos, Python puede saltar directamente al siguiente código de operación, evitando así parte de esta sobrecarga.
En este caso, cada vez que Python vea FOR_ITER
(la parte superior del bucle), "predecirá" STORE_FAST
cuál es el próximo código de operación que debe ejecutar. Python luego mira el siguiente código de operación y, si la predicción fue correcta, salta directamente a STORE_FAST
. Esto tiene el efecto de comprimir los dos códigos de operación en un solo código de operación.
Por otro lado, el STORE_NAME
código de operación se usa en el ciclo a nivel global. Python * no * hace predicciones similares cuando ve este código de operación. En cambio, debe volver a la parte superior del ciclo de evaluación, lo que tiene implicaciones obvias para la velocidad a la que se ejecuta el ciclo.
Para dar más detalles técnicos sobre esta optimización, aquí hay una cita del ceval.c
archivo (el "motor" de la máquina virtual de Python):
Algunos códigos de operación tienden a venir en pares, por lo que es posible predecir el segundo código cuando se ejecuta el primero. Por ejemplo, a
GET_ITER
menudo le sigue FOR_ITER
. Y a FOR_ITER
menudo es seguido porSTORE_FAST
o UNPACK_SEQUENCE
.
Verificar la predicción cuesta una sola prueba de alta velocidad de una variable de registro contra una constante. Si el emparejamiento fue bueno, entonces la propia predicación interna de la rama del procesador tiene una alta probabilidad de éxito, lo que resulta en una transición de casi cero sobrecarga al siguiente código operativo. Una predicción exitosa ahorra un viaje a través del ciclo de evaluación que incluye sus dos ramas impredecibles, la HAS_ARG
prueba y el caso de cambio. Combinado con la predicción de rama interna del procesador, un éxito PREDICT
tiene el efecto de hacer que los dos códigos de operación se ejecuten como si fueran un solo código de operación nuevo con los cuerpos combinados.
Podemos ver en el código fuente del código de FOR_ITER
operación exactamente dónde se realiza la predicción 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
La PREDICT
función se expande a, if (*next_instr == op) goto PRED_##op
es decir, simplemente saltamos al inicio del código de operación predicho. En este caso, saltamos aquí:
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 variable local ahora está configurada y el siguiente código operativo está en ejecución. Python continúa a través del iterable hasta que llega al final, haciendo la predicción exitosa cada vez.
La página wiki de Python tiene más información sobre cómo funciona la máquina virtual de CPython.