Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für superskalare Prozessoren.

Ähnliche Präsentationen


Präsentation zum Thema: "Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für superskalare Prozessoren."—  Präsentation transkript:

1 Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für superskalare Prozessoren

2 Vorlesung, Wintersemester 2009/10M. Schölzel 2 Optimierungstechniken für superskalare Prozessoren Erzeugen feingranularer Parallelität

3 Optimierungstechniken in modernen CompilernGrundlagen3 Erzeugen feingranularer Parallelität Problem: Im Programmcode kann nicht genügend Parallelität gefunden werden, um alle Ausführungseinheiten eines superskalaren Prozessors auszulasten, weil: Basisblöcke bilden Grenzen für lokale Schedulingalgorithmen. Schleifen bilden für viele globale Schedulingalgorithmen Grenzen. Lösungsansätze: Zusammenfassen mehrerer Basisblöcke zu einem Basisblock und Anwendung lokaler Planungsverfahren. Erzeugen von feingranularer Parallelität aus Schleifen durch parallele Abarbeitung verschiedener Iterationen.

4 Optimierungstechniken in modernen CompilernGrundlagen4 Grundprinzipien der Erzeugung feingranularer Parallelität aus Schleifen s1s1 s3s3 s1s1 Iteration 1 Iteration 2 Iteration k s1s1 Iteration 1 Iteration 2 Iteration k s2s2 … … s2s2 s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 s3s3 s1s1 s2s2 s3s3 s1s1 s2s2 s3s3 s3s3 s2s2 s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 Takt 1 Takt 2 Takt 3 Takt 4 Takt 5 Takt 6 Takt 3k Takt 1 Takt 2 Takt 3 Takt 4 Takt 2k Takt 1 Takt 2 Takt 3 … Anweisungen innerhalb des Schleifenkörpers können umgeordnet werden, solange dadurch keine schleifenunabhängigen Abhängigkeiten verletzt werden. Schleife ohne schleifengetragene Abhängigkeiten Parallelisierung von Anweisungen derselben Iteration Parallelisierung von Anweisungen aus verschiedenen Iterationen Iterationen einer Schleife können parallel ausgeführt werden, falls es keine schleifengetragenen Abhängigkeiten zwischen den Iterationen gibt.

5 Optimierungstechniken in modernen CompilernGrundlagen5 Grundprinzipien der Erzeugung feingranularer Parallelität aus Schleifen Eliminierung von schleifengetragenen Abhängigkeiten in der innersten Schleife durch: Loop Distribution, Scalar Expansion, Loop Interchange. Anschließende Parallelisierung der Iterationen der innersten Schleife. s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 Takt 1 Takt 2 Takt 3 … Schleife mit schleifengetragene Abhängigkeiten s1s1 s3s3 Iteration 1 Iteration 2 Iteration k … s2s2 s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 Takt 1 Takt 2 Takt 3 Takt 4 Takt 5 Takt 6 Takt 3k Schleife mit schleifengetragenen Abhängigkeiten s1s1 s3s3 Iteration 1 Iteration 2 Iteration k … s2s2 s1s1 s3s3 s2s2 s1s1 s3s3 s2s2 Takt 1 Takt 2 Takt 4 Takt 5 Takt 6 Takt 3kIteration 1 Iteration 2 Iteration k … Schleife 1 Schleife 2 Ursprüngliche Schleife aufgeteilt in 2 Schleifen (Loop Distribution) Separate Parallelisierung beider Schleifen

6 Optimierungstechniken in modernen CompilernGrundlagen6 Loop Distribution bei vorwärts gerichteten schleifengetragenen Abhängigkeiten Anweisungen im Schleifenkörper sind parallelisierbar Maximal 2 gleichzeitige Multiplikationen Iterationen sind nicht parallelisierbar: Schleife trägt eine Abhängigkeit Iterationen jeder Schleife sind parallelisierbar: Bis zu N Multiplikationen können parallel ausgeführt werden! for i = 1 to N step 1 do (s) a[i+1] = b[i] * 3; (t) d[i] = a[i] * 5; od {1} D s t s1s1 t1t1 s2s2 t2t2 s3s3 t3t3 sNsN tNtN … s1s1 t1t1 s2s2 t2t2 s3s3 t3t3 sNsN tNtN … for i = 1 to N step 1 do (s) a[i+1] = b[i] * 3; od for i = 1 to N step 1 do (t) d[i] = a[i] * 5; od Quelltext Quelltext nach Loop Distribution Abhängigkeitsgraph Ausführungsreihenfolge mit Abhängigkeiten

7 Optimierungstechniken in modernen CompilernGrundlagen7 Loop Distribution bei rückwärts gerichteten schleifengetragenen Abhängigkeiten Anweisungen im Schleifenkörper sind parallelisierbar Maximal 2 gleichzeitige Multiplikationen Iterationen sind nicht parallelisierbar: Schleife trägt eine Abhängigkeit Durch Vertauschung der Anweisungen (s) und (t) erhalten wir die Schleife von Folie 4. Iterationen jeder Schleife sind parallelisierbar: Bis zu N Multiplikationen können parallel ausgeführt werden! for i = 1 to N step 1 do (s) d[i] = a[i] * 3; (t) a[i+1] = b[i] * 5; od {1} D s t s1s1 t1t1 s2s2 t2t2 s3s3 t3t3 sNsN tNtN … Quelltext Ausführungsreihenfolge mit Abhängigkeiten t1t1 s1s1 t2t2 s2s2 t3t3 s3s3 tNtN sNsN … for i = 1 to N step 1 do (t) a[i+1] = b[i] * 3; od for i = 1 to N step 1 do (s) d[i] = a[i] * 5; od Quelltext nach Loop DistributionAbhängigkeitsgraph Ausführungsreihenfolge mit Abhängigkeiten

8 Optimierungstechniken in modernen CompilernGrundlagen8 Wann funktioniert Loop Distribution nicht? Tauschen zweier Anweisungen s und t im Schleifenkörper führt nach Loop Distribution dazu, dass alle Instanzen von s nach den Instanzen von t ausgeführt werden. Loop-Distribution nicht möglich, weil zyklische Abhängigkeit im Abhängigkeitsgraphen existiert. Diese entsteht, weil: Es ex. eine rückwärtsgerichtete schleifengetragene Abhängigkeit von t nach s es existieren schleifenunabhängige bzw. vorwärtsgerichtete schleifengetragene Abhängigkeiten von s nach t. Dadurch: können Anweisungen auf dem Pfad von s nach t nicht getauscht werden und wegen der rückwärtsgerichteten Abhängigkeit können nicht alle Instanzen von s vor den Instanzen von t ausgeführt werden. for i = 1 to N step 1 do (s) b[i] = a[i] * 3; (t) a[i+1] = b[i] * 5; od {1} D s t {0} D s1s1 t1t1 s2s2 t2t2 s3s3 t3t3 sNsN tNtN … Ausführungsreihenfolge mit Abhängigkeiten QuelltextAbhängigkeitsgraph

9 Optimierungstechniken in modernen CompilernGrundlagen9 Planung mit Loop-Distribution und Paralleliserung von Schleifeniterationen L sei das Schleifennest, das die zu parallelisierenden Anweisungen in der innersten Schleife enthält, wobei die innerste Schleife von 1 bis N läuft. D ist der Abhängigkeitsgraph für die Anweisungen in L. Berechne die Menge {C 1,...,C m } maximaler stark zusammenhängender Komponenten in D. Konstruiere D' aus D durch Ersetzung der Knoten in jedem C i durch einen einzelnen Knoten i, wobei alle Kanten, die adjazent mit genau einem Knoten in C i sind, mit i adjazent gemacht werden. Es seien P = {1,...,m} die m Knoten in D' Solange P nicht leer ist: Falls ein Knoten i P ohne Vorgänger existiert und C i ein Zykel ist: Plane die Anweisungen aus S i in eine Schleife. Falls ein Knoten i P ohne Vorgänger existiert und C i kein Zykel ist: Erzeuge N parallele Anweisungen C i (C i enthält nur eine Anweisung). Entferne i und seine adjazenten Kanten aus P.

10 Optimierungstechniken in modernen CompilernGrundlagen10 Beispiel {1} D S1S1 S2S2 {0} D S0S0 {0} A S3S3 S4S4 S5S C1C1 C2C2 C3C3 C4C4 C5C5 for i = 1 to N step 1 do S 0 ; od for i = 1 to N step 1 do S 1 ; S 2 ; od S 3 ;...;S 3 ; S 4 ;...;S 4 ; S 5 ;...;S 5 ; N-mal Abhängigkeitsgraph DAbhängigkeitsgraph D'Parallelisierter Programmcode

11 Optimierungstechniken in modernen CompilernGrundlagen11 Loop Interchange Loop Interchange permutiert die Schachtelung der Schleifen und tauscht die korrespondierenden Komponenten in den Richtungsvektoren: Beweis: Jede Abhängigkeit wird für zwei Anweisungen s i und s k berechnet, indem zwei Belegung der Indexvariablen gesucht werden (Indexvektoren). Richtungsvektor der Abhängigkeit ergibt sich aus dem Verhältnis der Komponenten der Indexvektoren. Reihenfolge der Komponenten in den Indexvektoren hängt nur von der Reihenfolge der Schachtelung ab. Vertauschen von Schleifen vertauscht entsprechende Komponenten in den Indexvektoren. Damit: Tausch zweier Schleifen tauscht die korrespondierenden Spalten in der Abhängigkeitsmatrix. for i 1 = L 1 to U 1 step 1 do for i 2 = L 2 to U 2 step 1 do... for i d = L d to U d step 1 do s 1 ;...;s n od... od for i 2 = L 2 to U 2 step 1 do for i 1 = L 1 to U 1 step 1 do... for i d = L d to U d step 1 do s 1 ;...;s n od... od Richtungsvektor für eine Abhängigkeit: ( )Richtungsvektor für dieselbe Abhängigkeit: (=, )

12 Optimierungstechniken in modernen CompilernGrundlagen12 Gültigkeit einer Vertauschung Bei Vertauschung der Schleifen auf Level i und j werden auch die Komponenten i und j in den Richtungsvektoren aller Abhängigkeiten vertauscht. Falls jeder der daraus entstehenden Richtungsvektoren ein gültiger Richtungsvektor ist, dann ist die Vertauschung gültig. Beispiel: for i = 1 to N step 1 do for j = 1 to M step 1 do for k = 1 to L step 1 do a[i+1][j+1][k] = a[i][j][k] + a[i][j+1][k+1] od [<,<,=] [ ] Richtungsvektoren [<,<,=] [=, ] Tausch von Level 1 und 2Tausch von Level 1 und 3 (ungültig) [=,<,<] [>,=,<]

13 Optimierungstechniken in modernen CompilernGrundlagen13 Beispiel for i = 1 to N step 1 do for j = 1 to M step 1 do a[i][j+1] = a[i][j] + 2 od for j = 1 to N step 1 do for i = 1 to M step 1 do a[i][j+1] = a[i][j] + 2 od Innere Schleife trägt die Abhängigkeit Äußere Schleife trägt die Abhängigkeit

14 Optimierungstechniken in modernen CompilernGrundlagen14 Scalar Expansion - Motivation Zyklen im Abhängigkeitsgraphen verhindern Parallelisierung von Schleifeniterationen. Zyklen im Abhängigkeitsgraphen sind eliminiert – Schleifeniterationen können parallelisiert werden. for i = 1 to N step 1 do (s) t = a[i]; (t) a[i] = b[i]; (u) b[i] = t; od {0,1} D s u {0} A t {1} A {1} O for i = 1 to N step 1 do (s) t[i] = a[i]; (t) a[i] = b[i]; (u) b[i] = t[i]; od {0,1} D s u {0} A t

15 Optimierungstechniken in modernen CompilernGrundlagen15 Überdeckende Definition Eine Definition X einer skalaren Variablen S ist eine überdeckende Definition für die Schleife L, falls eine Definition für S, die am Beginn der Schleife L eingefügt wird, keine Verwendungen von S erreicht, ohne vorher X erreicht zu haben. Eine Menge C von Definition einer skalaren Variablen S ist eine Menge überdeckender Definitionen für L, falls eine Definition für S, die am Beginn der Schleife L eingefügt wird, keine Verwendungen von S erreicht, ohne vorher eine Definition aus der Menge C erreicht zu haben. Beispiele: for i = 1 to N step 1 do (s) t = a[i]; (u) b[i] = t; od Beispiel 1: s ist eine überdeckende Definition. for i = 1 to N step 1 do if anyCond then (s) t = a[i]; fi (u) b[i] = t; od Beispiel 2: Es existiert keine überdeckende Definition. for i = 1 to N step 1 do if anyCond then (s) t = a[i]+1; else (t) t = a[i]-1; fi (u) b[i] = t; od Beispiel 3: {s, t} sind eine Menge von überdeckenden Definitionen.

16 Optimierungstechniken in modernen CompilernGrundlagen16 Berechnen Überdeckender Definitionen für ein SSA-Programm Q sei die -Funktion am Beginn der Schleife L, die die Variable t definiert und eine SSA-Kante von außerhalb der Schleife nach Q führt. Setze Menge der überdeckenden Definitionen C := ; Leere den Stack S. D sei die erste Definition von t in L. C := C {D}. Für jede -Funktion P, die verschieden von Q und SSA-Nachfolger von D ist: push(S,P) und markiere P. Solange S nicht leer ist: P = pop(S) Füge alle SSA-Vorgänger von P, die keine -Funktion sind, zur Menge C hinzu. Für jede unmarkierte -Funktion R (verschieden von Q), die ein SSA-Vorgänger von P ist: push(S,R) und markiere R. Für jede unmarkierte -Funktion R, die SSA-Nachfolger von P ist und die nicht von P dominiert ist: push(S,R) und markiere R. Falls ein SSA-Kantenweg von Q nach P existiert, dann füge eine Anweisung T = T als letzte Anweisung auf dem Pfad von Q nach P ein und füge diese Anweisung zu C hinzu. t = 0; for i = 1 to N step 1 do a[i] = t; if anyCond then t = t + b[i] + c[i]; fi d[i] = t; od 1 t 0 = 0; for i = 1 to N step 1 do 2 t 1 = (t 0,t 3 ); 3 a[i] = t 1 ; if anyCond then 4 t 2 = t 1 + b[i] + c[i]; fi 5 t 3 = (t 1,t 2 ); 6 d[i] = t 3 ; od Q = 2D = 4C = {4} S = (5) C = {4}P = 5 7 else t = t C = {4,7}

17 Optimierungstechniken in modernen CompilernGrundlagen17 Scalar Expansion durchführen Erzeuge für die skalare Variable t ein Feld T geeigneter Länge. Für jede Anweisung S in C ersetze t auf der linken Seite durch T[i]. Jede Verwendung von t, die vor den Definitionen in C liegt (direkte SSA-Nachfolger von Q): Ersetze t durch T[i-1]. Jede andere Definition und Verwendung von t, ersetze durch T[i]. Falls Q vorhanden, dann füge T[0] = t vor dem Beginn der Schleife ein. Falls eine SSA-Kante von einer Definition von t nach außerhalb von L führt, dann füge t = T[N] nach dem Ende der Schleife ein, wobei N die obere Grenze der Schleife ist. Beispiel: 1 t 0 = 0; for i = 1 to N step 1 do 2 t 1 = (t 0,t 3 ); 3 a[i] = t 1 ; if anyCond then 4 t 2 = t 1 + b[i] + c[i]; else 7 t = t fi 5 t 3 = (t 1,t 2 ); 6 d[i] = t 3 ; od 1 t = 0; for i = 1 to N step 1 do 3 a[i] = t; if anyCond then 4 t = t + b[i] + c[i]; else 7 t = t fi 6 d[i] = t; od 1 t = 0; T[0] = t; for i = 1 to N step 1 do 3 a[i] = T[i-1]; if anyCond then 4 T[i] = T[i-1] + b[i] + c[i]; else 7 T[i] = T[i-1] fi 6 d[i] = T[i]; od

18 Vorlesung, Wintersemester 2009/10M. Schölzel 18 Optimierungstechniken für superskalare Prozessoren Schleifenfreie Ablaufplanung für superskalare Prozessorarchitekturen

19 Optimierungstechniken in modernen CompilernGrundlagen19 Trace-Scheduling Trace: Folge aufeinander folgender Basisblöcke. Steuerfluss führt an beliebigen Positionen in Traces hinein oder heraus: Ein Split ist eine Verzweigung aus dem Trace heraus. Ein Join ist eine Verzweigung in den Trace hinein. Planung der Operationen in einem Trace durch einen List- Scheduling-Algorithmus. Trace-Scheduling erlaubt: Abwärtsverschiebung von Operationen hinter Splits. Aufwärtsverschiebung von Operationen vor einen Split nur bei Operationen ohne Seiteneffekte. Aufwärts-/Abwärtsverschiebung von Operationen vor/hinter Joins. In praktischen Implementierungen (z.B. Multiflow-Compiler) wird die Reihenfolge von Sprunganweisungen in einem Trace nicht verändert. Einfügen von Korrekturcode an solchen Positionen, an denen in einen gescheduleten Trace hineingesprungen oder herausgesprungen wird.

20 Optimierungstechniken in modernen CompilernGrundlagen20 Algorithmus Auswahl einer Folge von Basisblöcken (Trace), deren Operationen gemeinsam geplant werden sollen. Grenzen eines Traces: Start-/Stoppknoten des Steuerflussgraphen. Schleifen (kein Trace enthält Blöcke einer Schleife und Blöcke, die nicht zu dieser Schleife gehören). Bereits geplante Blöcke. Entfernen des Traces aus dem Steuerflussgraphen und Anwendung eines List-Scheduling-Algorithmus auf den Trace. Einsetzen des Ergebnisses des List-Schedulers in den Steuerflussgraphen und Einfügen von Korrekturcode für solche Operationen, die hinter Splits oder vor/hinter Joins verschoben wurden. Wiederholung dieser Schritte, bis alle Blöcke im Steuerflussgraphen verplant wurden. Ausgabe des Codes im Steuerflussgraphen in einer geeigneten Reihenfolge.

21 Optimierungstechniken in modernen CompilernGrundlagen21 Korrektur bei Verschiebung hinter Split Korrekturcode: Für jeden Split mit einem Sprungziel b: Erzeuge einen neuen Vorgängerblock für b und kopiere jede Operation, die hinter den Split verschoben wurde, in diesen Block unter Beachtung der ursprünglichen Reihenfolge. Op 1 Op 2 Branch Op 4 Op 5 Op 6 Trace b Op 3 Op 2Branch Op 1Op 4 Op 3Op 5 Op 6 Schedule b Op 1 Op 3 Op 1 Op 3 Korrekturcode

22 Optimierungstechniken in modernen CompilernGrundlagen22 Korrektur bei Verschiebung vor/hinter einen Join Korrekturcode: Für jeden Join Bestimme im Schedule die maximale Position p an der eine Operation ausgeführt wird, die im Trace vor dem Join ausgeführt wurde. Bestimme alle Operationen, die im Trace nach dem Join ausgeführt wurden und im Schedule bis zur Position p ausgeführt werden. Füge diese Operationen als neuen Nachfolgerblock von b in den Steuerflussgraphen ein. Am Ende dieses Blocks wird an Position p+1 in den Schedule des Traces verzweigt. Op 1 Op 2 Op 4 Op 5 Op 6 Trace b Op 3 Op 2 Op 4Op 1 Op 5 Op 6 Schedule b Op 1 Op 3 Op 4 Korrekturcode Op 5 Op 3

23 Optimierungstechniken in modernen CompilernGrundlagen23 Split-Operationen im Korrekturcode für Joins Für Splits, die sich im Korrekturcode für Joins befinden, müssen die Operationen bestimmt werden, die im Trace vor dem Split ausgeführt wurden und sich im Schedule hinter dem Einsprung des Korrekturcodes für den Join befinden. Op 1 Op 2 Op 4 Branch Op 5 Trace b Op 3 Op 1 Op 3 b' Op 1Branch Op 3 Op 2 Op 4 Op 5 b' Op 2 Op 3 Op 4 b Op 3 Branch Op 5 Op 4 Schedule

24 Optimierungstechniken in modernen CompilernGrundlagen24 Beispiel für Explosion des Korrekturcodes C1C1 A1A1 B1B1 C2C2 A2A2 B2B2 CnCn AnAn BnBn TraceSchedule CnCn AnAn C n-1 A n-1 C1C1 A1A1... C1C1 A1A1 C2C2 A2A2 BnBn C n-1 A n-1... C1C1 A1A1 C2C2 A2A2 B n-1 C n-2 A n-2... CnCn AnAn B1B1 C2C2 A2A2 CnCn AnAn Korrekturcode, der zu n neuen Traces führt.

25 Optimierungstechniken in modernen CompilernGrundlagen25 Superblöcke Vereinfachung von Traces: Trace darf nur am Anfang betreten werden. Verlassen ist an beliebigen Positionen möglich. Erzeugen von Superblöcken in 2 Schritten: Auswählen eines Traces. Tail-Duplication: Eliminierung von Eintrittspunkten durch Duplizierung des Resttraces nach dem Eintrittspunkt. Vorteil: Vereinfachung der Erzeugung des Korrekturcodes.

26 Optimierungstechniken in modernen CompilernGrundlagen26 Tail-Duplication

27 Optimierungstechniken in modernen CompilernGrundlagen27 Superblock Scheduling Auswahl eines Traces. Tailduplication ausführen. Konstruktion des Abhängigkeitsgraphen. Anwendung eines List-Schedulers unter Beachtung des Abhängigkeitsgraphen. Korrekturen ausführen: Operation wurde vor einen Split verschoben, dann wird Operation spekulativ ausgeführt: Nur dann möglich, wenn die Operation keine Exception verursacht und wenn der definierte Wert vor dem Split nicht lebendig war. Operationen wurden hinter einen Split verschoben, dann muss Korrekturcode wie bei Trace-Scheduling erzeugt werden.

28 Optimierungstechniken in modernen CompilernGrundlagen28 Unterstützung zur Ausführung spekulativer Operationen Bisher zwei wesentliche Probleme bei der spekulativen Ausführung einer Operation: Operation mit Seiteneffekten kann Exception auslösen. Falls die Operation eine load-Operation ist, muss der Compiler beweisen, dass keine RAW-Abhängigkeit zwischen dem load und einer vorangehenden store- Operation existiert, falls load vor dieses store verschoben werden soll.

29 Optimierungstechniken in modernen CompilernGrundlagen29 Spekulative Ausführung von loads Problem: In einigen Fällen kann der Compiler nicht beweisen, dass es keine RAW-Abhängigkeit zwischen store- und load- Operation gibt, da die zugegriffene Adresse nicht statisch berechnet werden kann. Spekulative Ausführung trotzdem möglich durch: Einfügen von Test- und Korrekturcode ins Programm oder Hardwareunterstützung (Memory Conflict Buffer) für das Programm.

30 Optimierungstechniken in modernen CompilernGrundlagen30 Test- und Korrekturcode load-Operation u wird spekulativ ausgeführt. Nach jeder store-Operation v, für die es eine Datenabhängigkeit (v,u) geben kann, wird Programmcode eingefügt, der prüft, ob u und v dieselbe Adresse nutzen. Falls ja, müssen alle von u datenabhängigen Operationen, die vor v ausgeführt wurden, wiederholt werden. Deren Operanden dürfen nicht verändert worden sein. mul R2,R3 R1 store R11 [R9] store R1 [R3] load [R5] R4 add R4,R1 R6 mul R2,R3 R1 load [R5] R4 store R11 [R9] jmpneq R9,R5 skip1 mov R11 R4 skip1: store R1 [R3] jmpneq R3,R5 skip2 mov R1 R4 skip2: add R4,R1 R6 Originalcode spekulative Ausführung des loads

31 Optimierungstechniken in modernen CompilernGrundlagen31 Hardwareunterstützung zur spekulativen Ausführung von load-Operationen Memory-Conflict-Buffer (MCB) speichert Tripel mit Registernummer, Speicheradresse und Konfliktbit. Unterscheidung im Befehlssatz des Prozessors zwischen spekulativem load und nicht-spekulativem load. Spekulatives load von der Adresse a in ein Register b erzeugt im MCB einen Eintrag (a,b,0). store-Operation an eine Adresse c im Speicher ändert Konfliktbit in allen Einträgen (a,b,x) des MCB mit a = c auf 1. Befehlssatz enthält eine Operation check r, label, wobei r ein Register und label die Adresse des Korrekturcodes ist. Ausführung der check r, label Operation an der ursprünglichen Position des loads, wobei r Zielregister der load-Operation ist. Falls Im MCB ein Eintrag (a,r,1) existiert, wird die Operation mit dem Label label angesprungen.

32 Optimierungstechniken in modernen CompilernGrundlagen32 Integration in Superblock Scheduling Erzeugen eines Superblocks. Erzeugen des Abhängigkeitsgraphen. check r, label_i nach jeder Operation i der Form load [a] r einfügen. check-Operation übernimmt die Abhängigkeiten mit store- Operationen des zugehörigen loads und ist abhängig von der zugehörigen load-Operation sowie umgebenden Sprunganweisungen. RAW-Abhängigkeiten für jede load-Operation entfernen, wodurch das load verschoben werden kann. Superblock planen. Korrekturcode einfügen: Quelloperanden der Operationen im Korrekturcode dürfen nicht überschrieben worden sein (es darf kein WAR-Abhängigkeiten zwischen den Operationen des Korrekturcodes geben).

33 Optimierungstechniken in modernen CompilernGrundlagen33 Beispiel mul R2,R3 R1 store R11 [R9] store R1 [R3] load [R5] R4 add R4,R1 R6 store R6 [R10] add R6,R12 R11... Originalcode mit möglichen Abhängigkeiten des loads von stores Geplanter Superblock mit Korrekturcode und dupliziertem Ende, um Einsprung zu vermeiden. mul R2,R3 R1 store R11 [R9] store R1 [R3] load [R5] R4 check R4, Korrektur add R4,R1 R6 store R6 [R10] add R6,R12 R11... Abhängigkeiten der check- Operation mul R2,R3 R1 load [R5] R4 add R4,R1 R6 store R11 [R9] store R1 [R3] check R4, Korrektur store R6 [R10] add R6,R12 R11... Korrektur: load [R5] R4 add R4,R12 [R11] jmp tailDup TailDup: store R6 [R10] add R6,R12 R11...

34 Optimierungstechniken in modernen CompilernGrundlagen34 Behandlung von Exceptions bei spekulativer Ausführung analog Behandlung von Exceptions durch E-Tags in Registern: Jede Operation ist in zwei Varianten vorhanden: Auftretende Exception wird sofort ausgeführt, Auftretende Exception wird verzögert. Im Fehlerfall wird E-Tag im Zielregister gesetzt und PC in das Register kopiert. Falls kein Fehler auftritt wird das Ergebnis der Operation ins Zielregister geschrieben und E-Tag gelöscht. Falls eine spekulative Operation einen ungültigen Operanden verwendet (E-Tag gesetzt), wird dieser in das Zielregister kopiert. An der Position, an der sich die Operation ursprünglich befunden hat wird durch eine check-Operation geprüft, ob eine Execption ausgelöst wurde. Für Recovery wurde der PC gespeichert, außerdem müssen alle spekulativ ausgeführten Operationen wiederholt werden. Operanden der Operation müssen an dieser Stelle auch noch zur Verfügung stehen, um Operation zu wiederholen.

35 Vorlesung, Wintersemester 2009/10M. Schölzel 35 Optimierungstechniken für Superskalare Prozessoren Ablaufplanung von Schleifen

36 Optimierungstechniken in modernen CompilernGrundlagen36 Scheduling von Schleifen Bisher konnten Operationen nicht über die Grenzen des Schleifenkörpers verschoben werden. Lösung: Schleife ausrollen, Trace-Scheduling anwenden. Bewegung der Operationen nur innerhalb der ausgerollten Schleifenkörper. B H E B H B H BE

37 Optimierungstechniken in modernen CompilernGrundlagen37 Prinzip Modulo-Scheduling Aus dem gegebenen Schleifenkörper wird ein neuer Moduloschleifenkörper erzeugt, der die Länge II hat. Die Operationen des Schleifenkörpers werden in II vielen Instruktionen geplant. Dabei können Operationen, die in derselben Iteration des Schleifenkörpers ausgeführt werden, in eine andere Iteration der Moduloschleife verschoben werden, wenn dadurch keine Abhängigkeiten verletzt werden. Gesucht ist somit ein Schedule (, ), der einer Operation v einen Zeitpunkt 0 (v) < II und eine Iteration (v) zuordnet, in der v ausgeführt wird: Op1 Schleifenkörper, der keine schleifengetragenen Abhängigkeiten besitzt. Op2 Op3 Op4 Op1 0 Op2 0 Op3 0 Op4 0 Op1 1 Op2 1 Op3 1 Op4 1 Op1 2 Op2 2 Op3 2 Op4 2 Op1 0 Op2 0 Op1 1 Op2 1 Op3 0 Op1 2 Op2 2 Op3 1 Op4 0 Op1 3 Op2 3 Op3 2 Op4 1 Op1 3 Op2 3 Op3 2 Op4 1 Iteration 1 Iteration 2 Iteration 3 Normale AusführungModuloschleifeniterationen für II = 1Moduloschleifenkörper Op1Op2Op3Op4 Iteration 1 Iteration 2 Iteration 3 Iteration 4 Iteration 5

38 Optimierungstechniken in modernen CompilernGrundlagen38 Planung der Operation Zunächst Einschränkung auf azyklische Abhängigkeitsgraphen. Damit bei vorhandener Kante (v,w) keine Abhängigkeit verletzt wird, muss (v) + d(v,w) (w), falls (w) = (v) oder II * (v) + (v) + d(v,w) II * (w) + (w), was die erste Forderung überflüssig macht. Konsequenz: Jede mögliche Position innerhalb von II kann zur Planung von v genutzt werden. Einschränkung entsteht nur durch vorhandene Ressourcen. Untere Schranke für II: Potentieller Ausführungszeitpunkt für w v II d(v,w)d(v,w) Iteration (v) w II Iteration (w)... w

39 Optimierungstechniken in modernen CompilernGrundlagen39 Schedulingalgorithmus für azyklische Abhängigkeitsgraphen Algorithmus: moduloSchedule Eingabe: Abhängigkeitsgraph G Länge II des Initiation Intervalls Ausgabe: Schedule (, ) Es sei v 1,...,v n eine topologische Sortierung der Knoten im Abhängigkeitsgraphen G. for i = 1 to n do earlyS = 0; earlyI = 0; for each w mit (w,v i ) E do thisS = (w) + d(w); thisI = (w); if(thisS II) then thisI = thisI + (thisS div II) thisS = thisS mod II; fi if(thisI > earlyI or thisS > earlyS) then earlyI = thisI; earlyS = thisS; fi od Versuche ab earlyS eine Position c mit freier Ressource für w zu finden. Falls das nicht möglich ist, dann ab Anfang des Moduloschleifenkörpers die Suche fortsetzen. (v) = c; if Suche wurde ab Schleifenanfang fortgesetzt then (v) = earlyI + 1; else (v) = earlyI; fi od

40 Optimierungstechniken in modernen CompilernGrundlagen40 Beispiel Op1Op2 Op3 Op4 Op1Op2 (1) = 0, (2) = 0 Op3 Op4 Ursprünglicher SchleifenkörperScheduling für II = 2 und 2 Ressourcen pro Takt. Topologische Sortierung: Op1, Op2, Op3, Op4. i = 1 und 2 i = 3 Op1Op2 (1) = 0, (2) = 0 (3) = 0 Op3 i = 4 Op1Op2 (1) = 0, (2) = 0 (3) = 0, (4) = 1

41 Optimierungstechniken in modernen CompilernGrundlagen41 Interschleifenabhängigkeiten Schleifen ohne Interschleifenabhängigkeiten sind unrealistisch: Schreiboperationen auf skalare Variablen (Schleifenzähler, Zeiger in Felder) erzeugen WAW-Abhängigkeit zwischen aufeinander folgenden Iterationen. Häufig auch Abhängigkeiten zwischen Zugriffen auf Feldelemente. Beispiele: for(i = 1; i < n; i++) A[i] = A[i-1] + k; for(i = 2; i < n; i++) A[i] = A[i-2] + k; for(i = 0; i < n; i++) A[i] = A[i+2] + k; Iteration i = k load A[k-1] store A[k] Iteration i = k+1 load A[k] store A[k+1] RAW Iteration i = k load A[k-2] store A[k] Iteration i = k+1 load A[k-1] store A[k+1] RAW Iteration i = k+2 load A[k] store A[k+2] Iteration i = k load A[k+2] store A[k] Iteration i = k+1 load A[k+3] store A[k+1] WAR Iteration i = k+2 load A[k+4] store A[k+2]

42 Optimierungstechniken in modernen CompilernGrundlagen42 Beachtung von Interschleifenabhängigkeiten Es sei (w,v) eine Kante mit cross((w,v)) > 0. Wo muss v geplant werden? w muss im Moduloschedule d(w,v) viele Takte vor der Operation v ausgeführt werden, die cross(w,v) viele Iterationen nach der Operation w im ursprünglichen Schleifenkörper ausgeführt wird: (w) * II + (w) + d(w,v) (v) * II + (v) + cross(w,v) * II Es ergibt sich: (w) + d(w,v) (v) + ( (v) – (w) + cross(w,v))*II vmvm (w) – (v) mal wmwm... cross(w,v) mal vzvz Instruktion (w) * II + (w) Instruktion (v) * II + (v) cross(w,v) * II viele Instruktionen z = m + cross(w,v)

43 Optimierungstechniken in modernen CompilernGrundlagen43 Welche Einschränkungen ergeben sich bei der Planung? Es seien v 1,...,v k die Knoten eines Zykels im Abhängigkeitsgraphen, wobei: für alle Kanten (v i,v i+1 ) mit 1 i < k gilt: cross((v i,v i+1 )) = 0 und cross((v k,v 1 )) > 0. Planung der Operationen v 1,...,v k durch Moduloscheduling führt dazu, dass für Operation v k gilt: (v k ) (v 1 ). Das bedeutet, dass bei Ausführung von v 1 und v k in einer Moduloschleifeniteration, wobei v 1 aus der Schleifeniteration m sein soll, die Operation v k aus der Schleifeniteration m – ( (v k ) – (v 1 )) ausgeführt wird. Analog: Stammt v k aus der Schleifenitertion m, dann gehört v 1 zur Schleifeniteration m + ( (v k ) – (v 1 )). Damit (v k ) + d(v k,v 1 ) (v 1 ) + ( (v 1 ) – (v k ) + cross(v k,v 1 ))*II erfüllt ist, darf v k nicht um mehr als cross(v k,v 1 ) viele Iterationen nach v 1 ausgeführt werden. Beispiel: Op1Op2 Op3 Op4 Op1Op2Op3Op4 Op4 muss einen Takt bevor Op3 aus der nächsten Iteration ausgeführt wird, ausgeführt worden sein. II = 1: Op1 Op2 II = 2: Op3 Op4 (1) = 0 (2) = 0 (3) = 1 (4) = 2 Op1 Op3 II = 2: Op2 Op4 (1) = 0 (2) = 0 (3) = 0 (4) = 1 (1) = 0 (2) = 0 (3) = 1 (4) = 1

44 Optimierungstechniken in modernen CompilernGrundlagen44 Weitere untere Schranke für II Da bei einer Kante (v k,v 1 ) v k nicht um mehr als cross(v k,v 1 ) viele Iterationen nach v 1 ausgeführt werden darf, müssen alle Operationen auf einem Pfad von v 1 in diesen Iterationen der Moduloschleife verteilt werden. Es ergibt sich für einen Zykel v 1,...,v k,v k+1 mit v 1 = v k+1 im Abhängigkeitsgraphen: Und damit als weitere untere Schranke für II: Op1 Op2 Op3 Op4 Op5 II = 2: Op1 Op2 Op3 Op4 Op5 Iterationnn–1n–2 II = 3: Op1 Op2 Op3 Op4 Op5 Iterationnn–1

45 Optimierungstechniken in modernen CompilernGrundlagen45 Schedulingalgorithmus für zyklische Abhängigkeitsgraphen Eingabe: Abhängigkeitsgraph G mit Knotenmenge N und Kantenmenge E Ausgabe: Schedule (, ) MinII = max(ResII(G),RecII(G)) Es sei G' der Graph G, in dem alle Zyklen durch entfernen der Rückkante aufgebrochen wurden failed = true; for II = MinII to |N| do (, ) = moduloSchedule(G',II) allOk = true; for each Zykel z in G do Es sei v 1 die erste und v k die letzte Operation auf dem Zykel z in G if not ( (v k ) + delay(v k ) (v 1 ) + ( (v k ) – (v 1 ) + cross(v k,v 1 ))*II) then allOk = false fi od if allOk then return (, ) fi od

46 Optimierungstechniken in modernen CompilernGrundlagen46 Prologgenerierung Prolog zum Initialisieren des Moduloschleifenkörpers. Epilog zum Beenden der begonnenen Iterationen. range(, ) = max{ (n) | n ist Operation im Schedule} ist die Anzahl der Iterationen, die ausgeführt werden müssen, bevor der Moduloschedule ausgeführt werden kann. Es werden immer range(, ) + 1 viele Iterationen ausgeführt. Generierung des Prolog: Einfügen von range vielen Kopien des Moduloschedules vor dem Moduloschedule und Ersetzen von allen Operationen v in Kopie k mit (v) k durch NOPs. Generierung des Epilogs: Einfügen von range vielen Kopien des Moduloschedules nach dem Moduloschedule und Ersetzen von allen Operationen v in Kopie k mit (v) < k durch NOPs. Op1 Op2 Op3 Op4 (1) = 0 (2) = 0 (3) = 1 (4) = 1 Moduloschedule Op1 Op2 Op3 Op4 Moduloschleifenkörper Op1 Op2 nop Kopie 1 nop Op3 Op4 Kopie 1 Prolog Epilog

47 Optimierungstechniken in modernen CompilernGrundlagen47 Beispiel für Prologgenerierung load [R1] R4 add R3,R4 R5 store R5 [R0] inc R1 inc R0 dec R2 bne R2,L load [R1] R4add R3,R4 R5store R5 [R0]inc R1inc R0dec R2 bne R2,L load [R1] R4add R3,R4 R5nopinc R1nopdec R2nop load [R1] R4nop inc R1nop L:

48 Optimierungstechniken in modernen CompilernGrundlagen48 Zusammenfassung Scheduling von Schleifen Ausrollen einer Schleife zur Bildung längerer Traces: Transformation relativ einfach, Schleifenkörper darf keine Schleifen enthalten, Bedingte Verzweigungen sind zulässig, Es entsteht viel Programmcode, Sprung zum Schleifenanfang stellt Grenze für den Schedulingalgorithmus dar, an der Ressourcen oft nicht ausgelastet werden. Moduloscheduling: Komplexe Transformation, Schleifenkörper muss ein Basisblock sein, Kompakter Programmcode, Rücksprung ist keine Grenze für den Schedulingalgorithmus. Transformation von Steuerfluss- in Datenabhängigkeiten, um bedingte Verzweigungen im Schleifenkörper zuzulassen.

49 Vorlesung, Wintersemester 2009/10M. Schölzel 49 Optimierungstechniken für superskalare Prozessoren Behandlung von Steuerflussabhängigkeiten

50 Optimierungstechniken in modernen CompilernGrundlagen50 Bedingte Ausführung und If-Conversion Technik zur Transformation von Steuerflussabhängigkeiten in Datenabhängigkeiten. Prinzip: Prozessorarchitektur unterstützt bedingt Ausführung (Guarded Statements). Guard ist ein 1-Bit-Register, das als zusätzlicher Operand einer Operation genutzt werden kann. Falls Wert des Guard-Registers 1 ist, wird die Operation normal ausgeführt, sonst wird die Operation nicht ausgeführt. Verzweigungsbedingungen im Programm werden negiert und nicht negiert in Guard-Register berechnet. Alle steuerflussabhängigen Operationen erhalten dieses Register als zusätzlichen Operanden. Spekulative Ausführung nicht mehr möglich, da dann Datenflussabhängigkeit verletzt wäre.

51 Optimierungstechniken in modernen CompilernGrundlagen51 Prinzip If-Conversion Ziel: Eliminierung von Vorwärtssprüngen in schleifenfreiem Programmcode. Idee: Jede bedingte Verzweigung berechnet einen neuen Guard-Wert in ein Guardregister, der 0 ist, falls die Verzweigungsanweisung gar nicht ausgeführt wird, 0 ist, falls die Verzweigungsanweisung ausgeführt wird und die Bedingung falsch 1 ist, falls die Verzweigungsanweisung ausgeführt wird und die Bedingung wahr. Für jede Anweisung, die mit einem Label versehen ist (angesprungen werden kann) merken der Guardregister, die den Guard-Wert enthalten, der festlegt, ob von der zugehörigen bedingten Verzweigung zu diesem Label verzweigt wurde. Beim Lauf durch den Steuerflussgraphen bilden eines Ausdrucks, der an der aktuellen Position wahr sein muss, damit diese erreicht wird. Label: Anweisung if c then Label Start Stopp Bedingung b muss gelten. Bedingungen b und c müssen gelten. Bedingungen b und !c müssen gelten.

52 Optimierungstechniken in modernen CompilernGrundlagen52 Algorithmus Eingabe: azyklischer Steuerflussgraph mit Blöcken b 1,...,b n Es sei i 1,...,i n die Anweisungsfolge in einer topologischen Sortierung der Blöcke. currentGuard = g, der mit True initialisiert ist. Für jede Anweisung i k mit einem Label wird cond(i k ) = gesetzt for j = 1 to n do if(a j besitzt ein Label) then g sei ein unbenutztes Guardregister initialisiert mit currentGuard for each g' cond(a j ) do Erzeuge Code g := g or g' od currentGuard = g fi if(a j ist ein bedingter Sprung zum Label L abhängig von c) then a l sei die Anweisung mit dem Label L g,g' seien unbenutzte Guardregister Erzeuge Code g := currentGuard and c cond(a l ) = cond(a l ) {g} Erzeuge Code g' := currentGuard and !c Ersetze a j durch erzeugten Code currentGuard = g' else Ersetze a j durch currentGuard: a j. fi od

53 Optimierungstechniken in modernen CompilernGrundlagen53 Beispiel if c 1 then l 1 s1s1 if c 2 then l 2 s2s2 s3s3 s5s5 s6s6 l1:l1: l2:l2: s4s4 AnweisungencurrentGuard und Ausdruck darin g 0 = True g 0 and c 1 g 1 g 1 = c 1 g 0 and !c 1 g 0 g 0 = !c 1 g 0 : s 1 g 0 and c 2 g 2 g 0 and !c 2 g 0 g 2 = !c 1 and c 2 g 0 = !c 1 and !c 2 g 2 = (!c 1 and !c 2 ) or (c 1 ) = !c 2 or c 1 g 0 : s 2 g 0 or g 1 g 3 g 3 : s 3 g 3 : s 4 g 3 =!c 2 or c 1 g 4 = (!c 2 or c 1 ) or (!c 1 and c 2 ) = True g 3 or g 2 g 4 g 4 : s 5 g 4 : s 6 g 4 = (!c 2 or c 1 ) or (!c 1 and c 2 ) = True cond(a j ) Zielcode:

54 Optimierungstechniken in modernen CompilernGrundlagen54 Anwendung von If-Conversion zur Bildung von Hyperblöcken Kombination von Basisblöcken aus verschiedenen Pfaden des Programms. Dienen der Unterstützung des Schedulings von stark steuerflussdominierten Programmabschnitten. Ein Eintrittspunkt; Verlassen an mehreren Positionen möglich. Konstruktion des Hyperblocks aus den Basisblöcken einer Region (z.B. Schleifenkörper): Initial besteht der Hyperblock aus den Blöcken der Region. Basisblöcke werden anhand folgender Kriterien ausgeschlossen: Ausführungshäufigkeit (Ausschluss selten ausgeführter Blöcke), Größe (Ausschluss großer Basisblöcke, die nicht zum hauptsächlich ausgeführten Trace der Region gehören), Charakteristik der Operationen (Ausschluss von BBs mit Unterprogrammaufrufen und nicht vorhersagbaren Speicherzugriffen). So gebildeter Hyperblock muss zwei Kriterien erfüllen: Nur ein Eintrittspunkt (evtl. Tail-Duplication durchführen). Darf keine Schleifen enthalten (evtl. Loop-Peeling durchführen).

55 Optimierungstechniken in modernen CompilernGrundlagen55 Beispiel Hyperblockbildung Aus der Region A, B, C, D, E, F wurde der Block D aus dem Hyperblock ausgeschlossen. Tail-Duplication wurde durchgeführt. If-Conversion wurde durchgeführt.

56 Optimierungstechniken in modernen CompilernGrundlagen56 Loop-Peeling Schleife in einem Hyperblock wird n-mal ausgerollt. Für verbleibende Durchläufe wird der Schleifenkörper kopiert. Konsequenz: Nur wenn die Schleife öfter als n-mal ausgeführt wird, wird der Hyperblock verlassen.


Herunterladen ppt "Vorlesung, Wintersemester 2009/10M. Schölzel 1 Optimierungstechniken in modernen Compilern Optimierungstechniken für superskalare Prozessoren."

Ähnliche Präsentationen


Google-Anzeigen