Heim > Artikel > System-Tutorial > Optimierung der Leistung auf x86-Pipeline-Ebene
Einführung | Wie wählt man den richtigen Weg, wenn man vor einem Ast steht? Wenn die Befehlsauswahl falsch ist, muss die gesamte Pipeline auf die Ausführung der verbleibenden Befehle warten, diese löschen und dann an der richtigen Position erneut beginnen. Je tiefer die Rohrleitung verläuft, desto größer ist der Schaden. |
Leistungsoptimierung, der Schlüssel liegt darin, die CPU gut zu versorgen. Als Programmierer, der die ultimative Leistung anstrebt, ist das Verständnis der internen Mechanismen der CPU ein unvermeidbares Thema. Dies ist ein kontinuierlicher Prozess, der im Laufe der Zeit eine Akkumulation erfordert, aber es ist nicht notwendig, tief in digitale Schaltkreise einzutauchen. Genauso wie ein Experte für das Entwerfen von CPUs nicht unbedingt über Kenntnisse im Software-Design verfügen muss, muss man auch kein CPU-Experte sein Hochleistungssoftware zu schreiben.
Als wertvolles Geschenk einer kleinen Gruppe menschlicher Eliten an die breite Öffentlichkeit repräsentieren CPUs, die nach Belieben auf dem Markt gekauft werden können, tatsächlich den modernsten technologischen Stand der Menschheit, genau wie Atomwaffen, die nicht gekauft werden können. Selbst ein x86-CPU-Experte kann nur im Detail darüber sprechen, worauf er spezialisiert ist. Obwohl es für uns unmöglich ist, alles zu verstehen, sind drei Teile sehr wichtig: Pipeline, Cache und Befehlssatz. Unter diesen drei Teilen kann das „Fließband“ als laufender Hinweis verwendet werden. Schauen wir uns daher zunächst, dem Beispiel aus dem vorherigen Artikel folgend, die Pipeline an.
Grundlegende Konzepte
Die Hauptaufgabe von PU besteht darin, Datenoperationen gemäß den Anweisungen durchzuführen. Dieser Satz erklärt im Grunde, was ein Fließband ist. Ich weiß, dass niemand, der auf diesen Artikel klicken kann, etwas über das Konzept des „Fließbandes“ weiß. Ich möchte nicht zu Beginn einen großen lehrbuchartigen Text auflisten und die Definitionen verschiedener Konzepte auflisten Verzichten Sie voll und ganz auf das Wesentliche und verfolgen Sie das Minderwertige. Die Entwicklung der Technologie ist nur eine Form der Bewegung widersprüchlicher Dinge. Dieses Mal werden wir versuchen, die verschiedenen Komponenten der Pipeline aus der Perspektive der historischen Entwicklung der CPU vorzustellen.
Seit Intel vor 40 Jahren den ersten 8086-Prozessor produzierte, bis heute haben die Veränderungen bei den CPUs das Gefühl vermittelt, dass frühere Prozessoren nur als „Ein-Chip-Computer“ bezeichnet werden können. Aber auch wenn es sich um einen Single-Chip-Mikrocomputer handelt, der auf Taobao nur ein paar Cent kostet, weist er dennoch einige Ähnlichkeiten mit dem heutigen i7-Prozessor auf. Der 8086-Prozessor verfügt über 14 Register, die auch heute noch verwendet werden: 4 Allzweckregister (General Purpose Register), 4 Segmentregister (Segment Register), 4 Indexregister (Index Register) und 1 Flag-Register (EFLAGS Register). Markieren Sie den CPU-Status, und das letzte, das Befehlszeigerregister, wird zum Speichern der Adresse des nächsten Befehls verwendet, der ausgeführt werden muss. Dieses Befehlszeigerregister steht in direktem Zusammenhang mit dem Betriebsprozess der Pipeline. Sein Fortbestehen zeigt auch die zeitliche Konsistenz der Grundprinzipien der Pipeline.
Von vor 40 Jahren bis heute folgen alle von der CPU ausgeführten Anweisungen dem folgenden Prozess: Die CPU erhält (Fetch) zunächst die Adresse der im Codesegment auszuführenden Anweisung basierend auf dem Anweisungszeiger und dekodiert (Decode). ) die Anweisung an der Adresse. Nach der Dekodierung gelangt es in die eigentliche Ausführungsphase (Execute), gefolgt von der „Write Back“-Phase, in der das Endergebnis der Verarbeitung in den Speicher oder das Register zurückgeschrieben wird und das Befehlszeigerregister aktualisiert wird, um auf das zu zeigen nächste Anweisung. Dies ist im Grunde eine Designlösung, die vollständig mit der menschlichen Logik übereinstimmt.
Zunächst und ganz natürlich verarbeitet die CPU alle Anweisungen nacheinander. Jede Anweisung wird gemäß dem obigen Prozess ausgeführt, und dann wird die nächste Anweisung ausgeführt. Der Hauptwiderspruch war damals der Widerspruch zwischen den wachsenden Leistungsanforderungen der Software und der rückläufigen CPU-Verarbeitungsgeschwindigkeit. Unter der richtigen Anleitung des Mooreschen Gesetzes hat die CPU-Konstruktionsarbeit historische Ergebnisse erzielt, und der Hauptwiderspruch hat sich verschoben: Die Ausführungsgeschwindigkeit der CPU hat langsam die Lese- und Schreibgeschwindigkeit des Speichers überschritten. Da es immer unerträglicher wurde, jedes Mal Anweisungen aus dem Speicher abzurufen, wurde 1982 ein Befehlscache im Prozessor eingeführt.
Da CPUs immer schneller werden, wird als Kompromiss zwischen den beiden Konfliktparteien auch das Daten-Caching im Prozessor eingeführt. Aber das sind keine dauerhaften Lösungen. Der Hauptaspekt des Widerspruchs besteht darin, dass die CPU nicht in der Sättigung läuft. Daher führte der i486-Prozessor 1989 konstruktiv eine fünfstufige Pipeline ein. Die Idee besteht darin, die überschüssige Kapazität der CPU zu verdauen, indem die Inlandsnachfrage angekurbelt wird: Statt jeweils nur einen Befehl zu verarbeiten, können fünf Befehle gleichzeitig verarbeitet werden.
Lassen Sie uns auf der Ebene der x86-Pipeline darüber sprechen, wie Sie die Leistung optimieren könnenIch weiß nicht, was du denkst, aber ich habe immer Schwierigkeiten, dieses Bild zu verstehen. Um ein einfaches Verständnis zu vermitteln: Stellen Sie sich jede Anweisung als ein zu verarbeitendes Produkt vor, das in ein Fließband mit 5 Verarbeitungsschritten fließt. Dadurch kann jeder Prozess der CPU stets eine gesättigte Arbeitslast aufrechterhalten, was den Befehlsdurchsatz und die Programmleistung grundlegend verbessert.
Durch das Fließband verursachte Probleme
Wenn Sie einfach jede Codezeile in eine XOR-Anweisung abstrahieren, gemäß dem i486-Pipeline-Diagramm oben, tritt die erste Anweisung in die Fetch-Stufe der Pipeline ein und dann in die D1-Stufe, zu diesem Zeitpunkt tritt die zweite Anweisung in die Fetch-Phase ein. Beim nächsten Maschinenzyklus geht der erste Befehl in D2, der zweite in D1 und der dritte Befehl wird abgerufen. Bisher ist alles normal, aber im nächsten Maschinenzyklus, wenn der erste Befehl in die Ausführungsphase eintritt, kann der zweite Befehl nicht weiter in die nächste Phase eintreten, da das Endergebnis der von ihm benötigten Variablen a im ersten sein muss erhalten werden, nachdem die Anweisung ausgeführt wurde. Daher wird die zweite Anweisung in der Pipeline blockiert und nicht fortgesetzt, bis die erste Anweisung abgeschlossen ist. Während der Ausführung der zweiten Anweisung kommt es bei der dritten Anweisung zu einer ähnlichen Begegnung. Wenn eine Pipeline-Blockierung auftritt, wird die Pipeline-Ausführung von Anweisungen von der individuellen Ausführung getrennt, was als Pipeline-„Blase“ bezeichnet wird.
Taktzyklus: auch Oszillationszyklus genannt. Es ist der Kehrwert der Taktfrequenz (Hauptfrequenz) und der Mindestzeitdauer
Maschinenzyklus: Jede Phase in der Pipeline wird als Grundvorgang bezeichnet, und die Zeit, die zum Abschließen eines Grundvorgangs erforderlich ist, ist der Maschinenzyklus
Befehlszyklus: Die zum Ausführen eines Befehls erforderliche Zeit, die im Allgemeinen aus mehreren Maschinenzyklen besteht
Zusätzlich zu den oben genannten Situationen gibt es einen weiteren häufigen Grund für die Blasenbildung. Die für die Ausführung jeder Anweisung erforderliche Zeit (Anweisungszyklus) ist unterschiedlich. Wenn einer einfachen Anweisung eine komplexe Anweisung vorausgeht, die lange dauert, muss die einfache Anweisung auf die komplexe Anweisung warten. Was ist außerdem, wenn es im Programm eine Verzweigung wie if gibt? Diese Situationen führen dazu, dass die Pipeline nicht mit voller Kapazität arbeiten kann, was zu einem relativen Leistungsabfall führt.
Wenn Menschen mit einem Problem konfrontiert werden, neigen sie immer dazu, einen komplexeren Mechanismus zur Lösung des Problems einzuführen. Das mehrstufige Fließband ist ein Beispiel. Komplexität kann technologische Verbesserungen widerspiegeln, aber „Komplexität“ selbst ist ein neues Problem. Das ist möglicherweise der Grund, warum Widersprüche niemals verschwinden werden und die Technologie niemals aufhören wird, sich weiterzuentwickeln. Aber „je mehr wir lernen, desto mehr verlieren wir für das Tao.“ Der immer komplexer werdende Mechanismus wird bei einer bestimmten Gelegenheit immer einen großen Durchbruch erleben, aber vielleicht ist die Zeit noch nicht gekommen. Angesichts des „Blasen“-Problems führte der Prozessor eine komplexere Lösung ein: Als Intel 1995 den Pentium Pro-Prozessor auf den Markt brachte, fügte er einen Out-of-Order-Kern (OOO-Kern) hinzu.
Out-of-Order-Ausführungskern (OOO-Kern)Tatsächlich ist die Idee der Out-of-Order-Ausführung sehr einfach: Wenn die nächste Anweisung blockiert ist, suchen Sie einfach eine andere ausführbare Anweisung aus den folgenden Anweisungen. Aber das umzusetzen ist ziemlich kompliziert. Zunächst muss sichergestellt werden, dass das Endergebnis des Programms mit der sequentiellen Ausführung übereinstimmt, und gleichzeitig müssen verschiedene Datenabhängigkeiten identifiziert werden. Um den gewünschten Effekt zu erzielen, muss neben der parallelen Ausführung auch die Granularität der Anweisungen weiter verfeinert werden, um den Effekt zu erzielen, dass keine Dicke verwendet wird, um den gewünschten Effekt zu erzielen. Auf diese Weise werden „Mikrooperationen“ (Mikrooperationen) erzielt , μ-ops) werden das Konzept von eingeführt. In der Dekodierphase der Pipeline werden die Montageanweisungen weiter zerlegt, und das Endprodukt ist eine Reihe von Mikrooperationen.
Einführung des Anweisungs-μ-Ops-Verarbeitungsablaufs nach dem Out-of-Order-Verarbeitungskern. Module unterschiedlicher Farbe entsprechen verschiedenfarbigen Pipeline-Verarbeitungsstufen im ersten Bild.
In der Abrufphase gibt es nicht viele Änderungen. In der Dekodierphase können vier Anweisungen parallel dekodiert werden, und das Endprodukt der Dekodierung sind die oben erwähnten μ-Ops. Die folgende Register-Alias-Tabelle und der Neuordnungspuffer können als Vorverarbeitungsstufe des Out-of-Order-Ausführungskerns betrachtet werden.
Bei parallel ausgeführten Mikrooperationen oder außerhalb der Reihenfolge ausgeführten Operationen ist es sehr wahrscheinlich, dass dasselbe Register gleichzeitig gelesen und geschrieben wird. Daher werden die ursprünglichen Register innerhalb des Prozessors als interne Register „aliased“, die für Software-Ingenieure unsichtbar sind, sodass die ursprünglich an demselben Register ausgeführten Operationen unabhängig davon, ob Lesen und Schreiben nicht miteinander in Konflikt geraten, vorübergehend an unterschiedlichen Registern ausgeführt werden können other (Hinweis: Dies erfordert, dass die beiden Vorgänge keine Datenabhängigkeiten aufweisen). Die Operanden der entsprechenden Mikrooperationen wurden ebenfalls in temporäre Alias-Register umgewandelt, was einer Raum-für-Zeit-Strategie entspricht, und gleichzeitig werden die Mikroanweisungen basierend auf den Alias-Registern übersetzt.
Dann gelangt die Mikrooperation in den Neuordnungspuffer. An diesem Punkt sind die Mikroanweisungen fertig. Sie werden in der Reservierungsstation (RS) abgelegt und parallel ausgeführt. Aus dem Diagramm können Sie einige Ausführungseinheiten (Port X) erkennen. Jede Ausführungseinheit führt eine bestimmte Aufgabe aus, z. B. Lesen (Laden), Schreiben (Speichern), Ganzzahlberechnung (ALU, SEE) usw. Jeder zugehörige Mikrobefehl kann ausgeführt werden, nachdem die benötigten Daten bereit sind. Obwohl solche langwierigen Anweisungen und Anweisungen mit Datenabhängigkeiten aus ihrer eigenen Sicht keine Änderung erfahren, wird der Blockierungsaufwand, den sie mit sich bringen, durch die Parallelität und die Außer-Reihenfolge (Vorlauf) der nachfolgenden Anweisungen amortisiert und aufgeschlüsselt Teile, wodurch der Gesamtdurchsatz verbessert wird.
Die Magie des Out-of-Order-Execution-Kerns besteht darin, dass er die Effizienz dieses Mechanismus maximieren kann und Anweisungen von außen in der richtigen Reihenfolge ausgeführt werden. Die detaillierten Einzelheiten würden den Rahmen dieses Artikels sprengen. Aber der Out-of-Order-Execution-Kern ist so erfolgreich, dass selbst bei großer Arbeitslast der Out-of-Order-Execution-Kern der CPU, die diesen Mechanismus einführt, die meiste Zeit noch im Leerlauf ist und noch lange nicht ausgelastet ist. Daher wird ein weiteres Front-End (einschließlich Fetch und Decode) eingeführt, um μ-Ops an den Kern zu liefern. Aus Sicht des Systems kann es in zwei Verarbeitungskerne abstrahiert werden N physische Kerne und 2N logische Kerne.
Eine Ausführung außerhalb der Reihenfolge erzielt nicht unbedingt zu 100 % den Effekt einer sequentiellen Codeausführung. Manchmal müssen Programmierer Speicherbarrieren einführen, um die Ausführungsreihenfolge sicherzustellen.
Aber komplexe Dinge bringen immer neue Probleme mit sich. Diesmal wurde der Widerspruch auf die Abrufphase übertragen. Wie wählt man den richtigen Weg, wenn man vor einer Verzweigung steht? Wenn die Befehlsauswahl falsch ist, muss die gesamte Pipeline auf die Ausführung der verbleibenden Befehle warten, diese löschen und dann an der richtigen Position erneut beginnen. Je tiefer die Rohrleitung verläuft, desto größer ist der Schaden. In den folgenden Artikeln werden einige Optimierungsmethoden auf Programmierebene vorgestellt.
Autorenvorstellung
Zhang Pan, ein Yunshan-Netzwerkingenieur, konzentriert sich auf die Entwicklung und Leistungsoptimierung von x86-Netzwerksoftware. Er engagiert sich intensiv in ONF/OPNFV/ONOS und anderen Organisationen und Communities. Er war einst stellvertretender Vorsitzender der ONF-Testarbeitsgruppe.
Das obige ist der detaillierte Inhalt vonOptimierung der Leistung auf x86-Pipeline-Ebene. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!