Ok, j'ai lu les questions suivantes de SO concernant les clôtures CPU x86 (LFENCE
, SFENCE
et MFENCE
):
Cela a-t-il un sens pour LFENCE dans les processeurs x86/x86_64?
Quel est l'impact SFENCE et LFENCE sur les caches des cœurs voisins?
et:
et je dois être honnête, je ne suis pas encore tout à fait sûr quand une clôture est nécessaire. J'essaie de comprendre du point de vue de la suppression des verrous complets et de l'utilisation d'un verrouillage plus fin via des clôtures, afin de minimiser les retards de latence.
Voici d'abord deux questions spécifiques que je ne comprends pas:
Parfois, lors d'un stockage, un processeur écrit dans son tampon de stockage au lieu du cache L1. Je ne comprends cependant pas les conditions dans lesquelles un CPU fera cela?
CPU2 peut souhaiter charger une valeur qui a été écrite dans le tampon de stockage de CPU1. Si je comprends bien, le problème est que CPU2 ne peut pas voir la nouvelle valeur dans le tampon de stockage de CPU1. Pourquoi le protocole MESI ne peut-il pas simplement inclure le vidage des tampons de magasin dans le cadre de son protocole ??
Plus généralement, quelqu'un pourrait-il essayer de décrire le scénario global et aider à expliquer quand des instructions LFENCE
/MFENCE
et SFENCE
sont requises?
NB Un des problèmes de lecture autour de ce sujet est le nombre d'articles écrits "généralement" pour plusieurs architectures CPU, quand je ne suis intéressé que par l'architecture Intel x86-64 en particulier.
La réponse la plus simple: vous devez utiliser l'une des 3 clôtures (LFENCE
, SFENCE
, MFENCE
) pour fournir l'une des 6 données cohérentes:
C++ 11:
Initialement, vous devriez considérer ce problème du point de vue du degré d'ordre d'accès à la mémoire, qui est bien documenté et normalisé en C++ 11. Vous devez d'abord lire: http://en.cppreference.com/w/cpp/atomic/memory_order
x86/x86_64:
1. Acquire-Release Cohérence: Ensuite, il est important de comprendre que dans le x86 pour accéder à la convention RAM (marqué par défaut comme WB - Write Back, et le même effet avec WT (Write Throught) ou UC (Uncacheable)) en utilisant asm MOV
sans aucune commande supplémentaire fournit automatiquement l'ordre de la mémoire pour la cohérence acquisition-libération - std::memory_order_acq_rel
. C'est-à-dire que cette mémoire est logique à utiliser uniquement std::memory_order_seq_cst
Uniquement pour fournir une cohérence séquentielle, c'est-à-dire lorsque vous utilisez: std::memory_order_relaxed
Ou std::memory_order_acq_rel
Puis le code assembleur compilé pour std::atomic::store()
(ou std::atomic::load()
) sera le même - seulement MOV
sans L/S/MFENCE
.
Remarque: Mais vous devez savoir que non seulement le CPU mais aussi le compilateur C++ peuvent réorganiser les opérations avec de la mémoire et que les 6 barrières mémoire affectent toujours le compilateur C++ quelle que soit l'architecture du CPU.
Ensuite, vous devez savoir comment le compiler de C++ vers ASM (code machine natif) ou comment l'écrire sur l'assembleur. Pour fournir une cohérence excluant séquentielle, vous pouvez simplement écrire MOV
, par exemple MOV reg, [addr]
Et MOV [addr], reg
Etc.
2. Cohérence séquentielle: Mais pour fournir une cohérence séquentielle, vous devez utiliser des clôtures implicites (LOCK
) ou explicites (L/S/MFENCE
) comme décrit ici: - Pourquoi GCC n'utilise pas LOAD (sans clôture) et STORE + SFENCE pour la cohérence séquentielle?
LOAD
(sans clôture) et STORE
+ MFENCE
LOAD
(sans clôture) et LOCK XCHG
MFENCE
+ LOAD
et STORE
(sans clôture)LOCK XADD
(0) et STORE
(sans clôture)Par exemple, GCC utilise 1, mais MSVC en utilise 2. (Mais vous devez savoir que MSVS2012 a un bogue: La sémantique de `std :: memory_order_acquire` nécessite-t-elle des instructions de processeur sur x86/x86_64? )
Ensuite, vous pouvez lire Herb Sutter, votre lien: https://OneDrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c
Exception à la règle:
Cette règle est vraie pour l'accès en utilisant MOV
à conventionnel RAM marqué par défaut comme WB - Write Back. La mémoire marque dans le Page Table , dans chaque PTE (Page Table Enrty) pour chaque page (4 Ko de mémoire continue).
Mais il y a des exceptions:
Si nous marquons la mémoire dans le tableau des pages comme étant combinée en écriture (ioremap_wc()
dans POSIX), alors automatiquement, nous ne fournissons que la cohérence d'acquisition, et nous devons agir comme dans le paragraphe suivant.
Voir la réponse à ma question: https://stackoverflow.com/a/27302931/1558037
- Les écritures en mémoire ne sont pas réorganisées avec d'autres écritures, avec les exceptions suivantes :
- écrit exécuté avec l'instruction CLFLUSH;
- les magasins de streaming (écritures) exécutés avec les instructions de déplacement non temporelles (MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS et MOVNTPD); et
- opérations de chaîne (voir Section 8.2.4.1).
Dans les deux cas, 1 et 2, vous devez utiliser des SFENCE
supplémentaires entre deux écritures à la même adresse, même si vous souhaitez acquérir la cohérence de la libération, car ici, automatiquement, ne fournit que la cohérence de l'acquisition et vous devez effectuer la libération (SFENCE
) toi même.
Répondez à vos deux questions:
Parfois, lors d'un stockage, un processeur écrit dans son tampon de stockage au lieu du cache L1. Je ne comprends cependant pas les conditions dans lesquelles un CPU fera cela?
Du point de vue de l'utilisateur, le cache L1 et le tampon de stockage agissent différemment. L1 rapide, mais Store-Buffer plus rapide.
Store-Buffer - est une file d'attente simple où ne stocke que des écritures, et qui ne peut pas être réorganisée - elle est conçue pour augmenter les performances et masquer la latence d'accès au cache (L1 - 1ns, L2 - 3ns, L3 - 10ns) (CPU-Core pensez que Write a été stocké dans le cache et exécute la commande suivante, mais en même temps, vos écritures ne sont enregistrées que dans le magasin-tampon et seront enregistrées dans le cache L1/2/3 plus tard), c'est-à-dire que CPU-Core n'a pas besoin attendre quand les écritures auront été stockées dans le cache.
Cache L1/2/3 - ressemble à un tableau associé transparent (adresse - valeur). Il est rapide mais pas le plus rapide, car x86 fournit automatiquement la cohérence acquisition-libération en utilisant cache cohérent protocole MESIF / MOESI . Cela est fait pour une programmation multithread plus simple, mais diminue les performances. (Vraiment, nous pouvons utiliser des algorithmes et des structures de données Write Contentions Free sans utiliser de cache cohérent, c'est-à-dire sans MESIF/MOESI par exemple sur PCI Express ). Les protocoles MESIF/MOESI fonctionnent sur QPI qui connecte les cœurs dans le CPU et les cœurs entre différents CPU dans les systèmes multiprocesseurs ( ccNUMA ).
CPU2 peut souhaiter charger une valeur qui a été écrite dans le tampon de stockage de CPU1. Si je comprends bien, le problème est que CPU2 ne peut pas voir la nouvelle valeur dans le tampon de stockage de CPU1.
Oui.
Pourquoi le protocole MESI ne peut-il pas simplement inclure le vidage des tampons de magasin dans le cadre de son protocole ??
Le protocole MESI ne peut pas simplement inclure le vidage des tampons de magasin dans le cadre de son protocole, car:
Mais vider manuellement le tampon de stockage sur le CPU-Core actuel - oui, vous pouvez le faire en exécutant la commande SFENCE
. Vous pouvez utiliser SFENCE
dans deux cas:
Remarque:
Avons-nous besoin de LFENCE
dans tous les cas sur x86/x86_64? - la question n'est pas toujours claire: Cela a-t-il un sens pour l'instruction LFENCE dans les processeurs x86/x86_64?
Autre plateforme:
Ensuite, vous pouvez lire comme en théorie (pour un processeur sphérique sous vide) avec Store-Buffer et Invalidate-Queue, votre lien: http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf
Et comment vous pouvez fournir une cohérence séquentielle sur d'autres plates-formes, non seulement avec L/S/MFENCE et LOCK, mais aussi avec LL/SC : http://www.cl.cam.ac .uk/~ pes20/cpp/cpp0xmappings.html