W przypadku Intela niekontrolowany, niestabilny odczyt jest dość tani. Jeśli weźmiemy pod uwagę następujący prosty przypadek:
public static long l;
public static void run() {
if (l == -1)
System.exit(-1);
if (l == -2)
System.exit(-1);
}
Używając możliwości Java 7 do drukowania kodu asemblera, metoda run wygląda mniej więcej tak:
# {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
Jeśli spojrzysz na 2 odwołania do getstatic, pierwsze dotyczy ładowania z pamięci, drugie pomija ładowanie, ponieważ wartość jest ponownie używana z rejestrów, do których jest już załadowany (long jest 64-bitowy i na moim laptopie 32-bitowym wykorzystuje 2 rejestry).
Jeśli zmienimy l zmienną jako ulotną, wynikowy zestaw będzie inny.
# {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
W tym przypadku oba odwołania getstatic do zmiennej l są ładowane z pamięci, tj. Wartość nie może być przechowywana w rejestrze podczas wielu ulotnych odczytów. Aby zapewnić atomowy odczyt, wartość jest odczytywana z pamięci głównej do rejestru MMX, movsd 0x6fb7b2f0(%ebp),%xmm0
dzięki czemu operacja odczytu jest pojedynczą instrukcją (z poprzedniego przykładu widzieliśmy, że wartość 64-bitowa normalnie wymagałaby dwóch 32-bitowych odczytów w systemie 32-bitowym).
Zatem całkowity koszt ulotnego odczytu będzie mniej więcej równy obciążeniu pamięci i może być tak tani jak dostęp do pamięci podręcznej L1. Jeśli jednak inny rdzeń zapisuje zmienną ulotną, linia pamięci podręcznej zostanie unieważniona, wymagając pamięci głównej lub być może dostępu do pamięci podręcznej L3. Rzeczywisty koszt będzie w dużej mierze zależał od architektury procesora. Nawet między Intelem a AMD protokoły spójności pamięci podręcznej są różne.