Bei Intel ist ein unkontrolliertes flüchtiges Lesen recht billig. Betrachten wir den folgenden einfachen Fall:
public static long l;
public static void run() {
if (l == -1)
System.exit(-1);
if (l == -2)
System.exit(-1);
}
Mit der Fähigkeit von Java 7, Assembler-Code zu drucken, sieht die Ausführungsmethode etwa so aus:
# {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
Wenn Sie sich die 2 Verweise auf getstatic ansehen, beinhaltet der erste ein Laden aus dem Speicher, der zweite überspringt das Laden, da der Wert aus den Registern wiederverwendet wird, in die er bereits geladen ist (long ist 64 Bit und auf meinem 32-Bit-Laptop verwendet er 2 Register).
Wenn wir die Variable l flüchtig machen, ist die resultierende Baugruppe anders.
# {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
In diesem Fall beinhalten beide getstatic-Verweise auf die Variable l ein Laden aus dem Speicher, d.h. der Wert kann nicht über mehrere flüchtige Lesevorgänge hinweg in einem Register gehalten werden. Um sicherzustellen, dass ein atomares Lesen erfolgt, wird der Wert aus dem Hauptspeicher in ein MMX-Register gelesen movsd 0x6fb7b2f0(%ebp),%xmm0
was den Lesevorgang zu einer einzigen Anweisung macht (aus dem vorherigen Beispiel haben wir gesehen, dass ein 64-Bit-Wert normalerweise zwei 32-Bit-Lesevorgänge auf einem 32-Bit-System erfordern würde).
Somit entsprechen die Gesamtkosten eines flüchtigen Lesevorgangs in etwa denen einer Speicherladung und können so günstig sein wie ein L1-Cache-Zugriff. Wenn jedoch ein anderer Kern in die flüchtige Variable schreibt, wird die Cache-Zeile ungültig und erfordert einen Hauptspeicher- oder vielleicht einen L3-Cache-Zugriff. Die tatsächlichen Kosten hängen stark von der CPU-Architektur ab. Sogar zwischen Intel und AMD sind die Cache-Kohärenzprotokolle unterschiedlich.
1 Stimmen
Sie können meinen Beitrag über die Konfiguration mit mehreren CPUs lesen, auf die Sie sich beziehen. Es kann vorkommen, dass auf Multi-CPU-Systemen für eine kurzzeitige Referenz nicht mehr als ein einziges Lesen/Schreiben im Hauptspeicher stattfindet.
2 Stimmen
Das flüchtige Lesen selbst ist nicht teuer. Die Hauptkosten liegen darin, dass es Optimierungen verhindert. In der Praxis sind diese Kosten im Durchschnitt auch nicht sehr hoch, es sei denn, flüchtiges Lesen wird in einer engen Schleife verwendet.
2 Stimmen
Dieser Artikel auf infoq ( infoq.com/articles/memory_barriers_jvm_concurrency ) könnte Sie auch interessieren. Es zeigt die Auswirkungen von volatile und synchronized auf den generierten Code für verschiedene Architekturen. Dies ist auch ein Fall, in dem der jvm besser abschneiden kann als ein "ahead of time"-Compiler, da er weiß, ob er auf einem Uniprozessorsystem läuft und einige Speicherbarrieren auslassen kann.