126 votes

Est volatile cher ?

Après la lecture de La JSR-133 livre de cuisine pour les Rédacteurs du Compilateur sur la mise en œuvre de la volatilité, en particulier la section "Interactions Atomiques Instructions" je suppose que la lecture d'une variable volatile sans mise à jour, il a besoin d'un LoadLoad ou un LoadStore barrière. En bas de la page je vois que LoadLoad et LoadStore sont effectivement pas des opérations sur les Processeurs X86. Est-ce à dire que la volatilité des opérations de lecture peut être fait sans un explicite invalidation du cache sur x86, et est aussi vite qu'une variable normale de lecture (sans prendre en compte la réorganisation des contraintes de la volatilité)?

Je crois que je ne comprends pas ce correctement. Quelqu'un pourrait-il soin de m'éclairer?

EDIT: je me demande si il y a des différences dans les multi-processeur environnements. Sur des systèmes à UC le PROCESSEUR peut regarder son propre thread caches, comme John V. les états, mais aussi sur des systèmes à UC il doit y avoir une option de configuration pour les Processeurs que ce n'est pas suffisant et la mémoire principale doit être frappé, faisant de volatilité plus lent sur les systèmes à uc multiples, non?

PS: Sur mon chemin pour en savoir plus à propos de cela, je suis tombé sur la suite de grands articles, et depuis que cette question peut être intéressant pour les autres, je vais partager mes liens ici:

127voto

Michael Barker Points 8234

Sur Intel des nations unies a soutenu lecture volatile est assez bon marché. Si nous considérons le cas simple suivant:

public static long l;

public static void run() {        
    if (l == -1)
        System.exit(-1);

    if (l == -2)
        System.exit(-1);
}

À l'aide de Java 7 capacité d'assemblage d'impression de code à l'exécution de la méthode ressemble à quelque chose comme:

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb396ce80: mov    %eax,-0x3000(%esp)
0xb396ce87: push   %ebp
0xb396ce88: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::run2@-1 (line 33)
0xb396ce8e: mov    $0xffffffff,%ecx
0xb396ce93: mov    $0xffffffff,%ebx
0xb396ce98: mov    $0x6fa2b2f0,%esi   ;   {oop('Test2')}
0xb396ce9d: mov    0x150(%esi),%ebp
0xb396cea3: mov    0x154(%esi),%edi   ;*getstatic l
                                    ; - Test2::run@0 (line 33)
0xb396cea9: cmp    %ecx,%ebp
0xb396ceab: jne    0xb396ceaf
0xb396cead: cmp    %ebx,%edi
0xb396ceaf: je     0xb396cece         ;*getstatic l
                                    ; - Test2::run@14 (line 37)
0xb396ceb1: mov    $0xfffffffe,%ecx
0xb396ceb6: mov    $0xffffffff,%ebx
0xb396cebb: cmp    %ecx,%ebp
0xb396cebd: jne    0xb396cec1
0xb396cebf: cmp    %ebx,%edi
0xb396cec1: je     0xb396ceeb         ;*return
                                    ; - Test2::run@28 (line 40)
0xb396cec3: add    $0x8,%esp
0xb396cec6: pop    %ebp
0xb396cec7: test   %eax,0xb7732000    ;   {poll_return}
;... lines removed

Si vous regardez les 2 références à getstatic, la première implique une charge de la mémoire, le deuxième ignore la charge que la valeur est réutilisé par le registre(s), il est déjà chargé dans la (longue est de 64 bits et sur mon 32 bits portable, il utilise 2 registres).

Si nous faisons le l variable volatile de l'assemblage qui en résulte est différent.

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb3ab9340: mov    %eax,-0x3000(%esp)
0xb3ab9347: push   %ebp
0xb3ab9348: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::run2@-1 (line 32)
0xb3ab934e: mov    $0xffffffff,%ecx
0xb3ab9353: mov    $0xffffffff,%ebx
0xb3ab9358: mov    $0x150,%ebp
0xb3ab935d: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab9365: movd   %xmm0,%eax
0xb3ab9369: psrlq  $0x20,%xmm0
0xb3ab936e: movd   %xmm0,%edx         ;*getstatic l
                                    ; - Test2::run@0 (line 32)
0xb3ab9372: cmp    %ecx,%eax
0xb3ab9374: jne    0xb3ab9378
0xb3ab9376: cmp    %ebx,%edx
0xb3ab9378: je     0xb3ab93ac
0xb3ab937a: mov    $0xfffffffe,%ecx
0xb3ab937f: mov    $0xffffffff,%ebx
0xb3ab9384: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab938c: movd   %xmm0,%ebp
0xb3ab9390: psrlq  $0x20,%xmm0
0xb3ab9395: movd   %xmm0,%edi         ;*getstatic l
                                    ; - Test2::run@14 (line 36)
0xb3ab9399: cmp    %ecx,%ebp
0xb3ab939b: jne    0xb3ab939f
0xb3ab939d: cmp    %ebx,%edi
0xb3ab939f: je     0xb3ab93ba         ;*return
;... lines removed

Dans ce cas, les deux de la getstatic références à la variable l implique une charge à partir de la mémoire, c'est à dire la valeur ne peut pas être conservés dans un registre au travers de multiples volatils lit. Pour s'assurer qu'il n'y est atomique lire la valeur est lue depuis la mémoire principale dans un registre MMX movsd 0x6fb7b2f0(%ebp),%xmm0 faire l'opération de lecture d'une seule instruction (à partir de l'exemple précédent, nous avons vu que 64bit valeur aurait normalement besoin de deux 32bit lit sur un système 32 bits).

Si le coût global de la volatilité et de la lecture à peu près l'équivalent de la charge de la mémoire et peuvent être aussi bas que un cache L1 de l'accès. Toutefois, si un autre noyau est écrit à la volatilité de la variable, le cache-ligne sera invalidé nécessitant une mémoire principale ou peut-être un cache L3 de l'accès. Le coût réel dépendra fortement de l'architecture du PROCESSEUR. Même entre Intel et AMD, les protocoles de cohérence de cache sont différents.

22voto

John Vint Points 19804

En règle générale, sur la plupart des processeurs modernes volatile charge est comparable à une charge normale. Un volatile magasin est d'environ 1/3 du temps de montior-entrée/moniteur-sortie. Ce qui est vu sur les systèmes de cache cohérente.

Pour répondre à l'OP de la question, la volatilité des écritures sont chers, alors que les lectures ne sont généralement pas.

Est-ce à dire que la volatilité lire les opérations peuvent se faire sans un explicite invalidation du cache sur x86, et est un moyen rapide d'une variable normale de lecture (sans prendre en compte la réorganisation des les contraintes de la volatilité)?

Oui, parfois, lors de la validation d'un champ de l'UC peut même pas touché la mémoire principale, au lieu d'espionner l'autre thread caches et de récupérer la valeur de y (très explication générale).

Cependant, je seconde Neil suggère que si vous avez un champ accessible par plusieurs threads vous shold l'envelopper comme un AtomicReference. Étant un AtomicReference il exécute à peu près le même débit pour les lectures/écritures, mais aussi de plus évident que le champ sera consulté et modifié par plusieurs threads.

Edit pour répondre à l'OP d'édition:

Cohérence de Cache est un peu compliqué protocole, mais en bref: le CPU sera partagent une même ligne de cache qui est attaché à la mémoire principale. Si la charge du CPU mémoire et pas d'autre PROCESSEUR que le CPU suppose que c'est la plus à jour de la valeur. Si un autre CPU essaie de charger le même emplacement mémoire déjà chargé CPU sera au courant de cette et fait part de la mise en cache de référence à la demande de l'UC - maintenant, la demande de la CPU a une copie de ce mémoire dans son cache du PROCESSEUR. (Il n'a jamais eu à chercher dans la mémoire principale pour la référence)

Il y a un peu plus de protocole, mais cela donne une idée de ce qui se passe. Aussi, pour répondre à votre autre question, avec l'absence de plusieurs processeurs, la volatilité des lectures/écritures peuvent en fait être plus rapide, puis avec plusieurs processeurs. Il y a quelques applications qui pourraient en fait courir plus vite simultanément avec un seul PROCESSEUR, puis plusieurs.

14voto

Neil Bartlett Points 12330

Dans les paroles de la Java du Modèle de Mémoire (tel que défini pour Java 5+ dans la JSR 133), toute opération -- lire ou écrire -- sur un volatile variable crée un passe-avant la relation à l'égard de 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 de l'exécution d'opérations que dans le cache local.

Depuis quelques optimisations ne sont pas disponibles, le code résultant est nécessairement plus lent que ça aurait été, mais probablement pas par beaucoup.

Néanmoins, vous ne devriez pas faire une variable volatile , sauf si vous savez qu'il sera accessible à partir de plusieurs threads à l'extérieur de l' synchronized blocs. Même alors, vous devriez considérer si volatile est le meilleur choix contre synchronized, AtomicReference et de ses amis, de l'explicite Lock de classes, etc.

4voto

krakover Points 1447

L'accès à une variable volatile est à bien des égards similaire à l'emballage d'accès à une variable dans un bloc synchronisé. Par exemple, l'accès à une variable volatile empêche la CPU de re-commander les instructions de la section avant et après l'accès, et cela ralentit l'exécution (même si je ne peux pas dire combien).

Plus généralement, sur un système multi-processeur, je ne vois pas comment accéder à une variable volatile peut être fait sans peine, qu'il doit y avoir un moyen de s'assurer d'une écriture sur Un processeur sera synchronisé à une lecture sur le processeur B.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X