Tatsächlich scheinen Menschen, die Java schreiben, nichts mit der CPU zu tun zu haben. Es hat höchstens etwas damit zu tun, wie man die CPU ausführt und wie wir die zuvor erwähnte Threads festlegen können. Dieser Algorithmus ist jedoch nur eine Referenz. Viele verschiedene Szenarien erfordern praktische Mittel, um es zu lösen. Nach dem Ausführen der CPU werden wir außerdem überlegen, wie die CPU nicht so voll ist. Haha, Menschen, das war's. Haha, ok, dieser Artikel handelt von anderen Dingen. Vielleicht schreiben Sie fast keinen Code in Java. Achten Sie auf die CPU, denn die Befriedigung des Geschäfts ist das erste Wichtige. Wenn Sie die Framework -Ebene erreichen und dem Framework mit vielen gemeinsam genutzten Daten -Caches bereitstellen möchten, müssen in der Mitte viele Probleme mit der Datenanforderung bestehen. Natürlich bietet Java viele Klassen von gleichzeitigen Paketen an, und Sie können es verwenden, aber wie es intern gemacht wird, müssen Sie die Details verstehen, um sie besser zu verwenden, andernfalls ist es besser, sie nicht zu verwenden. Dieser Artikel erklärt diesen Inhalt möglicherweise nicht als Fokus, denn wie die Titelparty: Wir wollen über CPU sprechen, haha.
Das Gleiche heißt, es scheint, dass Java nichts mit CPU zu tun hat. Lassen Sie uns also darüber sprechen, was jetzt vor sich geht.
1. Bei der Begegnung mit einem gemeinsamen Element besteht unsere erste Idee darin, konsistente Lesevorgänge durch volatile, dh absolute Sichtbarkeit zu gewährleisten. Die sogenannte Sichtbarkeit bedeutet, dass die CPU jedes Mal, wenn Sie diese Daten verwenden möchten, keinen Cache-Inhalt verwenden und Daten aus dem Speicher abrufen. Dieser Prozess ist für mehrere CPUs weiterhin gültig, was bedeutet, dass die CPU und der Speicher zu diesem Zeitpunkt synchronisiert sind. Die CPU wird eine Montageanweisung ausgeben, die addl 0 ähnelt, wie ein Bus, +0, aber nichts in Bezug auf irgendetwas tun. Sobald die Anweisung jedoch abgeschlossen ist, wirkt sich die nachfolgenden Operationen nicht mehr auf den Zugriff anderer Threads dieses Elements aus, was die absolute Sichtbarkeit ist, die sie erreichen kann, sondern keine konsistenten Vorgänge implementieren kann. Das heißt, was volatile nicht erreichen kann, ist die Konsistenz von Operationen wie i ++ (Parallelität unter mehreren Threads), da i ++ Operationen zerlegt werden in:
int tmp = i; tmp = tmp + 1; i = tmp;
Diese drei Schritte sind abgeschlossen. Ab diesem Zeitpunkt können Sie auch sehen, warum ich zuerst andere Dinge tun und dann 1 zu sich hinzufügen kann, da ihm der Wert einer anderen Variablen zugeordnet wird.
2. Wenn wir die Konsistenz mit Multi-Thread-Parallelität verwenden möchten, müssen wir den Sperrmechanismus verwenden. Gegenwärtig können Dinge wie Atomic* diese Anforderungen im Grunde erfüllen. Viele unsichere Klassenmethoden werden intern bereitgestellt. Durch ständiges Vergleich der Absolute -Sichtbarkeitsdaten können wir sicherstellen, dass die erfassten Daten auf dem neuesten Stand sind. Als nächstes werden wir weiterhin über andere CPU -Angelegenheiten sprechen.
3. In der Vergangenheit konnten wir die CPU nicht ausführen, um sie zu füllen, aber wir waren nicht zufrieden, egal wie wir die Verzögerung zwischen Speicher und CPU ignorieren. Da wir es heute erwähnt haben, werden wir kurz über die Verzögerung sprechen. Im Allgemeinen verfügt die aktuelle CPU über drei Ebenen, und die Verzögerungen sind in verschiedenen Altersgruppen unterschiedlich, sodass die spezifische Zahl nur ungefähr gerecht sein kann. Der heutige CPUs hat im Allgemeinen eine Verzögerung von 1-2ns, der Cache der zweiten Stufe beträgt im Allgemeinen ein paar NS bis etwa zehn Ns, und der Cache der dritten Ebene liegt im Allgemeinen zwischen 30 ns und 50 ns, und der Speicherzugriff erreicht im Allgemeinen 70 ns oder sogar mehr (der Computer entwickelt sich sehr schnell, und dieser Wert ist nur für die Daten zu einigen CPUs für einen Reichweite). Obwohl diese Verzögerung sehr klein ist, ist alles auf Nanosekundenebene, dass Sie bei Aufteilungen Ihres Programms in Anleitungsvorgänge aufgeteilt werden, es gibt viele CPU -Interaktionen. Wenn die Verzögerung jeder Interaktion so groß ist, ändert sich die Systemleistung zu diesem Zeitpunkt.
4. Gehen Sie zurück zum flüchtigen Erwähnten. Jedes Mal, wenn es Daten aus dem Speicher erhält, verlässt es den Cache. Wenn es in einigen Einthreadoperationen langsamer wird, wird es natürlich langsamer. Manchmal müssen wir das tun. Sogar Lese- und Schreibvorgänge erfordern Konsistenz, und selbst der gesamte Datenblock wird synchronisiert. Wir können die Granularität des Schlosses nur bis zu einem gewissen Grad reduzieren, aber wir können überhaupt keine Schlösser haben. Sogar die CPU -Ebene selbst hat Anleitungsebenenbeschränkungen.
5. Atomare Operationen auf CPU -Ebene werden im Allgemeinen als Barrieren bezeichnet, mit Lesebarrieren, Schreibbarrieren usw. Sie werden im Allgemeinen um einen Punkt ausgelöst. Wenn mehrere Anweisungen des Programms an die CPU gesendet werden, werden einige Anweisungen möglicherweise nicht in der Reihenfolge des Programms ausgeführt, und einige müssen in der Reihenfolge des Programms ausgeführt werden, solange sie in der endgültigen Reihenfolge des Programms garantiert konsistent sind. In Bezug auf die Sortierung wird sich JIT während der Laufzeit ändern, und auch die CPU -Anweisungsstufe ändert sich. Der Hauptgrund ist, die Laufzeitanweisungen zu optimieren, um das Programm schneller laufen zu lassen.
6. Die CPU -Ebene betreibt die Cache -Linie im Speicher. Die sogenannte Cache-Linie liest kontinuierlich ein Stück Speicher, das im Allgemeinen mit dem CPU-Modell und der Architektur zusammenhängt. Heutzutage lesen viele CPUs jedes Mal das kontinuierliche Gedächtnis, und die frühen haben 32Byte, sodass es beim Durchqueren einiger Arrays schneller ist (es ist sehr langsam, basierend auf Spaltenquellen), dies ist jedoch nicht vollständig korrekt. Im Folgenden wird einige entgegengesetzte Situationen verglichen.
7. Wenn die CPU die Daten ändert, müssen wir über den Status der CPU sprechen und die Daten ändern. Wenn alle Daten gelesen werden, kann sie parallel durch mehrere Threads unter mehreren CPUs gelesen werden. Beim Schreiben von Vorgängen auf den Datenblöcken ist dies anders. Die Datenblöcke haben exklusiv, modifiziert, Ungültigkeit und andere Zustände, und die Daten werden nach der Änderung natürlich fehlschlagen. Wenn mehrere Threads denselben Datenblock unter mehreren CPUs ändern, tritt die Busdatenkopie (QPI) zwischen dem CPUs auf. Wenn wir es an denselben Daten ändern, haben wir natürlich keine andere Wahl, aber wenn wir in Punkt 6 zur Cache -Zeile zurückkehren, ist das Problem problematischer. Wenn sich die Daten auf demselben Array befinden und die Elemente im Array gleichzeitig in eine CPU zwischenstrichen werden, ist der QPI von Multi-Threads sehr häufig. Manchmal tritt dieses Problem auch dann auf, wenn die auf dem Array zusammengestellten Objekte zusammengebaut sind, z. B.:
class InputItinger {private int value; public InputIntEger (int i) {this.value = i;}} InputIntEger [] Integers = new InputItinger [Größe]; für (int i = 0; i <size; i ++) {Intarmer [i] = new InputIntinger (i);};};};};};};}; Zu diesem Zeitpunkt können Sie sehen, dass alles in Ganzzahlen Objekte sind, und es gibt nur Hinweise auf Objekte im Array, aber die Anordnung von Objekten ist theoretisch unabhängig und wird nicht kontinuierlich gespeichert. Wenn Java jedoch das Objektgedächtnis zuteilt, wird er häufig im Edenbereich kontinuierlich zugewiesen. Wenn in der für Schleife auf keine anderen Threads zugegriffen werden, werden diese Objekte zusammen gespeichert. Auch wenn sie in der alten Gegend GC sind, wird es sehr wahrscheinlich zusammengestellt. Daher scheint die Art und Weise, wie das gesamte Array durch einfache Objekte zur Lösung der Cache -Zeile stützt, unzuverlässig, da sie 4 Bytes sind. Wenn im 64 -Modus diese Größe 24 Bytes beträgt (4bytes sind gefüllt), und die Zeigerkomprimierung 16 Bytes; Das heißt, die CPU kann jedes Mal mit 3-4 Objekten übereinstimmen. So erstellen Sie den CPU -Cache, aber es wirkt sich nicht auf das QPI des Systems aus. Denken Sie nicht daran, es zu vervollständigen, indem Sie die Objekte trennen, da der GC -Prozessspeicherprozess wahrscheinlich zusammen kopiert wird. Der beste Weg ist es, es zu füllen. Obwohl es sich um ein Speicherabfall handelt, ist dies die zuverlässigste Methode, bei der das Objekt in 64 Bytes füllen soll. Wenn die Zeigerkomprimierung nicht aktiviert ist, gibt es 24 Bytes und es gibt zu diesem Zeitpunkt 40 Bytes. Sie müssen nur 5 Longs im Objekt hinzufügen.
Klasse InputIntinger {public int value; private long a1, a2, a3, a4, a5;} Haha, diese Methode ist sehr rustikal, aber sie funktioniert sehr gut. Manchmal, wenn JVM kompiliert wird, stellt es manchmal fest, dass diese Parameter nicht durchgeführt wurden, sodass es direkt für Sie getötet wird. Die Optimierung ist ungültig. Die Methode plus die Methode besteht darin, diese 5 Parameter einfach in einem Methodenkörper zu betreiben (verwendet sie alle), aber diese Methode wird sie niemals aufrufen.
8. Auf der CPU -Ebene ist es manchmal nicht möglich, das erste zu tun. Es ist der König. Wenn Sie Getanteset (True) in einem einzigen Thread anrufen, werden Sie feststellen, dass es bei der Operation von Atomicintegerfieldupdater feststellen, dass es ziemlich schnell läuft und sich unter einer Multi-Core-CPU verlangsamt. Warum wird es deutlich oben gesagt? Da GetAndset modifiziert und verglichen wird und es dann zuerst ändern wird, ist der QPI sehr hoch. Zu diesem Zeitpunkt ist es zuerst besser, zuerst Operationen zu erhalten und es dann zu ändern. Und es ist auch ein guter Weg, es einmal zu bekommen. Wenn es nicht erhalten werden kann, geben Sie nach und lassen Sie andere Themen andere Dinge tun;
9. Manchmal, um das Problem einer geschäftigen CPU und nicht beschäftigt zu lösen, gibt es viele Algorithmen zu lösen. Zum Beispiel ist Numa eine der Lösungen. Unabhängig davon, welche Architektur in bestimmten Szenarien nützlicher ist, ist sie möglicherweise nicht für alle Szenarien wirksam. Es gibt einen Warteschlangenmechanismus, um das CPU -Staatsmanagement zu vervollständigen, aber dies hat auch das Problem der Cache -Linie, da sich der Zustand häufig ändert und die Kerne verschiedener Anwendungen auch einige Algorithmen erzeugen, um mit der CPU zusammenzuarbeiten, damit die CPU effektiver verwendet werden kann, z. B. CLH -Queue.
Es gibt viele Details dazu, wie die Überlagerung gewöhnlicher variabler Schleifen, flüchtigen Typen und atomarer* -serien, die völlig unterschiedlich sind. Mehrdimensionale Array-Schleifen, die in verschiedenen Breiten nach hinten nach hinten schleifen, und es gibt viele Details, und ich verstehe, warum der tatsächliche Optimierungsprozess inspiriert ist. Die Details der Schlösser sind zu dünn und schwindelig, und auf der unteren Ebene des Systems gibt es immer einige leichte atomare Operationen. Egal, wer sagt, dass sein Code kein Sperren erfordert, das Beste kann so einfach sein, wie die CPU nur in jedem Moment eine Anweisung ausführen kann. Multi-Core-CPUs verfügt auch über einen gemeinsamen Bereich, um einige Inhalte auf der Busebene zu steuern, einschließlich Lesestufe, Schreibebene, Speicherebene usw. In verschiedenen Szenarien wird die Granularität des Schlosses so weit wie möglich reduziert. Die Leistung des Systems ist selbstverständlich und ein normales Ergebnis.