Après avoir lu ce message (réponse sur StackOverflow) (dans la section optimisation), je me demandais pourquoi les déplacements conditionnels ne sont pas vulnérables à l’échec de la prédiction de branche. J'ai trouvé sur un article sur cond move ici (PDF par AMD) . Là aussi, ils revendiquent l'avantage de performance de cond. se déplace. Mais pourquoi est-ce? Je ne le vois pas Au moment où cette instruction ASM est évaluée, le résultat de l'instruction CMP précédente n'est pas encore connu.
Merci.
Un processeur moderne exécute généralement entre une et trois instructions à chaque cycle si les choses se passent bien (s’il ne s’arrête pas dans l’attente des dépendances de données pour que ces instructions proviennent d’instructions précédentes ou de la mémoire).
La déclaration ci-dessus est étonnamment bonne pour les boucles serrées, mais cela ne doit pas vous faire oublier une dépendance supplémentaire qui peut empêcher l'exécution d'une instruction lorsque son cycle arrive: Pour qu'une instruction soit exécutée, le processeur doit avoir commencé à chercher et décoder 15-20 cycles avant.
Que doit faire le processeur lorsqu'il rencontre une branche? La récupération et le décodage des deux cibles ne sont pas mis à l'échelle (si plusieurs branches se suivent, un nombre exponentiel de chemins devrait être extrait en parallèle). Ainsi, le processeur ne récupère et décode que l’une des deux branches, de manière spéculative.
C'est pourquoi les branches mal prédites sont coûteuses: elles coûtent les 15 à 20 cycles qui sont généralement invisibles en raison d'un pipeline d'instructions efficace.
Le mouvement conditionnel ne nécessite pas de prédiction, il ne peut donc jamais avoir cette pénalité. Il a des dépendances de données, identiques aux instructions ordinaires. En fait, un déplacement conditionnel comporte plus de dépendances de données que d'instructions ordinaires, car les dépendances de données incluent les cas «condition vraie» et «condition fausse». Après une instruction qui déplace conditionnellement r1
vers r2
, le contenu de r2
semble dépendre de la valeur précédente de r2
et de r1
. Une branche conditionnelle bien prédite permet au processeur d'inférer des dépendances plus précises. Mais les dépendances de données prennent généralement un à deux cycles, si elles ont besoin de temps.
Notez qu'un passage conditionnel de mémoire à registre constituerait parfois un pari dangereux: si la condition est telle que la valeur lue dans la mémoire n'est pas affectée au registre, vous n'avez rien attendu à la mémoire. Mais les instructions de déplacement conditionnel offertes dans les jeux d'instructions sont généralement des registres à enregistrer, évitant cette erreur de la part du programmeur.
Il s’agit du pipeline instruction . N'oubliez pas que les processeurs modernes exécutent leurs instructions dans un pipeline, ce qui améliore considérablement les performances lorsque le flux d'exécution est prévisible par le processeur.
add eax, ebx
cmp eax, 0x10
cmovne ebx, ecx
add eax, ecx
Au moment où cette instruction ASM est évaluée, le résultat de l'instruction CMP précédente n'est pas encore connu.
Peut-être, mais le processeur sait toujours que l'instruction suivant la cmov
sera exécutée juste après, quel que soit le résultat de l'instruction cmp
et cmov
. L'instruction suivante peut donc être récupérée/décodée en toute sécurité, ce qui n'est pas le cas avec les branches.
La prochaine instruction pourrait même être exécutée avant la cmov
(dans mon exemple, cela serait sans danger)
add eax, ebx
cmp eax, 0x10
je .skip
mov ebx, ecx
.skip:
add eax, ecx
Dans ce cas, lorsque le décodeur de la CPU verra je .skip
, il devra choisir de continuer les instructions de pré-extraction/décodage soit 1) à partir de l'instruction suivante, soit 2) à partir de la cible de saut. La CPU devinera que cette branche conditionnelle ne se produira pas, aussi l'instruction suivante mov ebx, ecx
sera-t-elle intégrée dans le pipeline.
Quelques cycles plus tard, le je .skip
est exécuté et la branche prise. Oh merde! Notre pipeline contient maintenant des fichiers aléatoires qui ne doivent jamais être exécutés. La CPU doit vider toutes ses instructions en cache et recommencer à neuf à partir de .skip:
.
C'est la pénalité de performance des branches mal prédites, ce qui ne peut jamais arriver avec cmov
car cela ne modifie pas le déroulement de l'exécution.
En effet, le résultat peut ne pas être encore connu, mais si d'autres circonstances le permettent (notamment la chaîne de dépendance), le cpu peut réorganiser et exécuter des instructions en suivant la variable cmov
. Comme il n'y a pas de branche impliquée, ces instructions doivent être évaluées dans tous les cas.
Considérons cet exemple:
cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]
Les deux instructions suivant la variable cmov
ne dépendent pas du résultat de la variable cmov
; elles peuvent donc être exécutées même lorsque la variable cmov
est elle-même en attente (cette opération est appelée exécution non conforme ). Même s'ils ne peuvent pas être exécutés, ils peuvent toujours être récupérés et décodés.
Une version de branchement pourrait être:
jne skip
mov edx, eax
skip:
add ecx, ebx
mov eax, [ecx]
Le problème ici est que le flux de contrôle est en train de changer et que le processeur n’est pas assez intelligent pour voir qu’il pourrait simplement "insérer" l’instruction sautée mov
si la branche était mal prédite comme prise - elle jetterait tout ce qu’elle a fait après la branche, et redémarre à partir de zéro. C'est de là que vient la peine.
Vous devriez lire ceci. Avec Fog + Intel, il suffit de rechercher CMOV.
Critique de CMOV par Linus Torvald vers 2007
Comparaison des microarchitectures par Agner Fog
Manuel de référence de l'optimisation des architectures Intel® 64 et IA-32
Réponse courte, les prédictions correctes sont "gratuites" tandis que les erreurs de prédiction de branche conditionnelles peuvent coûter entre 14 et 20 cycles sur Haswell. Cependant, CMOV n'est jamais gratuit. Néanmoins, je pense que CMOV est BEAUCOUP meilleur maintenant que lorsque Torvalds a déclamé. Il n'y a pas un seul correct pour tous les temps sur tous les processeurs jamais répondu.