Après avoir lu The JSR-133 Cookbook for Compiler Writers sur l'implémentation de volatile, en particulier la section "Interactions avec les instructions atomiques", je suppose que lire une variable volatile sans la mettre à jour nécessite un LoadLoad ou une barrière LoadStore. Plus bas dans la page, je vois que LoadLoad et LoadStore sont effectivement des no-ops sur les processeurs X86. Cela signifie-t-il que les opérations de lecture volatile peuvent être effectuées sans invalidation de cache explicite sur x86, et est aussi rapide qu'une lecture de variable normale (sans tenir compte des contraintes de réorganisation de volatile)?
Je crois que je ne comprends pas bien cela. Quelqu'un pourrait-il vouloir m'éclairer?
EDIT: Je me demande s'il existe des différences dans les environnements multiprocesseurs. Sur les systèmes à processeur unique, le processeur peut regarder ses propres caches de threads, comme le déclare John V., mais sur les systèmes à plusieurs processeurs, il doit y avoir une option de configuration pour les processeurs qui ne suffit pas et que la mémoire principale doit être touchée, ce qui ralentit la volatilité. sur les systèmes multi cpu, non?
PS: En chemin pour en savoir plus à ce sujet, je suis tombé sur les excellents articles suivants, et comme cette question peut intéresser d'autres personnes, je vais partager mes liens ici:
Réponses:
Sur Intel, une lecture volatile non contestée est assez bon marché. Si nous considérons le cas simple suivant:
En utilisant la capacité de Java 7 à imprimer le code d'assemblage, la méthode d'exécution ressemble à quelque chose comme:
Si vous regardez les 2 références à getstatic, la première implique un chargement de la mémoire, la seconde ignore la charge car la valeur est réutilisée à partir du ou des registres dans lesquels elle est déjà chargée (longue est de 64 bits et sur mon ordinateur portable 32 bits il utilise 2 registres).
Si nous rendons la variable l volatile, l'assemblage résultant est différent.
Dans ce cas, les deux références getstatic à la variable l impliquent un chargement de la mémoire, c'est-à-dire que la valeur ne peut pas être conservée dans un registre sur plusieurs lectures volatiles. Pour s'assurer qu'il y a une lecture atomique, la valeur est lue de la mémoire principale dans un registre MMX
movsd 0x6fb7b2f0(%ebp),%xmm0
faisant de l'opération de lecture une seule instruction (de l'exemple précédent, nous avons vu que la valeur 64 bits nécessiterait normalement deux lectures 32 bits sur un système 32 bits).Ainsi, le coût global d'une lecture volatile sera à peu près équivalent à une charge mémoire et peut être aussi bon marché qu'un accès au cache L1. Cependant, si un autre cœur écrit dans la variable volatile, la ligne de cache sera invalidée, nécessitant une mémoire principale ou peut-être un accès au cache L3. Le coût réel dépendra fortement de l'architecture du processeur. Même entre Intel et AMD, les protocoles de cohérence du cache sont différents.
la source
D'une manière générale, sur la plupart des processeurs modernes, une charge volatile est comparable à une charge normale. Un magasin volatil représente environ 1/3 du temps d'une entrée / sortie de surveillance. Cela se voit sur les systèmes qui sont cohérents avec le cache.
Pour répondre à la question du PO, les écritures volatiles coûtent cher alors que les lectures ne le sont généralement pas.
Oui, parfois lors de la validation d'un champ, le CPU peut même ne pas atteindre la mémoire principale, au lieu d'espionner d'autres caches de threads et obtenir la valeur à partir de là (explication très générale).
Cependant, j'appuie la suggestion de Neil selon laquelle si vous avez un champ accessible par plusieurs threads, vous devez l'envelopper comme une référence atomique. Étant un AtomicReference, il exécute à peu près le même débit pour les lectures / écritures, mais il est également plus évident que le champ sera accessible et modifié par plusieurs threads.
Modifier pour répondre à la modification de OP:
La cohérence du cache est un peu un protocole compliqué, mais en bref: les processeurs partageront une ligne de cache commune qui est attachée à la mémoire principale. Si un processeur charge de la mémoire et qu'aucun autre processeur ne l'a eu, ce processeur supposera qu'il s'agit de la valeur la plus à jour. Si un autre CPU essaie de charger le même emplacement mémoire, le CPU déjà chargé en sera conscient et partagera en fait la référence mise en cache avec le CPU demandeur - maintenant le CPU de demande a une copie de cette mémoire dans son cache CPU. (Il n'a jamais eu à chercher dans la mémoire principale la référence)
Il y a un peu plus de protocole impliqué mais cela donne une idée de ce qui se passe. Aussi pour répondre à votre autre question, en l'absence de plusieurs processeurs, les lectures / écritures volatiles peuvent en fait être plus rapides qu'avec plusieurs processeurs. Certaines applications fonctionneraient en fait plus rapidement simultanément avec un seul processeur puis plusieurs.
la source
Selon les termes du modèle de mémoire Java (tel que défini pour Java 5+ dans JSR 133), toute opération - lecture ou écriture - sur une
volatile
variable crée une relation d' avance par rapport à toute autre opération sur la même variable. Cela signifie que le compilateur et JIT sont obligés d'éviter certaines optimisations telles que la réorganisation des instructions dans le thread ou l'exécution d'opérations uniquement dans le cache local.Comme certaines optimisations ne sont pas disponibles, le code résultant est nécessairement plus lent qu'il l'aurait été, mais probablement pas de beaucoup.
Néanmoins, vous ne devriez pas créer une variable à
volatile
moins que vous ne sachiez qu'elle sera accessible à partir de plusieurs threads en dehors dessynchronized
blocs. Même dans ce cas, vous devriez vous demander si volatile est le meilleur choix par rapport àsynchronized
,AtomicReference
et ses amis, lesLock
classes explicites , etc.la source
L'accès à une variable volatile est à bien des égards similaire à l'encapsulation de l'accès à une variable ordinaire dans un bloc synchronisé. Par exemple, l'accès à une variable volatile empêche le CPU de réorganiser les instructions avant et après l'accès, ce qui ralentit généralement l'exécution (bien que je ne puisse pas dire de combien).
Plus généralement, sur un système multiprocesseur, je ne vois pas comment l'accès à une variable volatile peut se faire sans pénalité - il doit y avoir un moyen de garantir qu'une écriture sur le processeur A sera synchronisée avec une lecture sur le processeur B.
la source