Czy lotność jest droga?

Po przeczytaniu JSR-133 Cookbook for Compiler Writers o implementacji volatile, szczególnie sekcji "Interactions with Atomic Instructions" zakładam, że czytanie zmiennej volatile bez aktualizacji wymaga obciążenia lub bariery LoadStore. W dalszej części strony widzę, że LoadLoad i LoadStore faktycznie nie działają na procesorach X86. Czy to oznacza, że lotne operacje odczytu mogą być wykonywane bez jawnego unieważniania pamięci podręcznej na x86 i jest tak szybkie, jak normalne odczyt zmiennych(pomijając ograniczenia zmiany kolejności lotnych)?

Wydaje mi się, że nie rozumiem tego poprawnie. Czy ktoś mógłby mnie oświecić?

EDIT: zastanawiam się, czy istnieją różnice w środowiskach wieloprocesorowych. W systemach z pojedynczym procesorem procesor może patrzeć na własne buforowanie wątków, jak stwierdza John V., ale w systemach z wieloma procesorami musi być jakaś opcja konfiguracji procesorów, że to nie wystarczy i pamięć główna musi zostać uderzona, co wolniej działa na wielu procesorach systemy, prawda?

PS: w drodze, aby dowiedzieć się więcej na ten temat, natknąłem się na następujące świetne artykuły, a ponieważ to pytanie może być interesujące dla innych, podzielę się moimi linkami tutaj:

Author: reevesy, 2011-01-08

4 answers

W Intelu niezarejestrowany 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 Javy 7 do drukowania kodu assembly 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, pierwsza dotyczy obciążenia z pamięci, druga pomija obciążenie, ponieważ wartość jest ponownie używana z rejestru(s), do którego jest już załadowana (long to 64 bit i na moim 32-bitowym laptopie używa 2 rejestrów).

Jeśli zrobimy l zmienna lotna otrzymany zespół jest 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 wiążą się z obciążeniem z pamięci, tzn. wartość nie może być przechowywana w rejestrze podczas wielu odczytów lotnych. Aby upewnić się, że jest odczyt atomowy, wartość jest odczytywana z pamięci głównej do rejestru MMX movsd 0x6fb7b2f0(%ebp),%xmm0, co sprawia, że operacja odczytu jest pojedynczą instrukcją (z poprzedniego przykładu widzieliśmy, że wartość 64-bitowa normalnie wymaga dwóch odczytów 32-bitowych na 32-bitowym system).

Tak więc całkowity koszt odczytu zmiennego 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 do zmiennej volatile, linia pamięci podręcznej zostanie unieważniona, wymagając dostępu do pamięci głównej lub L3. Rzeczywisty koszt będzie zależał w dużej mierze od architektury procesora. Nawet między Intel i AMD protokoły Cache są różne.

 111
Author: Michael Barker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-22 14:51:36

Ogólnie rzecz biorąc, w większości nowoczesnych procesorów obciążenie lotne jest porównywalne z obciążeniem normalnym. Lotny sklep to około 1/3 czasu montior-enter / monitor-exit. Jest to widoczne w systemach, które są spójne w pamięci podręcznej.

Odpowiadając na pytanie OP, zmienne zapisy są drogie, podczas gdy odczyty zwykle nie są.

Czy to oznacza, że zmienny odczyt operacje można wykonywać bez jawne unieważnienie pamięci podręcznej na x86, i jest szybki a normalny odczyt zmiennej (pomijając zmianę kolejności contraints of volatile)?

Tak, Czasami podczas walidacji pola procesor może nawet nie trafić w pamięć główną, zamiast tego szpiegować inne pamięci podręczne wątków i pobierać z nich wartość (bardzo ogólne wyjaśnienie).

Popieram jednak sugestię Neila, że jeśli masz pole dostępne przez wiele wątków, możesz je zawinąć jako Atomikreferencję. Jako AtomicReference wykonuje mniej więcej taką samą przepustowość dla odczytu/zapisu, ale jest również bardziej oczywiste, że pole będzie dostępne i modyfikowane przez wiele wątków.

Edit to answer op ' s edit:

Spójność pamięci podręcznej jest nieco skomplikowanym protokołem, ale w skrócie: procesory będą miały wspólną linię pamięci podręcznej, która jest dołączona do pamięci głównej. Jeśli procesor ładuje pamięć i żaden inny procesor jej nie miał, to CPU założy, że jest to najbardziej aktualna wartość. Jeśli inny procesor spróbuje załadować tę samą lokalizację pamięci, już załadowany procesor będzie tego świadomy i faktycznie udostępni buforowane odniesienie do procesor żądający - teraz procesor żądający ma kopię tej pamięci w pamięci podręcznej procesora. (Nigdy nie musiał szukać referencji w pamięci głównej)

Jest trochę więcej protokołów, ale to daje wyobrażenie o tym, co się dzieje. Również, aby odpowiedzieć na inne pytanie, przy braku wielu procesorów, lotne odczyty/zapisy mogą być w rzeczywistości szybsze niż w przypadku wielu procesorów. Istnieją aplikacje, które w rzeczywistości działałyby szybciej jednocześnie z jednym procesorem, a następnie wiele.

 20
Author: John Vint,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-08 19:26:28

W słowach modelu pamięci Java (zdefiniowanego dla Java 5+ w JSR 133), każda operacja-odczyt lub zapis-na zmiennej volatile tworzy relacjęhappens-before w odniesieniu do każdej innej operacji na tej samej zmiennej. Oznacza to, że kompilator i JIT są zmuszeni unikać pewnych optymalizacji, takich jak zmiana kolejności instrukcji w wątku lub wykonywanie operacji tylko w lokalnej pamięci podręcznej.

Ponieważ niektóre optymalizacje nie są dostępne, otrzymany kod jest koniecznie wolniejszy niż by był, choć chyba nie bardzo.

Niemniej jednak nie powinieneś tworzyć zmiennej volatile, chyba że wiesz, że będzie ona dostępna z wielu wątków poza blokami synchronized. Nawet wtedy należy zastanowić się, czy Lotny jest najlepszym wyborem wobec synchronized, AtomicReference i jego przyjaciół, klas jawnych Lock, itp.

 11
Author: Neil Bartlett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-08 15:37:56

Dostęp do zmiennej zmiennej jest pod wieloma względami podobny do zawijania dostępu do zwykłej zmiennej w zsynchronizowanym bloku. Na przykład, dostęp do zmiennej volatile uniemożliwia procesorowi ponowne uporządkowanie instrukcji przed i po dostępie, a to generalnie spowalnia wykonywanie (choć nie mogę powiedzieć o ile).

Ogólnie rzecz biorąc, na systemie wieloprocesorowym nie widzę, jak można bezkarnie uzyskać dostęp do zmiennej zmiennej -- musi być jakiś sposób, aby zapewnić, że zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zmienna zapis na procesorze a zostanie zsynchronizowany z odczytem na procesorze B.

 4
Author: krakover,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-08 13:08:40