Am 16. Mai 2013 veröffentlichte GameDev.net eine technische Einführung in die CPU-Pipeline. Der Beitrag erklärt anhand der x86-Architektur, wie Instruktionen dekodiert, ausgeführt und wieder in ihrer ursprünglichen Reihenfolge abgeschlossen werden. Der Blick auf die Hardware richtet sich ausdrücklich auf Programmoptimierung und Spielecode.
Als Ausgangspunkt dient der Instruction Pointer, der auf den nächsten auszuführenden Befehl zeigt. Aus Sicht eines Programms bleibt der Prozessor eine Blackbox. Im Inneren werden Instruktionen jedoch in mehrere kleine Operationen zerlegt, über verschiedene Einheiten verteilt und später wieder geordnet zusammengesetzt.
Von Fetch bis Retirement
Die grundlegende Abfolge besteht aus dem Laden einer Instruktion aus dem Cache, dem Decodieren, der Ausführung und dem Zurückschreiben des Ergebnisses. Der i486 teilte diese Arbeit in fünf Stufen auf: Fetch, zwei Decodierstufen, Execute und Write Back. Dadurch konnten mehrere Instruktionen gleichzeitig in verschiedenen Stufen liegen. Der Ablauf blieb nach außen sequenziell, obwohl die Pipeline bereits parallel arbeitete.
Mit späteren x86-Prozessoren kamen größere Caches, zusätzliche Register und SIMD-Instruktionen hinzu. Der Artikel beschreibt auch die Entwicklung von einfachen Pipelines hin zu Out-of-Order-Kernen. Dabei werden Instruktionen in Micro-Ops zerlegt, internen Registern zugeordnet und in einem Reorder Buffer gesammelt. Der damalige Überblick nennt 128 Micro-Ops im Reorder Buffer und 36 Einträge in der Reservation Station als Beispiele für die Größenordnung moderner Intel-Kerne.
Out-of-Order-Ausführung hält Recheneinheiten beschäftigt
Die Ausführungseinheiten warten nicht zwingend auf die Instruktion, die im Programm als Nächstes steht. Ist eine Operation bereits mit ihren Daten versorgt, kann der Prozessor sie vorziehen. Das reduziert die Wartezeit durch langsame Speicherzugriffe. Beim Abschluss setzt der Reorder Buffer die Ergebnisse wieder in die Reihenfolge des ursprünglichen Programms. Die SIMD-Mathematik in einem Raytracer nutzt genau jene Nähe zu Registern und Rechenwerken, die bei solchen Optimierungen wichtig ist.
Sprungvorhersage verhindert Pipeline-Staus
Abhängige Instruktionen können eine Pipeline ausbremsen. Der Beitrag zeigt das mit einer Folge von XOR-Befehlen, bei der jeder Schritt auf das Ergebnis des vorherigen wartet. Ein solcher Pipeline-Stau erzeugt eine Lücke, bis die benötigten Daten bereitstehen. Bedingte Sprünge verschärfen das Problem, weil der Prozessor den weiteren Programmweg zunächst nicht sicher kennt.
Out-of-Order-Kerne versuchen deshalb, mehrere Pfade spekulativ auszuführen. Eine Sprungvorhersage entscheidet, welche Instruktionen vorab geladen werden. Fällt die Vorhersage falsch aus, verwirft der Prozessor die vorbereiteten Ergebnisse. Für Spielecode leitet der Artikel daraus einfache Regeln ab: Daten sollten möglichst zusammenhängend liegen, Abläufe sollten für den Compiler vorhersehbar bleiben und unnötig komplexe Sprungmuster können die Ausführung bremsen.
