Question

L'autre semaine, j'écrit une petite classe de fil et un tuyau d'un message à une voie pour permettre la communication entre les filets (deux tuyaux par fil, de toute évidence, pour une communication bidirectionnelle). Tout fonctionnait bien sur mon Athlon 64 X2, mais je me demandais si je courrais en aucun problème si les deux fils étaient à la recherche à la même variable et la valeur de mise en cache locale pour cette variable sur chaque noyau était désynchronisée.

Je sais que la volatile mot-clé forcera une variable à rafraîchir la mémoire, mais est-il un moyen sur les processeurs x86 multicœurs pour forcer les caches de tous les cœurs pour synchroniser? Est-ce quelque chose que je dois vous soucier, ou sera utilisation volatile et bon des mécanismes de verrouillage légers (j'utilisais _InterlockedExchange pour régler mes variables de conduite volatiles) traiter tous les cas où je veux écrire « verrouiller libre » code pour les processeurs multi-cœurs x86?

Je suis déjà au courant et ont utilisé les sections critiques, mutex, événements, et ainsi de suite. Je me demande surtout s'il y a x86 intrinsics que je ne suis pas au courant de laquelle la force ou peut être utilisé pour appliquer la cohérence du cache.

Était-ce utile?

La solution

volatile force que votre code à relire la valeur, il ne peut pas contrôler où la valeur est lue. Si la valeur a été récemment lu par votre code, puis il sera probablement dans le cache, auquel cas volatile forcera à être relue à partir du cache, pas de mémoire.

Il n'y a pas beaucoup d'instructions de cache en cohérence x86. Il y a des instructions prélecture comme prefetchnta , mais qui ne touche pas la sémantique-commande mémoire. Il sert à mettre en œuvre en apportant la valeur à cache L1 sans polluer L2, mais les choses sont plus compliquées pour Intel moderne conçoit avec une grande commune inclus cache L3.

processeurs x86 utilisent une variation sur le protocole MESI (MESIF pour Intel, AMD MOESI de) pour garder leurs caches cohérents les uns avec les autres (y compris les caches L1 privées de différents noyaux). Un noyau qui veut écrire une ligne de cache doit forcer d'autres noyaux d'invalider leur copie avant de pouvoir changer sa propre copie de Shared à l'état modifié.


Vous n'avez pas besoin d'instructions de clôture (comme MFENCE) pour produire des données en un seul fil et de le consommer dans un autre sur x86, car les charges x86 / magasins ont acquérir intégré à la sémantique / libération. Vous avez besoin MFENCE (de barrière complète) pour obtenir la cohérence séquentielle. (Une version précédente de cette réponse a suggéré que clflush était nécessaire, ce qui est incorrect).

Vous avez besoin d'éviter compilation réordonnancement, parce que modèle de la mémoire de C ++ est faiblement ordonné. volatile est une ancienne mauvaise façon de le faire; C de 11 std :: atomique est une bien meilleure façon d'écrire du code sans verrouillage.

Autres conseils

cohérence de mémoire cache est garantie entre les noyaux en raison du protocole MESI employé par les processeurs x86. Il vous suffit de vous soucier de la cohérence de la mémoire lorsqu'ils traitent avec du matériel externe qui peut accéder à la mémoire alors que les données sont encore sur les choix de l'emplacement des caches de noyaux. Ne ressemble pas à c'est votre cas ici, cependant, puisque le texte suggère que vous programmez en userland.

Vous n'avez pas à vous soucier de la cohérence du cache. Le matériel se chargera de cela. Ce que vous devrez peut-être à se soucier des problèmes de performance est due à cette cohérence du cache.

Si noyau # 1 écrit à une variable, qui invalident toutes les autres copies de la ligne de cache dans d'autres noyaux (car il doit obtenir propriété exclusive de la ligne de cache avant d'engager le magasin). Lorsque le noyau # 2 lit cette même variable, il manquera dans le cache (sauf noyau # 1 a déjà écrit en arrière jusqu'à un niveau commun de cache).

Depuis une ligne de cache entier (64 octets) doit être lu de la mémoire (ou réécrites à cache partagé, puis lu par cœur # 2), il aura un coût de performance. Dans ce cas, il est inévitable. Ceci est le comportement souhaité.


Le problème est que lorsque vous avez plusieurs variables dans la même ligne de cache, le processeur peut passer plus de temps en gardant les caches synchronisés même si les noyaux sont la lecture / écriture de variables différentes au sein de la même ligne de cache.

Ce coût peut être évité en faisant en sorte que ces variables ne sont pas dans la même ligne de cache. Cet effet est connu comme Faux partage puisque vous obligez les processeurs pour synchroniser les valeurs des objets qui ne sont pas réellement partagées entre les threads.

Volatile ne le fera pas. En C ++, seulement volatile affecte ce que les optimisations du compilateur telles que le stockage d'une variable dans un registre à la place de la mémoire, ou de retirer complètement.

Vous n'avez pas spécifié quel compilateur que vous utilisez, mais si vous êtes sur Windows, jetez un oeil à cet article ici . Jetez aussi un coup d'œil à la disposition fonctions ynchronization ici . Vous voudrez peut-être noter que, dans volatile général ne suffit pas de faire ce que vous voulez faire, mais en VC 2005 et 2008, il y a une sémantique non standard ajouté à ce qui ajoutent des barrières de mémoire implicite autour lu et écrit.

Si vous voulez que les choses soient portables, vous allez avoir une route beaucoup plus difficile qui vous attend.

Il y a une série d'articles expliquant les architectures de mémoire modernes , y compris < a href = "http://duartes.org/gustavo/blog/post/intel-cpu-caches" rel = "nofollow noreferrer"> Intel Core2 met en cache et de nombreux sujets d'architecture plus moderne.

Les articles sont très lisibles et bien illustré. Amusez-vous!

Il existe plusieurs sous-questions dans votre question donc je vais y répondre au mieux de ma connaissance.

  1. Il n'y a actuellement aucun moyen de mettre en œuvre des interactions portable sans blocage en C ++. La proposition C ++ 0x résout ce problème en introduisant la bibliothèque ATOMiCS.
  2. Volatile n'est pas garanti pour fournir atomicité sur un multi-cœurs et sa mise en œuvre est spécifique au fournisseur.
  3. Sur x86, vous n'avez pas besoin de faire quelque chose de spécial, à l'exception des variables partagées comme déclaration des volatiles pour éviter certaines optimisations du compilateur qui peut casser le code multithread. Volatile indique au compilateur de ne pas mettre en cache les valeurs.
  4. Il y a quelques algorithmes (Dekker, par exemple) qui ne fonctionnera pas même sur un système x86 avec des variables volatiles.
  5. Sauf si vous êtes sûr que le passage l'accès aux données entre les threads est un goulot d'étranglement majeur dans votre programme, rester à l'écart des solutions sans blocage. Utiliser les données de passage de la valeur ou des serrures.

Ce qui suit est un bon article en référence à l'utilisation volatile w / programmes filetés.

Herb Sutter semblait simplement que suggèrent les deux variables doivent résider sur des lignes de cache séparées. Il le fait dans sa file d'attente en même temps avec un rembourrage entre ses serrures et pointeurs de noeuds.

Edit: Si vous utilisez le compilateur Intel ou GCC, vous pouvez utiliser le noreferrer builtins atomique , qui semblent faire de leur mieux pour préempter le cache lorsque cela est possible.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top