16

Kausalität in JMM scheint der verwirrendste Teil davon zu sein. Ich habe einige Fragen bezüglich der JMM-Kausalität und erlaubte Verhaltensweisen in gleichzeitigen Programmen.Warum ist dieses Verhalten im Java Memory Model erlaubt?

Wie ich verstehe, verbietet das aktuelle JMM immer Kausalitätsschleifen. (Habe ich recht?)

nun gemäß dem JSR-133 Dokument, Seite 24, Abb.16, haben wir ein Beispiel, wo:

Anfangs x = y = 0

Gewinde 1:

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

Thread 2:

r2 = y; 
x = r2; 

Intuitiv scheint r1 = r2 = r3 = 42 unmöglich. Es wird jedoch nicht nur als möglich erwähnt, sondern auch in JMM erlaubt.

Für die Möglichkeit, die Erklärung aus dem Dokument, das ich nicht verstehen ist,:

Ein Compiler bestimmen könnte, dass die einzigen Werte je zugewiesen x sind 0 und 42. Von dass der Compiler konnte ableiten, dass an dem Punkt, wo wir r1 = x ausführen, entweder wir hatten gerade eine Schreib von 42 bis x durchgeführt wird, oder wir hatten gerade gelesen x und den Wert 42. In jedem Fall gesehen, es würde für eine Lese legal von x, um den Wertzu sehen42. Es könnte sich dann ändern r1 = x zu ; dies würde ermöglichen, y = r1 in y = 42 umgewandelt und früher durchgeführt werden, was zu dem Verhalten in Frage. In diesem Fall wird der Schreibvorgang auf y zuerst festgeschrieben.

Meine Frage ist, welche Art von Compiler-Optimierung ist es wirklich? (Ich bin Compiler-Ignorant.) Da 42 nur bedingt geschrieben wird, wenn die if-Anweisung erfüllt ist, wie kann der Compiler entscheiden, mit dem Schreiben von x zu gehen? Zweitens

, auch wenn Compiler erledigt diese spekulative Optimierung und begeht y = 42 und dann schließlich macht r3 = 42, ist es nicht eine Verletzung der Kausalität Schleife, da es keine Ursache und Wirkung unterscheiden verlassen nun?

Tatsächlich gibt es ein Beispiel im selben Dokument (Seite 15, Abbildung 7), wo eine ähnliche Kausalschleife als inakzeptabel erwähnt wird.

Wie kommt es, dass diese Ausführungsreihenfolge in JMM zulässig ist?

Antwort

6

Wie bereits erläutert, sind die einzigen Werte jemals geschrieben x sind 0 und 42 Gewinde 1:

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

daher der JIT-Compiler r1 = x als r1 = 42 und weiter y = 42 umschreiben kann.Der Punkt ist, Thread 1 wird immer, unbedingt schreiben 42 bis y. Die Variable r3 ist in der Tat redundant und könnte vollständig aus dem Maschinencode eliminiert werden. So gibt der Code im Beispiel nur den Anschein eines kausalen Pfeils von x bis y, aber eine detaillierte Analyse zeigt, dass es tatsächlich keine Kausalität gibt. Die überraschende Konsequenz ist, dass der Schreibvorgang zu y früh begangen werden kann.

Ein allgemeiner Hinweis zur Optimierung: Ich nehme an, Sie sind mit Leistungseinbußen beim Lesen aus dem Arbeitsspeicher vertraut. Das ist der Grund, warum der JIT-Compiler darauf abzielt, dies zu tun, wann immer es möglich ist, und in diesem Beispiel stellt es sich heraus, dass er x nicht lesen muss, um zu wissen, was in y geschrieben werden soll.

Ein allgemeiner Hinweis auf Notation: r1, r2, r3 sind lokale Variablen (sie auf dem Stapel oder in CPU-Register sein könnte); x, y sind shared Variablen (diese sind im Hauptspeicher). Ohne dies zu berücksichtigen, werden die Beispiele keinen Sinn ergeben.

1

Es ist nichts wert, dass die javac den Code in einem signifikanten Grad nicht optimiert. Die JIT optimiert den Code, ist jedoch ziemlich konservativ, wenn Code neu angeordnet wird. Die CPU kann die Ausführung neu anordnen, und dies geschieht in geringem Maße ganz.

Das Erzwingen der CPU, keine Befehlsebenenoptimierung durchzuführen, ist ziemlich teuer, z. es kann sich um einen Faktor von 10 oder mehr verlangsamen. AFAIK, die Java-Entwickler wollten das Minimum der Garantien spezifizieren, die benötigt würden, die auf den meisten CPUs effizient arbeiten würden.

3

Compiler kann einige Analysen und Optimierungen durchführt und mit folgendem Code für Thread1 Ende:

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

Für Single-Threaded-Ausführung, dieser Code auf den ursprünglichen Code entspricht und so ist legal. Wenn dann der Code von Thread2 zwischen Schritt 1 und Schritt2 ausgeführt wird (was gut möglich ist), dann wird auch r3 42 zugewiesen.

Die ganze Idee dieses Codebeispiels soll die Notwendigkeit einer korrekten Synchronisation demonstrieren.

+0

@Alexei Das erklärt einiges davon. Aber sollte der Compiler nicht "r3 = 0" statt "r3 = 42" machen? Oder sie zeigen nur "eine Möglichkeit"! – gaganbm

+2

Compiler macht 'r3 = 42' nicht, es lässt nur 'r3 = x' intakt. Die Compiler-Optimierung wird nicht immer bis zur maximalen Tiefe ausgeführt. Wenn die Wahrscheinlichkeit, dass die Optimierung die Korrektheit verletzt, minimal ist, wird sie aufgegeben. In dem angegebenen Code gibt es keine solchen Umstände, aber sie können auftreten, wenn ein anderer Code vorhanden ist. Außerdem kann der Compiler entscheiden, dass "r3 = 0" den gleichen Preis wie "r3 = x" hat. –

Verwandte Themen