Die CPU
Programmzähler, Befehlsregister, Registersatz, ALU, Speicher – und ein Steuerwerk, das in jedem Takt die richtigen Leitungen schaltet. Hier setzen sich die Bausteine aus Block H zur Lehrmaschine zusammen, und man sieht Befehl für Befehl, wie Holen, Dekodieren und Ausführen in Hardware aussehen.
Eine CPU besteht aus zwei Teilen. Der Datenpfad enthält die Bausteine, die Daten halten und verarbeiten: Programmzähler (PC), Befehlsregister (IR), Registersatz und ALU, verbunden mit dem Speicher durch Busse und Multiplexer. Das Steuerwerk ist ein Schaltwerk, das aus dem Opcode des aktuellen Befehls und der aktuellen Phase berechnet, welche Leitungen in diesem Takt aktiv sind: Welches Register wird gelesen, was rechnet die ALU, wohin wird geschrieben.
So wird jeder Befehl in Phasen ausgeführt: Holen (der Befehl an der Adresse im PC kommt ins IR, der PC zählt weiter), Dekodieren (das Steuerwerk liest den Opcode), Ausführen (die ausgewählten Bausteine arbeiten, das Ergebnis wird geschrieben). Moderne Prozessoren überlappen diese Phasen mehrerer Befehle – das Pipelining – und raten bei Sprüngen voraus, wohin es weitergeht.
Der Datenpfad der Lehrmaschine
Alle Teile kennen wir schon: Der PC ist ein 8-Bit-Register mit einem Addierer, der 1 dazuzählt (H4, H5). Der Speicher sind 256 Register mit Decoder (H5). Der Registersatz sind 16 Register, aus denen zwei Multiplexer gleichzeitig zwei Werte lesen und in das ein Decoder einen Wert schreibt. Die ALU rechnet (H4). Das IR hält den aktuellen Befehl, damit seine Bitfelder – Opcode, d, s, t, Adresse – während der Ausführung anliegen. Klick dich durch ein Programm aus H2, Phase für Phase:
Das Steuerwerk selbst ist ein Schaltwerk wie in H5: Ein kleines Register merkt sich die Phase, ein Schaltnetz berechnet aus Phase und Opcode die Steuersignale. Für 16 Opcodes und drei Phasen ist das eine Wahrheitstabelle mit 48 Zeilen und rund zwanzig Ausgängen – in Hardware wenige hundert Gatter. Bei komplizierten Befehlssätzen wie x86 werden manche Befehle intern zusätzlich in Folgen einfacherer Schritte zerlegt (Mikrocode, bei modernen Prozessoren Mikrooperationen).
Damit ist der Kreis von Block H geschlossen: Aus Bits (H1) werden Befehle (H2), aus Gattern (H3) Schaltnetze (H4) und Speicher (H5), und zusammen eine Maschine, die Programme ausführt – eine universelle Maschine im Sinne von G3.
Pipelining: mehrere Befehle gleichzeitig
Die Lehrmaschine erledigt einen Befehl nach dem anderen, jede Phase in einem eigenen Takt. Dabei steht der größte Teil der Schaltung meistens still: Während die ALU rechnet, hat der Speicher nichts zu tun. Echte Prozessoren teilen die Arbeit deshalb in Stufen ein und schieben die Befehle wie am Fließband durch – wenn Befehl 1 ausgeführt wird, wird Befehl 2 schon dekodiert und Befehl 3 geholt. Die klassische Lehrbuch-Pipeline hat fünf Stufen: Holen (IF), Dekodieren (ID), Ausführen (EX), Speicherzugriff (MEM), Zurückschreiben (WB).
Zwei Störungen bremsen das Fließband. Braucht ein Befehl das Ergebnis seines Vorgängers, muss er warten, bis es da ist – oder die Hardware leitet es auf einer Abkürzung direkt weiter (Forwarding). Und bei einem bedingten Sprung weiß der Prozessor erst spät, wo es weitergeht. Statt zu warten, rät er: Eine Sprungvorhersage merkt sich, wie sich jeder Sprung bisher verhalten hat, und liegt bei typischem Code weit über 90 % richtig. Lag sie falsch, werden die schon begonnenen Befehle verworfen. Bei modernen Prozessoren mit 15 bis 20 Stufen kostet das jeweils viele Takte.
Was moderne Prozessoren noch tun
| Technik | Idee |
|---|---|
| Superskalar | mehrere ALUs: mehrere Befehle pro Takt in jeder Stufe |
| Out-of-Order | Befehle in anderer Reihenfolge ausführen, wenn ihre Daten schon bereitstehen – das Ergebnis sieht aus, als wäre alles der Reihe nach passiert |
| Spekulation | hinter einer vorhergesagten Verzweigung weiterrechnen und bei Irrtum verwerfen |
| Caches | kleine SRAM-Speicher, damit nicht jeder Zugriff hunderte Takte auf den DRAM wartet (Speicherhierarchie) |
| SIMD | eine Operation auf 4, 8 oder 16 Werte zugleich – breite ALUs |
| Mehrere Kerne | mehrere vollständige Datenpfade auf einem Chip – für mehrere Threads |
Dass Spekulation Nebenwirkungen in den Caches hinterlässt, die man messen kann, wurde 2018 als Sicherheitsproblem bekannt (Spectre, Meltdown). Seitdem sind Betriebssysteme, Browser und JVMs mit Gegenmaßnahmen ausgestattet – ein Beispiel dafür, dass Hardwaredetails bis in die Softwareschichten durchschlagen.
Für Entwickler übersetzt
Die meisten Details der CPU bleiben unter der JVM verborgen – aber nicht alle. Das berühmteste Beispiel ist die Sprungvorhersage:
int[] daten = zufallszahlen(1_000_000, 0, 256);
// Arrays.sort(daten); // mit dieser Zeile kann die Schleife um ein Vielfaches schneller sein
long summe = 0;
for (int x : daten)
if (x >= 128) // zufällige Daten: die Vorhersage liegt oft falsch
summe += x; // sortierte Daten: erst immer nein, dann immer ja – fast perfekt vorhersagbarOb der Effekt auftritt, hängt allerdings vom JIT ab: Erkennt er das Muster, ersetzt er den Sprung durch einen bedingten Move oder vektorisiert die Schleife, und der Unterschied verschwindet. Genau deshalb misst man solche Dinge nicht mit System.nanoTime() um eine Schleife, sondern mit JMH, das Aufwärmen, JIT-Effekte und Optimierungen berücksichtigt.
| CPU-Eigenschaft | Was es in Java bedeutet |
|---|---|
| Sprungvorhersage | vorhersagbare Verzweigungen sind billig, zufällige teuer |
| Caches | ein int[] durchlaufen ist viel schneller als eine LinkedList – die Daten liegen hintereinander |
| SIMD | der JIT vektorisiert einfache Schleifen selbst; die Vector API (Inkubator-Modul jdk.incubator.vector) macht es ausdrücklich |
| Out-of-Order und mehrere Kerne | ohne Synchronisation sieht ein anderer Thread Schreibzugriffe womöglich in anderer Reihenfolge – deshalb gibt es das Java Memory Model, volatile und Locks |
| Befehlssatz | -XX:+PrintAssembly (mit hsdis) zeigt, was der JIT tatsächlich erzeugt |