Mis à part les temps de stockage variables locaux / globaux, la prédiction d'opcode rend la fonction plus rapide.
Comme les autres réponses l'expliquent, la fonction utilise l' STORE_FASTopcode dans la boucle. Voici le bytecode pour la boucle de la fonction:
>> 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
Normalement, lorsqu'un programme est exécuté, Python exécute chaque opcode l'un après l'autre, en gardant une trace de la pile et en effectuant d'autres vérifications sur le cadre de la pile après l'exécution de chaque opcode. La prédiction d'opcode signifie que dans certains cas, Python est capable de passer directement à l'opcode suivant, évitant ainsi une partie de cette surcharge.
Dans ce cas, chaque fois que Python voit FOR_ITER(le haut de la boucle), il "prédira" que STORE_FASTc'est le prochain opcode qu'il devra exécuter. Python jette ensuite un œil à l'opcode suivant et, si la prédiction était correcte, il passe directement à STORE_FAST. Cela a pour effet de compresser les deux opcodes en un seul opcode.
En revanche, l' STORE_NAMEopcode est utilisé dans la boucle au niveau global. Python ne fait * pas * de prédictions similaires lorsqu'il voit cet opcode. Au lieu de cela, il doit remonter en haut de la boucle d'évaluation, ce qui a des implications évidentes pour la vitesse à laquelle la boucle est exécutée.
Pour donner plus de détails techniques sur cette optimisation, voici une citation du ceval.cfichier (le "moteur" de la machine virtuelle de Python):
Certains opcodes ont tendance à venir par paires, ce qui permet de prédire le second code lorsque le premier est exécuté. Par exemple,
GET_ITERest souvent suivi de FOR_ITER. Et FOR_ITERest souvent suivi deSTORE_FAST ou UNPACK_SEQUENCE.
La vérification de la prédiction coûte un seul test à grande vitesse d'une variable de registre par rapport à une constante. Si le couplage était bon, alors la propre prédiction de branche interne du processeur a une forte probabilité de succès, ce qui entraîne une transition presque nulle pour le prochain opcode. Une prédiction réussie enregistre un voyage à travers la boucle d'évaluation, y compris ses deux branches imprévisibles, le HAS_ARGtest et le boîtier de commutation. Combiné avec la prédiction de branche interne du processeur, un succès PREDICTa pour effet de faire fonctionner les deux opcodes comme s'ils étaient un seul nouvel opcode avec les corps combinés.
Nous pouvons voir dans le code source de l' FOR_ITERopcode exactement où la prédiction STORE_FASTest faite:
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 PREDICTfonction se développe pour if (*next_instr == op) goto PRED_##opdire que nous venons de sauter au début de l'opcode prédit. Dans ce cas, nous sautons ici:
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 locale est maintenant définie et l'opcode suivant est prêt à être exécuté. Python continue à travers l'itérable jusqu'à ce qu'il atteigne la fin, faisant la prédiction réussie à chaque fois.
La page wiki Python contient plus d'informations sur le fonctionnement de la machine virtuelle de CPython.