Prozessorspeichermodell
Das sequenzielle Konsistenzspeichermodell ist ein theoretisches Referenzmodell. Das sequenzielle Konsistenzspeichermodell wird normalerweise als Referenz beim Entwurf von JMM- und Prozessorspeichermodellen verwendet. Das JMM- und Prozessorspeichermodell führt beim Entwurf zu einigen Lockerungen im sequentiellen Konsistenzmodell, denn wenn Prozessor und JMM vollständig gemäß dem sequentiellen Konsistenzmodell implementiert werden, werden viele Prozessor- und Compileroptimierungen verhindert, was sich nachteilig auf die Ausführungsleistung auswirkt wird einen großen Einfluss haben.
Entsprechend der Lockerung der Ausführungsreihenfolge verschiedener Arten von Lese-/Schreiboperationskombinationen können die Speichermodelle gängiger Prozessoren in die folgenden Typen unterteilt werden:
Lockerung der Reihenfolge der Schreib-Lese-Operationen in Das Programm führte zu dem Total Store Ordering Memory Model (kurz: TSO).
Basierend auf dem vorherigen Punkt 1 haben wir die Reihenfolge der Schreib-/Schreibvorgänge im Programm weiter gelockert und so das Speichermodell mit teilweiser Speicherreihenfolge (als PSO bezeichnet) erstellt.
Basierend auf den vorherigen Punkten 1 und 2 haben wir die Reihenfolge der Lese-/Schreib- und Lese-/Lesevorgänge im Programm weiter gelockert, was zum entspannten Speicherreihenfolge-Speichermodell (kurz RMO) und dem PowerPC-Speichermodell führte.
Beachten Sie, dass die Lockerung der Lese-/Schreibvorgänge durch den Prozessor hier auf der Prämisse basiert, dass zwischen den beiden Vorgängen keine Datenabhängigkeit besteht (da der Prozessor der Semantik „Als ob seriell“ entsprechen muss, ist dies beim Prozessor nicht der Fall). Zwei Speicheroperationen mit Datenabhängigkeiten werden neu angeordnet.
Die folgende Tabelle zeigt die detaillierten Eigenschaften gängiger Prozessor-Speichermodelle:
Name des Speichermodells
Entsprechender Prozessor
Store-Load-Neuordnung
Store-Store-Neuordnung
Load-Load und Load-Store-Neuordnung
Schreibvorgänge von anderen Prozessoren können früher gelesen werden
Schreibvorgänge vom aktuellen Prozessor können früher gelesen werden
TSO
sparc-TSOX64
Y
Y
PSO
sparc-PSO
J
J
J
RMO
ia64
J
J
J
J
PowerPC
PowerPC
Y
Y
Y
Y
Y
In dieser Tabelle können wir den gesamten Prozessorspeicher sehen. Die Modelle erlauben alle eine Schreib-Lese-Neuordnung aus dem in Kapitel 1 erläuterten Grund : Sie alle verwenden Schreibpuffer, was dazu führen kann, dass Schreib-Lese-Vorgänge neu angeordnet werden. Gleichzeitig können wir sehen, dass diese Prozessorspeichermodelle ein früheres Lesen der Schreibvorgänge des aktuellen Prozessors ermöglichen. Der Grund liegt auch im Schreib-Cache-Bereich: Da der Schreib-Cache-Bereich nur für den aktuellen Prozessor sichtbar ist, verursacht diese Funktion das Aktueller Prozessor, damit andere Prozessoren zuerst Schreibvorgänge sehen können, die vorübergehend in ihren eigenen Schreibpuffern gespeichert sind.
Die verschiedenen Prozessorspeichermodelle in der Tabelle oben, von oben nach unten, wechseln die Modelle von stark zu schwach. Je leistungsorientierter ein Prozessor ist, desto schwächer ist das Speichermodell. Denn diese Prozessoren möchten, dass das Speichermodell sie so wenig wie möglich einschränkt, damit sie möglichst viele Optimierungen zur Leistungssteigerung durchführen können.
Da das gemeinsame Prozessorspeichermodell schwächer als JMM ist, fügt der Java-Compiler beim Generieren von Bytecode an geeigneten Stellen in der Ausführungsbefehlssequenz Speicherbarrieren ein, um die Neuordnung des Prozessors zu begrenzen. Da die Speichermodelle verschiedener Prozessoren unterschiedliche Stärken und Schwächen aufweisen, variieren gleichzeitig auch die Anzahl und Art der Speicherbarrieren, die JMM in verschiedene Prozessoren einfügen muss, um Programmierern auf verschiedenen Prozessorplattformen ein konsistentes Speichermodell zu präsentieren . Nicht dasselbe. Die folgende Abbildung zeigt ein schematisches Diagramm der Speicherbarrieren, die JMM in verschiedene Prozessorspeichermodelle einfügen muss:
Wie in der Abbildung oben gezeigt, maskiert JMM die Unterschiede in verschiedenen Prozessorspeichermodelle, die Java-Programmierern ein konsistentes Speichermodell für verschiedene Prozessorplattformen bieten.
JMM, die Beziehung zwischen dem Prozessorspeichermodell und dem sequentiellen Konsistenzspeichermodell
JMM ist ein Speichermodell auf Sprachebene, und das Prozessorspeichermodell ist ein Speichermodell auf Hardwareebene, sequentiell konsistent Das sexuelle Gedächtnismodell ist ein theoretisches Referenzmodell. Das folgende Diagramm vergleicht die Stärken und Schwächen des Sprachspeichermodells, des Prozessorspeichermodells und des sequentiellen Konsistenzspeichermodells:
Aus der obigen Abbildung können wir ersehen, dass die vier gängigen Prozessorspeichermodelle schwächer sind als die drei häufig verwendeten Sprachspeichermodelle. Sowohl das Prozessorspeichermodell als auch das Sprachspeichermodell sind schwächer als das sequentielle Konsistenzspeichermodell. Wie beim Prozessor-Speichermodell gilt: Je mehr eine Sprache die Ausführungsleistung anstrebt, desto schwächer wird das Design des Speichermodells sein.
Design von JMM
Aus der Sicht eines JMM-Designers müssen beim Entwurf eines JMM zwei Schlüsselfaktoren berücksichtigt werden:
Die Verwendung des Speichermodells durch den Programmierer. Programmierer wünschen sich Speichermodelle, die leicht zu verstehen und leicht zu programmieren sind. Programmierer möchten Code schreiben, der auf einem starken Speichermodell basiert.
Compiler- und Prozessorimplementierung des Speichermodells. Compiler und Prozessoren möchten, dass das Speichermodell sie so wenig wie möglich einschränkt, damit sie so viele Optimierungen wie möglich durchführen können, um die Leistung zu verbessern. Compiler und Prozessoren möchten ein schwaches Speichermodell implementieren.
Da diese beiden Faktoren widersprüchlich sind, besteht das Hauptziel der JSR-133-Expertengruppe beim Entwurf von JMM darin, einen guten Gleichgewichtspunkt zu finden: Einerseits muss es Programmierern eine ausreichend starke Speichersichtbarkeitsgarantie bieten ; Andererseits sollten Compiler- und Prozessorbeschränkungen so weit wie möglich gelockert werden. Werfen wir einen Blick darauf, wie JSR-133 dieses Ziel erreicht.
Eine spezifische Erklärung finden Sie im zuvor erwähnten Beispielcode zur Berechnung der Fläche eines Kreises:
double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
Der obige Beispielcode zur Berechnung der Fläche eines Kreises hat Drei Beziehungen passieren:
A passiert – bevor B;
A passiert – bevor C; - Vorher erfordert Folgendes: Die Ausführung einer Operation sollte für B sichtbar sein und die Ausführungsreihenfolge von Operation A sollte vor Operation B liegen. Aus Sicht der Programmsemantik ändert die Neuordnung von A und B jedoch nicht die Ausführungsergebnisse des Programms, sondern kann auch die Ausführungsleistung des Programms verbessern (durch die Ermöglichung dieser Neuordnung werden die Einschränkungen bei der Compiler- und Prozessoroptimierung verringert). Mit anderen Worten, von den oben genannten drei Passiert-vorher-Beziehungen sind 2 und 3 zwar notwendig, 1 jedoch unnötig. Daher unterteilt JMM die durch Vorab-Anforderungen verbotene Neuordnung in die folgenden zwei Kategorien:
Neuordnung, die die Ergebnisse der Programmausführung ändert.
Eine Neuordnung hat keine Auswirkungen auf die Ergebnisse der Programmausführung.
JMM wendet unterschiedliche Strategien für diese beiden unterschiedlichen Arten der Neuordnung an:
Für eine Neuordnung, die die Ausführungsergebnisse des Programms verändert, verlangt JMM vom Compiler und Prozessor, eine solche Neuordnung zu verhindern.
JMM stellt keine Anforderungen an den Compiler und Prozessor für eine Neuordnung, die die Ausführungsergebnisse des Programms nicht verändert (JMM erlaubt eine solche Neuordnung).
Das Folgende ist das Designdiagramm von JMM:
Zwei Punkte sind aus dem obigen Bild ersichtlich: Die von bereitgestellten „Passed-Before“-Regeln JMM für Programmierer Kann die Anforderungen von Programmierern erfüllen. Die „Vorhergehensregeln“ von JMM sind nicht nur einfach und leicht zu verstehen, sondern bieten Programmierern auch ausreichend starke Garantien für die Speichersichtbarkeit (einige Garantien für die Speichersichtbarkeit müssen nicht unbedingt tatsächlich existieren, wie z. B. „A passiert vor B“ oben).
JMM hat so wenig Einschränkungen wie möglich für Compiler und Prozessoren. Aus der obigen Analyse können wir ersehen, dass JMM tatsächlich einem Grundprinzip folgt: Solange sich das Ausführungsergebnis des Programms nicht ändert (bezogen auf Single-Thread-Programme und korrekt synchronisierte Multi-Thread-Programme), können Compiler und Prozessor dies tun optimiert, egal wie sie optimiert sind. Wenn der Compiler beispielsweise nach sorgfältiger Analyse feststellt, dass auf eine Sperre nur von einem einzelnen Thread zugegriffen wird, kann die Sperre aufgehoben werden. Wenn der Compiler beispielsweise nach sorgfältiger Analyse feststellt, dass auf eine flüchtige Variable nur von einem einzelnen Thread zugegriffen wird, kann der Compiler die flüchtige Variable als gewöhnliche Variable behandeln. Diese Optimierungen ändern nicht die Ausführungsergebnisse des Programms, sondern verbessern auch die Ausführungseffizienz des Programms.
JMMs Speichersichtbarkeitsgarantie
Die Speichersichtbarkeitsgarantie von Java-Programmen kann je nach Programmtyp in die folgenden drei Kategorien unterteilt werden:
Single-Threaded-Programme. Single-Thread-Programme haben keine Probleme mit der Speichersichtbarkeit. Der Compiler, die Laufzeit und der Prozessor arbeiten zusammen, um sicherzustellen, dass die Ausführungsergebnisse eines Single-Threaded-Programms mit den Ausführungsergebnissen des Programms im sequentiellen Konsistenzmodell übereinstimmen.
Richtig synchronisierte Multithread-Programme. Die Ausführung eines ordnungsgemäß synchronisierten Multithread-Programms weist sequentielle Konsistenz auf (die Ergebnisse der Programmausführung sind dieselben, als ob das Programm in einem sequentiell konsistenten Speichermodell ausgeführt würde). Dies ist der Schwerpunkt von JMM, das Programmierern Garantien für die Speichersichtbarkeit bietet, indem es die Neuordnung von Compilern und Prozessoren begrenzt.
Unsynchronisierte/falsch synchronisierte Multithread-Programme. JMM bietet ihnen minimale Sicherheitsgarantien: Der bei der Ausführung eines Threads gelesene Wert ist entweder der von einem vorherigen Thread geschriebene Wert oder der Standardwert (0, null, falsch).
Die folgende Abbildung zeigt die Ähnlichkeiten und Unterschiede in den Ausführungsergebnissen dieser drei Arten von Programmen in JMM und im sequenziellen konsistenten Speichermodell:
Solange das Multithread-Programm korrekt synchronisiert ist, garantiert JMM, dass die Ausführungsergebnisse des Programms auf jeder Prozessorplattform mit den Ausführungsergebnissen des Programms im sequentiell konsistenten Speichermodell übereinstimmen.
Die Korrekturen von JSR-133 am alten Speichermodell vor JDK5 umfassen hauptsächlich zwei:
Verbesserung der Semantik des flüchtigen Speichers. Das alte Speichermodell ermöglichte die Neuordnung flüchtiger Variablen mit gewöhnlichen Variablen. JSR-133 schränkt die Neuordnung flüchtiger Variablen und gewöhnlicher Variablen streng ein, sodass flüchtiges Schreiben/Lesen und Lock-Release-Acquire dieselbe Speichersemantik haben.
Verbessern Sie die Gedächtnissemantik von final. Im alten Speichermodell kann der Wert derselben Endvariablen bei mehrmaligem Lesen unterschiedlich sein. Zu diesem Zweck fügt JSR-133 zwei Neuordnungsregeln für final hinzu. Final verfügt jetzt über Initialisierungssicherheit.
Das Obige ist die ausführliche Analyse des Java-Speichermodells: Zusammenfassung Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!