Po przeczytaniu książki kucharskiej JSR-133 dla autorów kompilatorów o implementacji ulotnych, szczególnie sekcji "Interakcje z instrukcjami Atomic", zakładam, że odczyt zmiennej ulotnej bez aktualizacji wymaga LoadLoad lub bariery LoadStore. W dalszej części strony widzę, że LoadLoad i LoadStore skutecznie nie działają na procesorach X86. Czy to oznacza, że ulotne operacje odczytu mogą być wykonywane bez jawnego unieważnienia pamięci podręcznej na x86 i są tak szybkie, jak zwykły odczyt zmiennej (pomijając ograniczenia zmiany kolejności zmiennych)?
Myślę, że nie rozumiem tego poprawnie. Czy ktoś mógłby mnie oświecić?
EDYCJA: Zastanawiam się, czy istnieją różnice w środowiskach wieloprocesorowych. W systemach z jednym procesorem procesor może patrzeć na własne pamięci podręczne wątków, jak stwierdza John V., ale w systemach z wieloma procesorami musi być jakaś opcja konfiguracji dla procesorów, że to nie wystarczy i pamięć główna musi zostać uderzona, co powoduje wolniejsze działanie ulotności w systemach z wieloma procesorami, 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, udostępnię tutaj moje linki:
Odpowiedzi:
W przypadku Intela niekontrolowany, niestabilny odczyt jest dość tani. Jeśli weźmiemy pod uwagę następujący prosty przypadek:
Używając możliwości Java 7 do drukowania kodu asemblera, metoda run wygląda mniej więcej tak:
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.
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.
źródło
Ogólnie rzecz biorąc, w większości nowoczesnych procesorów obciążenie ulotne jest porównywalne z normalnym. Niestabilny sklep to około 1/3 czasu wejścia montiora / wyjścia monitora. Jest to widoczne w systemach, które są spójne z pamięcią podręczną.
Odpowiadając na pytanie OP, niestabilne zapisy są drogie, podczas gdy odczyty zwykle nie.
Tak, czasami podczas walidacji pola procesor może nawet nie trafić do pamięci głównej, zamiast tego szpieguje inne pamięci podręczne wątków i uzyskuje stamtąd wartość (bardzo ogólne wyjaśnienie).
Jednak popieram sugestię Neila, że jeśli masz pole, do którego ma dostęp wiele wątków, powinieneś zawijać je jako AtomicReference. Będąc AtomicReference, wykonuje z grubsza taką samą przepustowość dla odczytów / zapisów, ale jest również bardziej oczywiste, że pole będzie dostępne i modyfikowane przez wiele wątków.
Edytuj, aby odpowiedzieć na edycję OP:
Spójność pamięci podręcznej to trochę skomplikowany protokół, ale w skrócie: procesory będą dzielić wspólną linię pamięci podręcznej, która jest podłączona do pamięci głównej. Jeśli procesor ładuje pamięć, a żaden inny procesor jej nie ma, to procesor przyjmie, ż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 żądającego procesora - teraz żądający procesor ma kopię tej pamięci w swojej pamięci podręcznej procesora. (Nigdy nie musiał szukać w pamięci głównej w celach informacyjnych)
Jest więcej protokołów, ale to daje wyobrażenie o tym, co się dzieje. Odpowiadając również na inne pytanie, przy braku wielu procesorów, ulotne 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 niż z wieloma.
źródło
W słowach Java pamięci model (jak zdefiniowano dla Java 5+ w JSR 133), każda operacja - czytać lub pisać - na
volatile
zmiennej tworzy się dzieje, zanim relacji w odniesieniu do jakiejkolwiek innej operacji na tej samej zmiennej. Oznacza to, że kompilator i JIT są zmuszone 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, wynikowy kod jest koniecznie wolniejszy niż byłby, chociaż prawdopodobnie nie bardzo.
Niemniej jednak nie powinieneś tworzyć zmiennej,
volatile
chyba że wiesz, że będzie ona dostępna z wielu wątków pozasynchronized
blokami. Mimo to należy rozważyć, czy lotny jest najlepszym wyborem w porównaniusynchronized
,AtomicReference
a jego przyjaciele, wyraźnychLock
klas itpźródło
Dostęp do zmiennej nietrwałej jest pod wieloma względami podobny do zawijania dostępu do zwykłej zmiennej w zsynchronizowanym bloku. Na przykład dostęp do zmiennej nietrwałej uniemożliwia procesorowi ponowne uporządkowanie instrukcji przed i po uzyskaniu dostępu, a to generalnie spowalnia wykonanie (chociaż nie mogę powiedzieć o ile).
Mówiąc bardziej ogólnie, w systemie wieloprocesorowym nie widzę, jak można uzyskać dostęp do zmiennej ulotnej bez ponoszenia kary - musi istnieć sposób, aby zapewnić, że zapis na procesorze A zostanie zsynchronizowany z odczytem na procesorze B.
źródło