Pipeline-Hazard
Pipeline-Hazards sind Konflikte in der Pipeline von Prozessoren, die während der Programmlaufzeit auftreten können.
Alle modernen Prozessoren sind in Pipeline-Architektur ausgeführt: Die einzelnen Instruktionen durchlaufen eine mehrstufige Pipeline, in der sie bei jedem Taktzyklus die nächste Stufe erreichen. In jeder Stufe wird ein Teil der Bearbeitung der Instruktion vollzogen. Jeder Teilschritt benötigt gewisse Ressourcen des Prozessors (z. B. Datenwege und Rechenwerke) und möglicherweise Ergebnisse einer vorhergegangenen Instruktion. Falls eine dieser Ressourcen von einer anderen Instruktion blockiert ist oder Ergebnisse noch nicht zur Verfügung stehen, muss diese Instruktion und alle in der Pipeline folgenden vorübergehend angehalten („stalled“) werden.[1]:114-132
Man unterscheidet drei Arten von Pipeline-Konflikten:
Datenkonflikte
Datenkonflikte ergeben sich aus Datenabhängigkeiten zwischen Befehlen im Programm. Hierbei gibt es drei Arten der Datenkonflikte.
1. Read after Write (RAW) oder Echte Abhängigkeit: Ein Operand wurde verändert und kurz darauf gelesen. Da der erste Befehl den Operanden evtl. noch nicht fertiggeschrieben hat (Pipeline-Stufe „store“ ist weit hinten), würde der zweite Befehl falsche Daten verwenden. Ein „Shortcut“ im Datenweg der Pipeline kann den Hazard vermeiden. Bei problematischeren Situationen, wenn beispielsweise ein Rechenergebnis zur Adressierung verwendet wird oder bei berechneten und bedingten Sprüngen, ist ein Anhalten der Pipeline aber unumgänglich.[1]:114-115
Beispiel
R1 = R2+R3 R4 = R1+1
2. Write after Read (WAR) oder Gegenabhängigkeit: Ein Operand wird gelesen und kurz danach überschrieben. Da das Schreiben bereits vor dem Lesen vollendet sein könnte, könnte der Lese-Befehl die neu geschriebenen Werte erhalten. Dies stellt ein potentielles Problem bei Out-of-order execution dar, nicht bei In-order execution.[1]:114-115
Beispiel
R1 = R2+R3 R2 = 2
3. Write after Write (WAW) oder Ausgabeabhängigkeit: Zwei Befehle schreiben auf denselben Operanden. Der zweite könnte vor dem ersten Befehl beendet werden und somit den Operanden mit einem falschen Wert belassen. Auch dies stellt ein potentielles Problem bei Out-of-order execution dar, nicht bei In-order execution.[1]:114-115
Beispiel
R1 = R2+R3 R1 = 2
Steuerkonflikte
Steuerkonflikte treten bei Instruktionen auf, die den Befehlszähler verändern, z. B. bei bedingten Sprungbefehlen.
Diese Art von Konflikten kann durch folgende Vorgehensweisen vermieden werden:
- Sprungvorhersage: Eine zusätzliche Hardware-Einheit berechnet die Wahrscheinlichkeit, mit der es zu einem Sprung kommt.
- Delayed Branching: In der Zeit, in der das Sprungziel berechnet wird, kann man die CPU dazu nutzen, andere, von dem Sprung unabhängige Instruktionen, zu berechnen. Delayed Branching ist nicht mehr aktuell, es wird heute durch dynamische Branch-Voraussage-Algorithmen ersetzt.
Strukturkonflikte
Strukturkonflikte treten auf, wenn Ressourcenkonflikte innerhalb von Befehlen in der Pipeline vorhanden sind, z. B. ein synchroner Zugriff auf einen Registerspeicher mit nur einem Eingang.
In diesem einfachen Fall können alle Prozessoren durch einen „Shortcut“ im Datenweg der Pipeline den Hazard vermeiden, bei problematischeren Situationen, wenn beispielsweise ein Rechenergebnis zur Adressierung verwendet wird oder bei berechneten und bedingten Sprüngen, ist ein Anhalten der Pipeline aber unumgänglich.
Das Beispiel zeigt, dass es sinnvoll ist, voneinander abhängige Befehle möglichst nicht direkt hintereinander auszuführen. Moderne Prozessoren können deshalb durch Out-of-order execution oder hardwareseitiges Multithreading in vielen Fällen Hazards und Ressourcen-Konflikte vermeiden.