16

Przyczynowość w JMM wydaje się być najbardziej zagmatwaną częścią tego. Mam kilka pytań dotyczących związku przyczynowego JMM i dozwolonych zachowań w programach współbieżnych.Dlaczego to zachowanie jest dozwolone w modelu pamięci Java?

Jak rozumiem, obecne JMM zawsze zabrania pętli przyczynowości. (Mam rację?)

Teraz, jak na dokumencie JSR-133, strona 24, Rys.16 mamy przykład, w którym:

Początkowo x = y = 0

gwintu 1:

r3 = x; 
if (r3 == 0) 
    x = 42; 
r1 = x; 
y = r1; 

gwintu 2:

r2 = y; 
x = r2; 

Intuicyjnie, r1 = r2 = r3 = 42 wydaje się niemożliwe. Jednak jest to nie tylko wymieniane jako możliwe, ale także "dozwolone" w JMM.

możliwość, wyjaśnienie od dokumentu, który nie rozumiem jest:

Kompilator może określić, że tylko wartości zawsze są przypisane do x 0 i 42. Z tego, kompilator mógł Wydedukować, że w punkcie , gdzie wykonaliśmy r1 = x, albo właśnie wykonaliśmy zapis 42 do x, albo właśnie przeczytaliśmy x i zobaczyliśmy wartość 42. W każdym przypadku to byłoby legalne dla odczytu z x, aby zobaczyć wartość 42. Może następnie zmienić r1 = x na r1 = 42; pozwoliłoby to na przekształcenie y = r1 na y = 42 i wykonano wcześniej, powodując zachowanie w postaci . W takim przypadku zapis do y jest najpierw zatwierdzony jako .

Moje pytanie brzmi, jaki rodzaj optymalizacji kompilacji jest naprawdę? (Jestem ignorantem kompilatora.) Ponieważ 42 jest napisane tylko warunkowo, gdy stwierdzenie if jest spełnione, w jaki sposób kompilator może zdecydować się na napisanie x?

Po drugie, nawet jeśli robi to kompilator optymalizacji spekulacyjnego oraz zobowiązuje y = 42 i wreszcie sprawia r3 = 42, nie jest to naruszenie pętli przyczynowości, ponieważ nie ma przyczyny i skutku rozróżnienie lewo teraz?

W rzeczywistości istnieje jeden przykład w tym samym dokumencie (strona 15, rysunek 7), w którym podobna pętla przyczynowa została wymieniona jako niedopuszczalna.

Jak to możliwe, że ten nakaz wykonania jest legalny w JMM?

Odpowiedz

6

Jak wyjaśniono, jedynymi wartościami, jakie kiedykolwiek napisano na x są 0 i 42. gwintu 1:

r3 = x; // here we read either 0 or 42 
if (r3 == 0) 
    x = 42; 
// at this point x is definitely 42 
r1 = x; 

Dlatego kompilator JIT może przepisać r1 = x jak r1 = 42 i dalszego y = 42.Chodzi o to, wątek 1 będzie zawsze zawsze, bezwarunkowo pisać 42 do y. Zmienna r3 jest w rzeczywistości nadmiarowa i może zostać całkowicie wyeliminowana z kodu maszynowego. Tak więc kod w tym przykładzie daje tylko wygląd strzałki przyczynowej od x do y, ale szczegółowa analiza pokazuje, że w rzeczywistości nie ma związku przyczynowego. Zaskakujące jest to, że zapis do y może zostać popełniony wcześniej.

Ogólna uwaga na temat optymalizacji: Zakładam, że znasz kary za wydajność związane z odczytywaniem z pamięci głównej. Dlatego kompilator JIT jest skłonny odmawiać robienia tego, gdy tylko jest to możliwe, iw tym przykładzie okazuje się, że w rzeczywistości nie trzeba czytać x, aby wiedzieć, co napisać do y.

Ogólna informacja dotycząca określenia: r1, r2, r3zmienne lokalne (może to być na stosie lub rejestrów CPU); x, yzmienne wspólne (są one w pamięci głównej). Bez wzięcia tego pod uwagę przykłady nie będą miały sensu.

1

Jego wartość jest niewystarczająca, aby javac nie zoptymalizował kodu w znaczącym stopniu. JIT optymalizuje kod, ale jest dość konserwatywny w zakresie ponownego zamawiania kodu. Procesor może ponownie zamówić wykonanie i robi to w małym stopniu dość dużo.

Wymuszenie, aby procesor nie optymalizował poziomu instrukcji jest dość drogi, np. może zwolnić go o współczynnik 10 lub więcej. AFAIK, projektanci Javy chcieli określić minimum wymaganych gwarancji, które działałyby wydajnie na większości procesorów.

3

kompilator może wykonywać kilka analiz i optymalizacji i kończy się po kod thread1:

y=42; // step 1 
r3=x; // step 2 
x=42; // step 3 

Na jednowątkowy wykonania, kod jest równoważne w oryginalnym kodzie, a więc jest legalny. Następnie, jeśli kod wątku 2 jest wykonywany między krokiem 1 i krokiem 2 (co jest całkiem możliwe), to r3 ma również przypisane 42.

Cała idea tej próbki kodu jest pokazanie potrzeby właściwej synchronizacji.

+0

@Alexei To wyjaśnia niektóre z nich. Ale czy kompilator nie powinien uczynić go 'r3 = 0' zamiast' r3 = 42'? Lub po prostu pokazują "możliwość"! – gaganbm

+2

Kompilator nie tworzy 'r3 = 42', po prostu pozostawia' r3 = x' w stanie nienaruszonym. Optymalizacja kompilatora nie zawsze jest wykonywana na maksymalnej głębokości. Jeśli istnieje minimalna możliwość, że optymalizacja może naruszyć poprawność, jest ona porzucana. W podanym kodzie nie ma takich okoliczności, ale mogą się pojawić, gdy obecny jest inny kod. Poza tym, kompilator może zdecydować, że 'r3 = 0' ma tę samą cenę co' r3 = x'. –

Powiązane problemy