Le fait est que les processeurs modernes sont compliqués. Toutes les instructions exécutées interagissent les unes avec les autres de manière compliquée et intéressante. Merci pour "l'autre gars" pour avoir posté le code.
Le PO et "l'autre gars" ont apparemment trouvé que la boucle courte prend 11 cycles, tandis que la longue prend 9 cycles. Pour la boucle longue, 9 cycles sont largement suffisants, même s'il y a beaucoup d'opérations. Pour la boucle courte, il doit y avoir un blocage causé par le fait qu'elle est si courte, et le simple fait d'ajouter une touche nop
rend la boucle suffisamment longue pour éviter le décrochage.
Une chose qui se passe si on regarde le code :
0x00000000004005af <+50>: addq $0x1,-0x20(%rbp)
0x00000000004005b4 <+55>: cmpq $0x7fffffff,-0x20(%rbp)
0x00000000004005bc <+63>: jb 0x4005af <main+50>
Nous lisons i
et le réécrire ( addq
). Nous le relisons immédiatement, et le comparons ( cmpq
). Et ensuite on boucle. Mais la boucle utilise la prédiction de branche. Donc au moment où la addq
est exécuté, le processeur n'est pas vraiment sûr d'être autorisé à écrire dans le fichier i
(car la prédiction de la branche peut être erronée).
Ensuite, nous comparons à i
. Le processeur essaiera d'éviter de lire i
de mémoire, car sa lecture prend beaucoup de temps. Au lieu de cela, une partie du matériel se souviendra que nous venons d'écrire dans le fichier i
en y ajoutant, et au lieu de lire i
El cmpq
reçoit les données de l'instruction de stockage. Malheureusement, nous ne sommes pas sûrs à ce stade si l'instruction write to i
s'est réellement produit ou non ! Cela pourrait donc introduire un blocage ici.
Le problème ici est que le saut conditionnel, le addq
qui mène à un magasin conditionnel, et l'option cmpq
qui ne sait pas d'où proviennent les données, sont toutes très très proches les unes des autres. Ils sont inhabituellement proches les uns des autres. Il se peut qu'elles soient si proches les unes des autres que le processeur n'arrive pas à savoir s'il doit prendre i
à partir de l'instruction de stockage ou pour le lire depuis la mémoire. Il le lit en mémoire, ce qui est plus lent car il doit attendre la fin de l'enregistrement. Et l'ajout d'un seul nop
donne au processeur suffisamment de temps.
En général, on pense qu'il y a la mémoire vive et le cache. Sur un processeur Intel moderne, la mémoire vive peut être lue de (du plus lent au plus rapide) :
- Mémoire (RAM)
- Cache L3 (facultatif)
- Cache L2
- Cache L1
- Instruction de stockage précédente qui n'a pas encore été écrite dans le cache L1.
Donc ce que le processeur fait en interne dans la boucle courte et lente :
- Lire
i
à partir du cache L1
- Ajouter 1 à
i
- Écrire à
i
vers le cache L1
- Attendez jusqu'à ce que
i
est écrit dans le cache L1
- Lire
i
à partir du cache L1
- Comparez
i
avec INT_MAX
- Passer à (1) si elle est inférieure.
Dans la boucle longue et rapide, le processeur le fait :
- Beaucoup de choses
- Lire
i
à partir du cache L1
- Ajouter 1 à
i
- Faites une instruction "store" qui écrira
i
vers le cache L1
- Lire
i
directement à partir de l'instruction "store" sans toucher au cache L1
- Comparez
i
avec INT_MAX
- Passer à (1) si elle est inférieure.