Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Optimierungstechniken in modernen Compilern

Ähnliche Präsentationen


Präsentation zum Thema: "Optimierungstechniken in modernen Compilern"—  Präsentation transkript:

1 Optimierungstechniken in modernen Compilern
Grundlagen

2 Aufbau eines Compilers Überblick über die Analysephase
Grundlagen Aufbau eines Compilers Überblick über die Analysephase

3 Aufbau eines Compilers
Kontext- prüfung Zielcodeabhängige Optimierungen Backend Quell-text Zwischencode und Symbol- tabelle Ziel-code Scanner Parser Zielcode- erzeugung Zielcodeunab- hängige Optimierungen Frontend Steuerfluss im Compiler Datenfluss im Compiler

4 Zeichenketten Es sei  eine abzählbare Menge von Zeichen, Alphabet genannt. Eine Zeichenkette w ist eine totale Funktion: Für w schreiben wir auch: w(0)…w(n-1) n ist die Länge der Zeichenkette. Es gibt genau eine Zeichenkette der Länge 0; diese wird mit  bezeichnet. Eine Zeichenkette wird auch Wort genannt.

5 Formale Sprache n: Menge aller Zeichenketten über  mit der Länge n:
*: Menge aller endlichen Zeichenfolgen über einem Alphabet : +: Menge aller nicht leeren endlichen Zeichenfolgen über einem Alphabet : Formale Sprache L: L  *

6 Grammatik Mittel zur konstruktiven Definition einer Sprache.
Wichtig für den Compilerbau: Neben den Worten, die zu einer Sprache gehören, wird auch eine Struktur der Worte definiert. Grammatik G = (, M, R, S) mit   M = , S  M und R  ((  M)* - *)  (  M)* endlich Bezeichnungen:  Grundsymble/Terminalsymbole/Terminals M Metasymbole/Nicht-Terminalsymbole/Nonterminals V :=   M Vokabular Worte   * sind terminale Worte (Zeichenketten) Worte   (  M)* - * sind nichtterminale Worte (Zeichenketten) Jede Regel (l,r)  R besitzt eine linke Seite l und eine rechte Seite r (auch Alternative genannt)

7 Ableitungsrelation Durch die Regeln R einer Grammatik ist eine Ableitungsrelation definiert: Statt (a, b)   schreiben wir auch a  b für den Ableitungsschritt von a nach b Eine Folge von Ableitungsschritten a1  a2, a2  a3,…, an-1  an wird kurz als a1  a2  a3 …an-1  an geschrieben und als Ableitung bezeichnet. Durch Bildung des reflexiven und transitiven Abschlusses * von  kann die durch G erzeugte Sprache definiert werden als: Mit  n  wird eine Ableitung mit genau n Schritten bezeichnet Insbesondere folgt aus  0 , dass  =  Mit  +  wird eine Ableitung mit mindestens einem Schritt bezeichnet.

8 Geordneter Baum Eine Knotenmenge B  * ist ein geordneter Baum, falls gilt: B ist endlich, Wenn i  B, dann auch  ( ist der Vater des Knotens i), mit   * und i  , Wenn (i+1)  B, dann auch i (i ist der linke Bruder des Knotens (i+1)), mit   * und i  . Beispiel: 1 1 0.0 0.1 0.2 1.0 1.1 1.2 B = {, 0, 1,1.0, 1.1, 1.2} B = {, 0, 1,0.0, 0.1, 0.2}

9 Syntaxbaum Sei  : B  V eine Markierung der Knoten eines geordneten Baums mit den Symbolen einer kfG G = (, M, S, R). Dann ist (B, ) genau dann ein Syntaxbaum, falls gilt: () = S   B: Falls () = A  M dann i   mit (A,[A,i])  R so dass für alle j mit 0  j < |[A,i]| gilt: (.j) = [A,i,j] .|[A,i]|  B   B: Falls () = a  , dann gilt .0  B Achtung: Nicht jedes Blatt im konkreten Syntaxbaum ist mit einem Terminalsymbol beschriftet!

10 Beispiel Syntaxbaum Grammatik G = ({+, –, *, /, n, (, )}, A, M, T}, R, A), wobei R = { (A, A+M), (A, A–M), (A, M), (M, M*T), (M, M / T), (M, T), (T, n), (T, ( A ))} A A + M M M * T n T T n n

11 Bedeutung eines Programms
Jedem syntaktisch korrekten Programm P der Sprache L ist durch eine Semantikfunktion .L eine Bedeutung PL = BP zugeordnet. Eine Bedeutung kann im Sinne der denotationalen Semantik als eine Funktion BP verstanden werden, die einer gegebenen Startbelegung der Programmvariablen im Programm P ihre Belegung zuordnet nachdem das Programm terminierte, falls das Programm unter der gegebenen Startbelegung terminiert. Für eine Eingangsbelegung e der Programmvariablen ist BP(e) = a die Belegung dieser Variablen nachdem P terminiert. Für eine Programmvariable v kann durch e(v) bzw. a(v) ihre Eingangs-/Ausgangsbelegung ermittelt werden. Beispiel: Programm P: BP(e) = a, wobei folgender Zusammenhang gilt: main() { if(t) c = c+1 }

12 Analysephase - Frontend
Aufgabe des Frontends: Q und Z seien zwei Alphabete Q  Q* sei eine Quellsprache Z  Z* sei eine Zwischensprache Das Frontend berechnet eine Funktion comp : Q*  Z  {error} mit Eine mögliche Zwischensprache ist die Menge der Syntaxbäume.

13 Synthesephase Im Backend werden in der Regel viele verschieden Zwischensprachen verwendet. Mögliche Transformationen der Zwischensprachenformate im Backend: In klassischen Compilern transformiert das Backend bedeutungserhaltend ein Zwischensprachenprogramm irp in ein Zielprogramm zp. D.h. z.B.: irp3AC = zpZC Klassische Optimierungsverfahren transformieren den Zwischencode ebenfalls bedeutungserhaltend. Das kann eine sehr starke Einschränkung für Optimierungen sein. Abschwächung: Nur für die interessierenden Ausgabevariablen v müssen die Semantikfunktionen bedeutungsgleich für alle Eingabebelegungen e sein: pQ(e)(v) = pZ(e)(v) . Optimierungen Optimierungen 3-Adress- Code Steuer- fluss- graph .3AC Optimierungen Optimierungen Syntax- baum Zielcode .ZC Optimierungen SSA- Code DAG .DAG .SSA Optimierungen

14 Beispiel Ursprüngliches Programm P Transformiertes Programm P'
main() { for(i = 0; i++; i < 10) p = p + c; } main() { for(i = 10; i--; i) p = p + c; } Für eine Eingangsbelegung e und BP(e) = a gilt der Zusammenhang: Für eine Eingangsbelegung e und BP'(e) = a gilt der Zusammenhang: Sollte oben angegebene Transformation bedeutungserhaltend sein, ist sie unzulässig. Falls sie nur bedeutungserhaltend für die interessierenden Ausgabevariablen c und p sein soll, ist sie zulässig.

15 Grundlagen Zwischencodeformate

16 3-Adress-Code (3AC) Folge von 3-Adress-Code-Anweisungen mit Markierungen. Anweisungsarten: Zuweisungen: Binäranweisungen: x := y  z Unäranweisungen: x :=  y Kopieranweisungen: x:=y, x:=&y Castoperationen: x := (Type) y Sprunganweisungen: Unbedingte Sprünge: goto index Bedingte Sprünge: if x then Label Funktionsaufrufe: x := call FLabel(y1,…,yn) Funktionsbeendigung: return x, return Markierungen: Funktionslabel: Function Label: Sprunglabel (mehrere Label hintereinander zulässig): Label1: Labe2:... Dabei sind: k Konstante, x, y, yi und z Variablen, wobei Variablen unterschieden werden in: Programmvariablen (lokal, global) Temporäre Variablen (immer lokal)

17 Beispiel: 3AC Function f: t0 := 1 fak = t0 while_0_cond: int f(int n){
t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11 int f(int n){ int fak = 1; while(n > 0) { fak = fak * n; n = n – 1; } return fak;

18 Unterschiedliche 3AC-Abstraktionsniveaus
Hohes Abstraktionsniveau Feldzugriffe bleiben erhalten. Ermöglicht z.B. Abhängigkeitsanalyse von Feldzugriffen. Mittleres Abstraktionsniveau Feldzugriffe sind in elementare Adressoperationen aufgelöst. Ermöglicht einfache Registerallokation und Codeauswahl. Niedriges Abstraktionsniveau Variablen wurde auf verfügbare Register abgebildet. Parameterübergaben in Stapeloperationen umgewandelt. Ermöglicht direkte Übersetzung in Zielcodeerzeugung; bereits architekturabhängig. t0 = 3 t10 = a[i+2*j] + b[j] a[i+2*j] = t10 + t0 t0 = 3 t1 = 2*j t2 = i+t1 t3 = j t10 = a[t2] + b[t3] a[t2] = t10 + t0 t0 = 3 t1 = 2*j t2 = i+t1 t3 = j t4 = &a t5 = t4 + t2 t6 t7 = &b t8 = t7 + t3 t9 t10 = t6 + t9 t11 = t10 + t0 @t5 = t1 Abstraktionsniveau

19 Prinzip der Transformation Syntaxbaum in 3-Adress-Code
Syntaxbaum für den Ausdruck c := a+2*b: Assign ir= t0:=a t1:=2 t2:=b t3:=t1*t2 t4:=t0+t3 c:=t4 ir=( t0:=a t1:=2 t2:=b t3:=t1*t2 t4:=t0+t3,t4) Quelltextfragment: LVal Expr id=c { int a,b,c; c := a+2*b; } IDENT id=c Expr Expr ir=(t0:=a,t0) ir=( t1:=2 t2:=b t3:=t1*t2,t3) IDENT Expr Expr id=a ir=(t1:=2,t1) ir=(t2:=b,t2) INTLIT IDENT iVal=2 id=b Für jede Knotenart (jede Art entspricht einer Regel in der Grammatik) im Syntaxbaum gibt es ein Schema, wie aus den 3-Adress-Code-Sequenzen der Söhne die 3-Adress-Code-Sequenz des Vaters gebildet wird. Generierung des 3-Adress-Codes kann dann Bottom-Up erfolgen.

20 Steuerflussgraph Modellierung aller potentiell möglichen Abarbeitungsfolgen der Anweisungen einer Funktion. Steuerflussgraph S = (N,E,q,s): q ist die Anweisung über die die Funktion betreten wird. s ist die Anweisung über die die Funktion verlassen wird (Transformation notwendig, falls mehrere return-Anweisungen in einer Funktion existieren). N ist Knotenmenge: Zu jeder Anweisung der Funktion gibt es genau einen Knoten, Kantenmenge E  N  N  {0,1} mit (a,b,0)  E gdw. b folgt im 3AC direkt auf a und a ist Zuweisung oder bedingter Sprung oder Funktionsaufruf, (a,b,1)  E gdw. a ist bedingter oder unbedingter Sprung zum Label l und b ist mit Sprunglabel l markiert.

21 Beispiel Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0
t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11 t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

22 Basisblöcke Ein Basisblock ist eine Folge maximaler Länge von Anweisungen im 3-Adress-Code, für die gilt: Nur die erste Anweisung darf mit einem Sprunglabel markiert sein (d.h., dass ein Sprung in einen Basisblock nur zu seiner ersten Anweisung führen kann) und nur die letzte Anweisung darf eine Sprunganweisung sein (d.h., dass alle Anweisungen des Basisblocks ausgeführt werden, wenn die erste Anweisung ausgeführt wird). Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11

23 Bilden von Basisblöcken im Steuerflussgraphen
Eingabe: Steuerflussgraph (N,E,q,s) Ausgabe: Zerlegung der Knoten in Basisblöcke B = {b0,…,bn} currBB = 0; B =  while(es ex. k  N, das noch keinem Basisblock zugeordnet wurde) do Wähle ein k  N, das noch keinem Basisblock zugeordnet wurde bcurrBB = {k} while(es ex. ein Knoten m  bcurrBB und n  bcurrBB und Pred(n) = {m} und Succ(m) = {n}) do bcurrBB = bcurrBB  {m} od while(es ex. ein Knoten m  bcurrBB und n  bcurrBB und Succ(n) = {m} und Pred(m) = {n}) do B := B  {bcurrBB} currBB = currBB + 1; Dabei sind: Pred(n) = {m | x  {0,1} und (m,n,x)  E} Succ(n) = {m | x  {0,1} und (n,m,x)  E} Basisblöcke bi mit 0  i  n sind Äquivalenzklassen, die eine Äquivalenzrelation bb definieren mit a bb b gdw. a  bi und b  bi.

24 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

25 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

26 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

27 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

28 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

29 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

30 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

31 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

32 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

33 Klassischer Steuerflussgraph
Im Steuerflussgraphen (N,E,q,s) wird N durch die Äquivalenzrelation bb in Äquivalenzklassen [a] = {b | a bb b} zerlegt. Dadurch ergibt sich der klassische Steuerflussgraph (N/bb,E/bb,[q],[s]) mit: N/bb = {[a] | a  N} ist die Menge der Basisblöcke, (p,q)  E/bb gdw. p,q  N/bb und a  p b  q mit (a,b)  E, [q] ist der Basisblock, der q enthält, [s] ist der Basisblock, der s enthält.

34 Beispiel t0 := 1 fak = t0 t1 := n t2 := 0 t3 := t1 > t2
t4 := not t3 if t4 then while_0_end t3 := t1 > t2 t4 := not t3 if t4 then while_0_end 1 t5 := fak t11 := fak t6 := n return t11 t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond t11 := fak return t11 1 t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond

35 Notationen N/bb = {b0,...,bn}:
über b0 wird die Funktion betreten, über b1 wird die Funktion verlassen. |bi| Anzahl der Anweisungen in Basisblock i. Basisblock bi enthält die Anweisungsfolge ir1(i),...,ir|bi|(i). Falls bi aus dem Kontext hervorgeht, auch kurz ir1,...,ir|bi|, Eine Programmposition ist ein Tupel (i,j), wobei damit die Position nach der j-ten (vor der (j+1)-ten) Anweisung im Basisblock i gemeint ist: (0,0) Position vor der ersten Anweisung einer Funktion. (1,|b1|) Position nach der letzten Anweisung einer Funktion. Ein Anweisungsfolge a1,...,ak ist ein Pfad im klassischen Steuerflussgraphen (N/bb, E/bb,[q],[s]) von Programmposition (i,j) zur Programmposition (m,n), falls: k: 1  k < k  (ak,ak+1)  E und irj+1(i) = a1 und irn(m) = ak.

36 Beispiel b0 |b2| = 5 b2 ir2(2),ir3(2),ir4(2) =
t0 := 1 fak = t0 |b2| = 5 b2 t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end ir2(2),ir3(2),ir4(2) = t2:=0,t3:=t1>t2,t4:=not t3 b3 t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond b1 t11 := fak return t11 Programmposition (1,1) Programmposition (3,7) Anweisungen auf dem Pfad von Programmposition (3,7) nach Position (1,1)

37 Qualität des Modells Steuerflussgraph modelliert alle möglichen Abarbeitungspfade des Programms, unabhängig davon, ob alle diese Pfade bei der Ausführung des Programms betreten werden können. Falls Turingmaschine i auf Eingabe i nach n Schritten stoppt, dann gibt es eine Eingabe n, so dass 4 nach 2 ausgeführt wird, sonst nicht. Eingabe von n 2 if (TMi(i)n) then 4 3 4 1

38 Grundlagen Datenflussanalyse

39 Definition/Verwendung einer Variablen
Verwendung einer Variablen v: Eine Variable v wird in der Zwischencodeanweisung iri(j) verwendet, falls iri(j) eine der Formen x := v, x :=  v, x := y  v, x := v  y, := x, return v, if v then goto label, x := (Type) v, x := call f(…,v,…) hat. Definition einer Variablen v: Eine Variable v wird in der Zwischencodeanweisung iri(j) definiert, falls iri(j) die Form v := … hat. Eine definierende Anweisung für v ist eine Anweisung, die v definiert. Eine verwendende Anweisung für v ist eine Anweisung, die v verwendet. Erreichende Definition: Die Definition einer Programmvariablen v erreicht eine Verwendung von v, falls es einen Pfad im Steuerflussgraphen von dieser Definition zur Verwendung gibt, auf dem keine andere Definition von v liegt.

40 Beispiel Erreichende Definitionen
fak = t0 t1 := n t2 := 0 3 t3 := t1 > t2 t0 := 1 4 t4 := not t3 fak = t0 6 if t4 then while_0_end t1 := n 7 t2 := 0 t5 := fak t11 := fak 8 t3 := t1 > t2 t6 := n return t11 9 t4 := not t3 t7 := t5 * t6 10 if t4 then while_0_end fak := t7 Definition zu einer Verwendung ist in unverzweigtem Steuerfluss einfach zu finden. t8 := n t9 := 1 t10 := t8 – t9 n := t10 Definition zu einer Verwendung ist bei Verzweigungen im Steuerfluss schwieriger zu finden.

41 Grundidee Datenflussanalyse am Beispiel der Erreichenden Definitionen
Es gibt eingehende Informationen I in einen Knoten: I = in(2) t2 := 2 Durch die Anweisung(en) in einem Knoten werden Informationen zerstört: I := I – Kill(2) 1 t1 := 1 in(2) = {(t2,0),(t1,1)} Kill(2) = {(t1,i) | i  } 2 t1 := n Gen(2) = {(t1,2)} Durch die Anweisung(en) in einem Knoten werden neue Informationen generiert: I := I  Gen(2) out(2) = {(t2,0),(t1,2)} 3 t3 := t1 > t2 Es entstehen ausgehende Informationen an einen Knoten: out(2) 4 if t3 then while_0_end

42 Transferfunktion für einen Basisblock
Steuerfluss in einem Basisblock i mit den Anweisungen ir0(i),…,ir#i(i) ist bekannt. Damit ist die Transferfunktion für diesen Basisblock: Vereinfachung der Transferfunktion zu out(i) = (in(i) – kill(i))  gen(i). in(i) - Kill(0)  Gen(0) t2 := 2 1 - Kill(1)  Gen(1) t1 := 1 2 - Kill(2)  Gen(2) t1 := n 3 - Kill(3)  Gen(3) t3 := t1 > t2 4 - Kill(4)  Gen(4) if t3 then while_0_end out(i)

43 Datenflussanalyse bei Verzweigungen im Steuerflussgraphen
Ausgehende Informationen out(i) gelangen zu jedem Steuerflussnachfolger von i. Treffen Informationen von mehreren Steuerflussvorgängern zusammen, müssen diese zu einer eingehenden Information zusammengefasst werden. Transformiert eingehende Informationen t3 := t1 + t2 Ausgehende Informationen I I Transformiert eingehende Informationen I t0 := t1 – t9 t2 := t3 * t5 Transformiert eingehende Informationen Hier müssen Informationen kombiniert werden

44 Grundprinzip Datenflussanalyse
Informationen I breiten sich entweder mit oder gegen den Steuerfluss aus. Für jeden Knoten b gibt es: Eingehenden Informationen: in(b), ausgehende Informationen: out(b), erzeugte Informationen: gen(b), zerstörte Informationen: kill(b). Abhängig von der Ausbreitungsrichtung der Informationen sind: Vorwärtsanalyse: in(b) = out(b1)  out(b2)  …  out(bn), wobei die bi die Steuerflussvorgänger von b sind und out(b) = (in(b) – kill(b))  gen(b) (Transferfunktion genannt) Rückwärtsanalyse: out(b) = in(b1)  in(b2)  …  in(bn), wobei die bi die Steuerflussnachfolger von b sind und in(b) = (out(b) – kill(b))  gen(b) (Transferfunktion genannt) Durch den Steuerflussgraphen wird eine Mengengleichungssystem definiert. Falls der Steuerflussgraph Zyklen enthält, ist das Mengengleichungssystem rekursiv; Lösung durch Fixpunktiteration.

45 Beispiel: Erreichende Definitionen
Bei Verwendung einer Programmvariablen an Position (i,j) interessiert, an welchen Programmpositionen der Wert der Variablen definiert wurde. Menge aller Informationen: (()V) Vorwärtsanalyse mit  :=  gen(bi) := {((i,j),v) | irj(i) ist letzte Definition von v in bi} kill(bi) := {((m,n),v) | m, n   und bi enthält eine Definition von v} gen(b2) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} kill(b2) = {((m,n),t0), ((m,n),t1), ((m,n),t2)} in(b2) = in(b3) = in(b4) = in(b1) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)}  {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,1),t1), ((2,2),t2), ((3,1),t0), ((2,0),t0), ((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)}  {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((3,1),t0)}  b0 b2 t0 := a t1 := b t2 := t0 + t1 gen(b3) = {((3,1),t0)} kill(b3) = {((m,n),t0)} out(b0) = out(b2) = out(b3) = out(b4) = {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((2,1),t1), ((2,2),t2), ((3,1),t0)} {((2,0),t0), ((2,1),t1)}  {((4,0),t3), ((4,1),t4), ((4,2),t2)} {((2,0),t0), ((2,1),t1), ((2,2),t2)} {((3,1),t0)} {((4,0),t3), ((4,1),t4), ((4,2),t2)} b3 t0 := t2 – t0 t0 := b b4 t3 := t2 t4 := t3 * t2 t2 := t2 – t4 b1 gen(b4) = {((4,0),t3), ((4,1),t4), ((4,2),t2)} kill(b4) = {((m,n),t3), ((m,n),t4), ((m,n),t2)}

46 Beispiel: Lebendigkeit
An einer Programmposition (i,j) interessiert für eine Variable v, ob es einen Pfad zu einer Programmposition gibt, an der v verwendet wird, ohne auf diesem Pfad definiert zu werden. Menge aller Informationen: (V) Rückwärtsanalyse mit  :=  gen(bi) := {v | irj(i) ist Verwendung von v und für alle 0  k < j gilt: irk(i) ist keine Defintion von v} kill(bi) := {v | v wird in bi definiert} gen(b2) = {a,b} kill(b2) = {t0,t1,t2} b0 in(b0) = in(b2) in(b2) = (in(b3)  in(b4) – kill(b2))  gen(b2) in(b3) = (in(b1) – kill(b3))  gen(b3) in(b4) = ((in(b4)  in(b1)) – kill(b4))  gen(b4) b2 t0 := a t1 := b t2 := t0 + t1 gen(b4) = {t2} kill(b4) = {t2,t3,t4} gen(b3) = {b, t2, t0} kill(b3) = {t0} b3 t0 := t2 – t0 t0 := b b4 t3 := t2 t4 := t3 * t2 t2 := t2 – t4 b1 gen(b1) =  kill(b1) = 

47 Zwischencodeformate (fortgesetzt)
Grundlagen Zwischencodeformate (fortgesetzt)

48 SSA-Code Eigenschaften: Zwei Schritte zur Konstruktion von SSA-Code:
Jede Variable wird statisch genau einmal definiert. Vorteil für viele Analysen: Jede Benutzung einer Variablen hat nur genau eine Definition. An solchen Punkten, an denen der Steuerfluss zusammentrifft, müssen -Funktionen eingefügt werden, die verschiedene Instanzen derselben Variablen zusammenführen. Zwei Schritte zur Konstruktion von SSA-Code: Programmpositionen finden, an denen eine -Funktion eingefügt werden muss. Variablenumbenennung (Indizierung), um eindeutige Namen für jede Definition zu erzeugen. v4 := … v := … v1 := … v := … v2 := … v := … … := v … := v4 … := v v :=  … := v v3 := (v1,v2) … := v3 v5 := (v3,v4) … := v5 … := v v :=  … := v

49 Dominator Block x dominiert Block y (x dom y) genau dann im Steuerflussgraphen, wenn jeder Pfad vom Startknoten zum Block y durch Block x führt. Doms(y) := {x | x dom y} Block x dominiert Block y streng (x sdom y), wenn x dom y und x  y. Beispiel: 5 dominiert 5, 6, 7, 8 5 dominiert streng 6, 7, 8 6 dominiert 6 6 dominiert streng  nicht dominiert von 5: 0, 1, 2, 3, 4, 9 nicht streng dominiert von 5: 0, 1, 2, 3, 4, 5, 9 Doms(3) = {0, 2, 3} Doms(4) = {0, 4} 2 5 3 6 7 4 8 9 1

50 Berechnung von Doms Berechnung der Information Doms(x) für Steuerflussgraphen S = (N,E,q,s) durch eine Datenflussanalyse mit den Parametern: Menge aller Informationen: (N) Vorwärtsanalyse mit  :=  gen(bi) := {i} kill(bi) :=  Starten mit in(bi) = N für i > 0 und in(b0) = 

51 Beispiel: Berechnung Doms
Doms(x) idom(x) out(x) out(0) = {0} = {0} out(1) = (out(0)  out(9))  {1} = {0,1} out(2) = out(1)  {2} = {0,1,2} out(3) = (out(2)  out(1)  out(8)  out(4))  {3} = {0,1,3} out(4) = (out(3)  out(7))  {4} = {0,1,3,4} out(5) = (out(4))  {5} = {0,1,3,4,5} out(6) = (out(4))  {6} = {0,1,3,4,6} out(7) = (out(5)  out(6)  out(10))  {7} = {0,1,3,4,7} out(8) = out(7)  {8} = {0,1,3,4,7,8} out(9) = out(8)  {9} = {0,1,3,4,7,8,9} out(10) = out(8)  {10} = {0,1,3,4,7,8,10} 1 3 4 7 8 1 2 3 4 5 6 7 8 Der direkte Dominator idom(x) eines Blocks x in einem Steuerflussgraphen ist der Block y  Doms(x) – {x}, so dass für jeden Block z  Doms(x) – {x} gilt: z  Doms(y). 9 10 Steuerflussgraph

52 Beispiel: Dominatorbaum
1 x idom(x) 1 2 3 4 5 6 7 8 9 10 1 3 4 7 8 1 2 2 3 3 4 4 5 6 6 7 7 5 6 8 7 9 10 8 9 10 Steuerflussgraph Dominatorbaum (grafische Darstellung der idom-Relation).

53 Dominanzgrenze (Dominance Frontier)
Die Dominanzgrenze DF(x) eines Blocks x ist die Menge der Blöcke y im Steuerflussgraphen S = (N,E,q,s) für die gilt: p  N: (p,y)  E und x dom p und (x,y)  sdom, d.h. y ist nicht streng dominiert von x. Eigenschaft der Knoten in DF (x): Falls ein Block y zu DF(x) gehört, dann gibt es vom Startknoten des Steuerflussgraphen einen Pfad nach y, der durch x führt und einen Pfad, der nicht durch x führt. Intuition: DF(x) sind auf den von x ausgehenden Pfaden die ersten Blöcke, die von x aus erreicht werden können, aber nicht von x dominiert werden. y  DF(x) ist damit ein Knoten, an dessen Beginn eine -Funktion für jede in x definierte Variable eingefügt werden muss, weil Definitionen dieser Variablen von unterschiedlichen Programmpositionen y erreichen können.

54 Beispiel: Dominanzgrenze
nicht streng dominiert von x x dominiert DF(x) 1 1 {1,…,10} {1} {1} 2 1 3 2 3 4 4 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

55 Beispiel: Dominanzgrenze
nicht streng dominiert von x x dominiert DF(x) 1 1 {1,…,10} {1} {1} 2 {2} {1,…,10} {3} 2 1 3 2 3 4 4 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

56 Beispiel: Dominanzgrenze
nicht streng dominiert von x x dominiert DF(x) 1 1 {1,…,10} {1} {1} 2 {2} {1,…,10} {3} 2 1 3 {3,…,10} {1,2,3} {1,3} 3 2 3 4 4 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

57 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 4 4 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

58 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

59 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 6 {6} {7} 5 6 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

60 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 6 {6} {7} 5 6 7 {7,8,9,10} {1,3,4,7} 5 6 6 7 7 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

61 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 6 {6} {7} 5 6 7 {7,8,9,10} {1,3,4} 5 6 6 7 7 8 {8,9,10} {1,3,7} 7 8 8 9 10 9 10 Steuerflussgraph Dominatorbaum

62 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 6 {6} {7} 5 6 7 {7,8,9,10} {1,3,4} 5 6 6 7 7 8 {8,9,10} {1,3,7} 7 8 9 {9} {1} 8 9 10 9 10 Steuerflussgraph Dominatorbaum

63 Beispiel: Dominanzgrenze
x dominiert DF(x) 1 1 {1,…,10} {1} 2 {2} {3} 2 1 3 {3,…,10} {1} 3 4 {4,…,10} {1,3} 2 3 5 {5} {7} 4 4 6 {6} {7} 5 6 7 {7,8,9,10} {1,3,4} 5 6 6 7 7 8 {8,9,10} {1,3,7} 7 8 9 {9} {1} 8 9 10 10 {10} {7} 9 10 Steuerflussgraph Dominatorbaum

64 -Funktionen einfügen
Für jede definierte Variable v in Block x muss eine -Funktion in jedem Block y  DF(x) eingefügt werden. Wird eine -Funktion für eine Variable v in einem Block y  DF(x) eingefügt, ist das eine neue Definition für v. Damit muss auch in jeden Block z  DF(y) eine -Funktion für v eingefügt werden. 3: v := … 4: v := … 5: v := … 6: v := … DF(3) = {7} DF(4) = {7} DF(5) = {8} DF(6) = {8} 7: … := v 8: … := v DF(7) = {9} DF(8) = {9} 9: … := v

65 Algorithmus -Funktionen einfügen
PutPhiHere(x)..Menge der Variablen für die in Block x eine -Funktion eingefügt werden muss Def(x)..Menge der in Block x definierten Variablen Steuerflussgraph S = (N,E,q,s) for each x  N do toProcess := DF(x) alreadyProcessed :=  while es existiert y  toProcess do PutPhiHere(y) := PutPhiHere(y)  Def(x) alreadyProcessed := alreadyProcessed  {y} toProcess := (toProcess  (DF(y)) – alreadyProcessed od

66 Beispiel 3: v := … 4: v := … 5: v := … 6: v := … 7: v :=  … := v 8:
9:

67 Variablenumbenennung
Für jede Variable v: Durchnummerieren der Definitionen von v mittels num(v,i,j), wobei (i,j) Programmposition der Definition von v ist. Erreichende Defintionen für Steuerflussgraph mit -Funktionen berechnen (v :=  wird wie eine Definition für v behandelt). Jede Verwendung von v wird wegen der Konstruktion nur noch von einer Definition (i,j) erreicht: Umbenennung dieser Verwendung von v in vnum(v,i,j). Eine -Funktionen für v wird im Allgemeinen von mehreren Definitionen (i0,j0),…,(in,jn) erreicht: Ersetze v :=  durch v := (vnum(v,i0,j0),…,vnum(v,in,jn)). Durch Dead-Code-Eliminierung können überflüssige -Funktionen entfernt werden.

68 Beispiel Durchnummerieren der Definitionen von v 3: v0 := … 4: v1 := …
5: v2 := … 6: v3 := … 7: v4 :=  … := v 8: v5 :=  … := v v6 :=  … := v 9: Durchnummerieren der Definitionen von v

69 Beispiel (Fortsetzung)
3: v0 := … 4: v1 := … 5: v2 := … 6: v3 := … ((5,0),v2), ((6,0),v3) ((3,0),v0), ((4,0),v1) 7: v4 :=  … := v 8: v5 :=  … := v ((8,0),v5) ((7,0),v4) ((7,0),v4), ((8,0),v5) v6 :=  … := v 9: ((9,0),v6) Berechnung der erreichenden Definitionen

70 Beispiel (Fortsetzung)
3: v0 := … 4: v1 := … 5: v2 := … 6: v3 := … ((5,0),v2), ((6,0),v3) ((3,0),v0), ((4,0),v1) 7: v4 := (v0,v1) … := v4 8: v5 := (v2,v3) … := v5 ((8,0),v5) ((7,0),v4) ((7,0),v4), ((8,0),v5) v6 := (v4,v5) … := v6 9: ((9,0),v6) Umbenennung

71 Erweiterung des Steuerflussgraphen um SSA-Kanten
v0 := … v1 := … v2 := … v3 := … v4 := (v0,v1) v5 := (v2,v3) … := v4 … := v5 v6 := (v4,v5) … := v6 SSA-Kante (q,s) gibt es genau dann, wenn die Anweisung im Knoten q die Variable v definiert und die Anweisung im Knoten s die Variable v verwendet.

72 Variablenumbenennung ohne iterative Analyse
renameVariable(x,b) x ... Umzubenennende Variable b ... Basisblock im Steuerflussgraphen V ... Stack mit Versionsnummern der Variable x rollback = Top(V) for each s  b do if s ist keine -Anweisung then Ersetze alle Verwendungen von x in s durch xTop(V) fi if s definiert x then Ersetze Definition von v durch vcurrVersion; push(currVersion,V); currVersion++; fi od for each succ  Succ(b) do b sei j-ter Vorgänger von succ und x := (...) Phi-Funktion für x in succ Setze j-tes Argument von x := (...) auf xTop(V); for each c, wobei c Kind von b im Dominatorbaum ist do renameVariable(x,c) while (Top(V)  rollBack) do Pop(V) od Aufruf durch: for each Variable x do currVersion = 1; push(0,V) renameVariable(x,0) od

73 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0 2 3 7 4
i := 0 read n 2 i = () n = () n  1 2 3 7 j n 3 even(n) 7 print(i) 4 6 5 6 7 1 j n 4 n := n / 2 5 n := 3n + 1 1 Stopp 6 n := () i := i + 1 V = 0

74 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1 2 3 7
i := 0 read n1 2 i = () n = ( ,n1) n  1 2 3 7 j n 3 even(n) 7 print(i) 4 6 5 6 7 1 j n 4 n := n / 2 5 n := 3n + 1 1 Stopp 6 n := () i := i + 1 V = 0,1

75 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2 2 3
i1 := 0 read n 2 i2 = () n2 = ( ,n1) n2  1 2 3 7 j n 3 even(n) 7 print(i) 4 6 5 6 7 1 j n 4 n := n / 2 5 n := 3n + 1 1 Stopp 6 n := () i := i + 1 V = 0,1,2

76 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2 2 3
i1 := 0 read n 2 i2 = () n = ( ,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n := n / 2 5 n := 3n + 1 1 Stopp 6 n := () i := i + 1 V = 0,1,2

77 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2,3 2
i1 := 0 read n 2 i2 = () n = ( ,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n := 3n + 1 1 Stopp 6 n := (n3, ) i := i + 1 V = 0,1,2,3

78 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2 2 3
i1 := 0 read n 2 i2 = () n = ( ,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n := 3n + 1 1 Stopp 6 n := (n3, ) i := i + 1 V = 0,1,2

79 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2,4 2
i1 := 0 read n 2 i2 = () n = ( ,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n := (n3, n4) i := i + 1 V = 0,1,2,4

80 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2 2 3
i1 := 0 read n 2 i2 = () n = ( ,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n := (n3, n4) i := i + 1 V = 0,1,2

81 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2,5 2
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0,1,2,5

82 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1,2 2 3
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0,1,2

83 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1 2 3 7
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0,1

84 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1 2 3 7
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0,1

85 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0,1 2 3 7
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0,1

86 Beispiel Steuerflussgraph mit -Funktionen Dominatorbaum V = 0 2 3 7 4
i1 := 0 read n 2 i2 = () n = (n5,n1) n  1 2 3 7 j n 3 even(n2) 7 print(i) 4 6 5 6 7 1 j n 4 n3 := n2 / 2 5 n4 := 3n2 + 1 1 Stopp 6 n5 := (n3, n4) i := i + 1 V = 0

87 Anwendung des SSA-Codes
Die Informationen Erreichende Definitionen und Nächste Verwendung sind direkt an den SSA-Kanten ablesbar. Vereinfachung der Eliminierung toten Codes: Knoten, die eine Variable definieren und keine weiteren Seiteneffekte verursachen und von denen keine SSA-Kante ausgeht, können gelöscht werden (einschließlich der eingehenden SSA-Kanten). Dadurch können weitere Knoten ohne ausgehende SSA-Kanten entstehen. Vereinfachung der Konstantenpropagierung.

88 Konstantenpropagierung
Steuerflussgraph ist in SSA-Form und jeder Knoten enthält genau eine Anweisung. W sei eine Liste aller Knoten im Steuerflussgraphen Solange W nicht leer ist: Entferne Knoten s aus W Falls s die Form v := (c1, …,ck) hat und c1 = c2 = … = ck sind Konstanten: Ersetze s durch v := c1 Falls s die Form v := c hat und c eine Konstante ist: Lösche s aus dem Graphen Für jeden Knoten t, der v verwendet: Ersetze in t v durch c Füge t zur Liste W hinzu Verbesserung durch Bedingte Konstantenpropagierung: Konstantenpropagierung und Eliminierung von Blöcken, die wegen eines konstanten Ausdrucks in einer bedingten Verzweigung nicht erreicht werden können.

89 Zusammenfassung SSA-Code
Explizite Darstellung von Definition und Verwendung einer Variablen. Dadurch Vereinfachung von: Konstantenpropagierung, Eliminierung toten Codes, Vorwärtsersetzung von Ausdrücken (später), Finden von Induktionsvariablen, Verschiebung von schleifeninvariantem Code. Verschiedene Optimierungsalgorithmen benötigen keine Datenflussanalyse mehr. Sie arbeiten mit dem Dominatorbaum oder der Dominanzgrenze. Dadurch werden globale Optimierungsverfahren teilweise so einfach wie lokale. Nicht alle Optimierungsverfahren profitieren vom SSA-Code; z.B. Lebendigkeitsanalyse.

90 Anwendung des Dominators: Erkennen von Schleifen
Eine Schleife (natural loop) im Steuerflussgraphen ist gekennzeichnet durch: Eine Menge L von Knoten, die auf einem Zykel im Steuerflussgraphen liegen. Es gibt einen ausgezeichneten Knoten h in L, der der einzige Knoten in L mit einem Vorgängerknoten v  L ist. Der Knoten h wird Kopf genannt. Eine Kante (t,h) im Steuerflussgraphen ist eine Rücksprungkante in der Schleife L, falls h der Kopf von L ist und t  L.

91 Finden von Schleifen Berechnung der Relation dom.
Finden von Rücksprungkanten durch Tiefensuche im Steuerflussgraphen (N,E,q,s) mit dfs(N,E,q): Für jede Rücksprungkante (t,h) Berechnung der Knoten, die zur Schleife mit Kopf h gehören durch: succsessors := Succ(h); Lh := {h}; Löschen des Knotens h mit allen adjazenten Kanten aus dem Steuerflussgraphen. Für jeden Knoten u, der Nachfahre eines Knotens in successors ist: Falls (u,t)  E+, dann Lh := Lh  {u}. dfs(N,E,node,fromNode) { if alreadyVisited(node) then if (node dom fromNode) then addToLoopHeads(fromNode, node) fi return; fi alreadVisited(node) = True; for each (node,dest)  E do dfs(N,E,dest,node) od }

92 Beispiel Besuchsreihenfolge der Knoten: 0, 2, 3, 4, 6, 2, 5, 6, 7, 1
i := 0 read n Besuchsreihenfolge der Knoten: 0, 2, 3, 4, 6, 2, 5, 6, 7, 1 2 n  1 2 dom 6 6 dom 5 3 even(n) 7 print(i) Rücksprungkante = (6,2); Kopf = 2 succsessors = {3,7} 4 n := n / 2 5 n := 3n + 1 1 Stopp L2 = {2,3,4,5,6} 6 i := i + 1 dfs(N,E,node,fromNode) { if alreadyVisited(node) then if (node dom fromNode) then addToLoopHeads(fromNode, node) fi return; fi alreadVisited(node) = True; for each (node,dest)  E do dfs(N,E,dest,node) od }

93 Modellierung von Abhängigkeiten
Grundlagen Modellierung von Abhängigkeiten

94 Steuerflussabhängigkeiten
Steuerflussabhängigkeiten: Im Steuerflussgraphen ist Knoten b steuerflussabhängig von Knoten a, falls: ein Pfad a,s1,...,sn,b für n  0 im Steuerflussgraphen existiert, so dass alle Knoten s1,...,sn von b postdominiert werden und a nicht von b postdominiert wird. Knoten steuerflussabhängig von - 1 - 2 3 2 3 4 4 2,3 5 2 5 6 6 0,2 1

95 Datenabhängigkeiten Datenabhängigkeiten: Im Steuerflussgraphen ist eine Anweisungen b von Anweisung a datenabhängig genau dann, wenn: a und b auf dasselbe Datum zugreifen und mindestens eine dieser Anweisungen schreibend auf das Datum zugreift und ein Pfad von Anweisung a zu Anweisung b existiert. Wenn b von a datenabhängig ist, dann wird a Quelle und b Senke der Abhängigkeit genannt. Datenabhängigkeiten ergeben sich durch die Reihenfolge, in der auf Daten lesend und schreibend zugegriffen wird. Steuerfluss- und Datenabhängigkeiten erzwingen eine Reihenfolge bei der Abarbeitung der Anweisungen, die nicht verändert werden darf, da sonst die Bedeutung des Programms verändert werden kann.

96 Arten von Datenabhängigkeiten
Im Folgenden: Anweisung b kann nach Anweisung a ausgeführt werden: Fluss-Abhängigkeit (True Dependency, Flow Dependency, RAW): Eine Anweisung b ist genau dann fluss-abhängig von Anweisung a, wenn a ein Datum schreibt, das von b gelesen wird: Anti-Abhängigkeit (Antidependency, WAR): Eine Anweisung b ist genau dann anti-abhängig von Anweisung a, wenn a ein Datum liest, das von b geschrieben wird: Ausgabe-Abhängigkeit (Output Dependency, WAW): Eine Anweisung b ist genau dann Ausgabe-abhängig von Anweisung a, wenn a ein Datum schreibt, das auch von b geschrieben wird: Falscher Wert wird genutzt. a: t2 = t0 + t1 b: t4 = t2 + t3 b: t4 = t2 + t3 a: t2 = t0 + t1 Falscher Wert wird genutzt. a: t0 = x b: x = t1 b: x = t1 a: t0 = x a: x = t0 b: x = t1 c: t4 = x b: x = t1 a: x = t0 c: t4 = x Falscher Wert wird genutzt.

97 Geschachtelte normalisierte Schleifen
Betrachtung des inneren Schleifenkörpers eines Schleifennestes mit einer Indexvariable je Schleife. d ist die Schachtelungstiefe. ik ist die Indexvariable in Schachtelungsebene k. Lk ist die untere Grenze der Schachtelungsebene k. Uk ist die obere Grenze der Schachtelungsebene k. hk und tk sind Anweisungsfolgen ohne Steuerflussanweisungen. for i1 = L1 to U1 step 1 do h1; for i2 = L2 to U2 step 1 do ... for id = Ld to Ud step 1 do hd; od t1

98 Normalisierung der Indexvariablen
Jede Indexvariable ist normalisiert. Das heißt sie wird in jeder Iteration um 1 erhöht. Transformation einer Schleife l durch folgenden Algorithmus möglich: Ersetze den Schleifenkopf for i = L to U step S durch for i' = 0 to (U–L)/S step 1 Ersetze jede Benutzung von i im Schleifenkörper durch (i' * S + L) Füge unmittelbar nach der Schleife l die Anweisung i = i' * S + L ein.

99 Indexvektor / Iterationsraum
Iterationsraum: I = {(i1,...,id) | Lk  ik  Uk für alle 1  k  d} Ein Indexvektor (i1,...,id)  I repräsentiert eine Belegung der Laufvariablen eines Schleifennestes der Tiefe d. Lexikografische Ordnung der Vektoren in I: (i1,...,id) < (j1,...,jd), falls es existiert ein k mit 1  k  d und ik < jk und für alle 1  n < k: in = jn. Jeder Indexvektor entspricht einer Abarbeitung des Schleifenkörpers hd. Lexikografische Ordnung der Indexvektoren entspricht der durch den Programmierer spezifizierten Abarbeitungsreihenfolge des Schleifenkörpers hd bei Abarbeitung des Schleifennestes.

100 Beispiel Iterationsraum
for i = 0 to 3 step 1 do for j = 0 to 2 step 1 do a[i][j] = b[j]; b[j] = b[j] + 1; od i = 3 a[i][j] = b[j]; b[j] = b[j] + 1; j = 0 a[i][j] = b[j]; b[j] = b[j] + 1; j = 1 j = 2 a[i][j] = b[j]; b[j] = b[j] + 1; i = 2 j = 0 a[i][j] = b[j]; b[j] = b[j] + 1; j = 1 a[i][j] = b[j]; b[j] = b[j] + 1; j = 2 a[i][j] = b[j]; b[j] = b[j] + 1; I = { (0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2), (3,0), (3,1),(3,2)} i = 1 a[i][j] = b[j]; b[j] = b[j] + 1; j = 0 a[i][j] = b[j]; b[j] = b[j] + 1; j = 1 j = 2 a[i][j] = b[j]; b[j] = b[j] + 1; i = 0 a[i][j] = b[j]; b[j] = b[j] + 1; j = 0 a[i][j] = b[j]; b[j] = b[j] + 1; j = 1 j = 2 a[i][j] = b[j]; b[j] = b[j] + 1; Als Instanz einer Anweisung wird eine Ausführung dieser Anweisung bei der Programmabarbeitung bezeichnet. Menge aller Instanzen der Anweisungen in einem Schleifennest: {sv | s ist Anweisung und v  I}.

101 Abhängigkeiten in Schleifen (1)
In einem Schleifenkörper existiert eine Abhängigkeit von Anweisung s zu Anweisung t genau dann, wenn: zwei Iterationsvektoren u und v mit u < v oder u = v existieren und ein nicht leerer Pfad von su nach tu existiert und Instanz su von s greift auf Speicherposition m zu und Instanz tv von t greift auf Speicherposition m zu und einer dieser beiden Zugriffe ist ein schreibender Zugriff. Beispiele: for i = 2 to 4 step 1 do for j = 2 to 3 step 1 do (s) a = i * j; (t) b = a * a; od (t) besitzt eine Fluss-Abhängigkeit von (s) wegen a=i*j(n,m) und b=a*a(n,m). (s) besitzt eine Anti-Abhängigkeit von (t) wegen a=i*j(n,m) und b=a*a(u,v) und (u,v)  (n,m) (s) besitzt eine Ausgabe-Abhängigkeit von (s) wegen a=i*j(n,m) und a=i*j(u,v) für (u,v) < (n,m) (t) besitzt eine Ausgabe-Abhängigkeit von (t) wegen b=a*a(n,m) und b=a*a(u,v) für (u,v) < (n,m)

102 Abhängigkeiten in Schleifen (2)
for i = 2 to 4 step 1 do for j = 2 to 3 step 1 do (s) a[i][j] = a[i][j] * 2; (t) a[i-1][j-1] = a[i][j] - 2; od i = 2 j = 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 Feld a: i = 2 j = 3 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 1,1 1,2 1,3 (t) besitzt eine Fluss-Abhängigkeit von (s). t(2,2) t(2,3) i = 3 j = 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 2,1 2,2 2,3 (t) besitzt eine Ausgabe-Abhängigkeit von (s). s(2,2) s(2,2) s(2,3) s(2,3) t(3,2) t(3,3) t(2,2) t(2,3) i = 3 j = 3 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 3,1 3,2 3,3 i (t) besitzt eine Anti-Abhängigkeit von (s). s(3,2) s(3,2) s(3,3) s(3,3) t(4,2) t(4,3) t(3,2) t(3,3) i = 4 j = 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 4,1 4,2 4,3 s(4,2) s(4,2) s(4,3) s(4,3) t(4,2) t(4,3) i = 4 j = 3 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 a[i][j] = a[i][j] * 2 a[i-1][j-1] = a[i][j] - 2 j

103 Distanzvektor Distanzvektor: Falls eine Abhängigkeit von su nach tv existiert, dann ist der Distanzvektor d(u,v) definiert als: d(u,v)k = vk – uk, wobei 1  k  n und n die Länge der Vektoren u und v ist. Ein Distanzvektor charakterisiert den Abstand zweier datenabhängiger Anweisungsinstanzen bezüglich ihres Abstandes im Iterationsraum der Schleife. Beispiel: Feld a: 1,1 1,2 1,3 Distanz für s(2,2) und t(2,2): d((2,2),(2,2)) = (0,0) t(2,2) t(2,3) 2,1 2,2 2,3 Distanz für s(3,2) und t(4,3): d((3,2),(4,3)) = (1,1) s(2,2) s(2,2) s(2,3) s(2,3) t(3,2) t(3,3) t(2,2) t(2,3) 3,1 3,2 3,3 i s(3,2) s(3,2) s(3,3) s(3,3) t(4,2) t(4,3) t(3,2) t(3,3) 4,1 4,2 4,3 s(4,2) s(4,2) s(4,3) s(4,3) t(4,2) t(4,3) j

104 Richtungsvektor Falls eine Abhängigkeit von su nach tv existiert, dann ist der Richtungsvektor D(u,v) definiert als: Der Richtungsvektor abstrahiert von der Distanz und reduziert die Abhängigkeit auf die Relation zwischen den Komponenten der Richtungsvektoren. Beispiel: Schreibender Zugriff mit Indexvektor (x,y,z) auf Feldelement a[x+1][y][z-1]: Richtungsvektor: D((x,y,z),(x+1,y,z-1)) = (<,=,>) 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][k-1] = a[i][j][k] * 2; od a[i+1][j][k-1](x,y,z) Flussabhängigkeit Lesender Zugriff auf a[x+1][y][z-1] mit Indexvektor (x+1,y,z-1): a[i][j][k](x+1,y,z-1)

105 Anzahl der Abhängigkeiten bei Betrachtung der Anweisungsinstanzen
Eine Abhängigkeit zwischen jedem Paar von Anweisungsinstanzen, das auf dieselbe Speicheradresse zugreift: for i = 1 to 10 step 1 do for j = 1 to 10 step 1 do a[i+1][j] = a[i][j] * 2; od Insgesamt 90 Abhängigkeiten! Feld a: 11,1 11,2 11,10 i = 10 a[i+1][j] = a[i][j] * 2 j = 1 a[i+1][j] = a[i][j] * 2 j = 2 ... j = 10 a[i+1][j] = a[i][j] * 2 3,1 3,2 3,10 ... i = 2 j = 1 a[i+1][j] = a[i][j] * 2 j = 2 a[i+1][j] = a[i][j] * 2 ... j = 10 a[i+1][j] = a[i][j] * 2 2,1 2,2 2,10 i = 1 a[i+1][j] = a[i][j] * 2 j = 1 a[i+1][j] = a[i][j] * 2 j = 2 ... a[i+1][j] = a[i][j] * 2 j = 10 1,1 1,2 1,10

106 Anzahl der Abhängigkeiten bei Betrachtung der Distanzvektoren
Zusammenfassen aller Abhängigkeiten mit demselben Distanzvektor zwischen Instanzen derselben zwei Anweisungen : for i = 1 to 10 step 1 do for j = 1 to 10 step 1 do a[i+1][j] = a[i][j] * 2; od Insgesamt 1 Abhängigkeit! Schreibender Zugriff mit Indexvektor (x,y) auf Feldelement a[x+1][y]: Distanzvektor für jede Abhängigkeit: d((x,y),(x+1,y)) = (1,0) a[i+1][j](x,y) Richtungsvektor für jede Abhängigkeit: D((x,y),(x+1,y)) = (<,=) Lesender Zugriff auf a[x+1][y] mit Indexvektor (x+1,y): a[i][j](x+1,y)

107 Anzahl der Abhängigkeiten bei Betrachtung der Richtungsvektoren
RAW, (0,6), (=,<) RAW, (0,4), (=,<) a[j][i] = b[i][j] * 2; for j = 1 to 10 step 1 do for i = 1 to 7 step 1 do a[j][i] = b[i][j] * 2; c[i][j] = a[j][8-i] + 4; od RAW, (0,2), (=,<) RAW, (0,0), (=,=) (=,<) (=,=) (=,<) WAR, (0,2), (=,<) WAR, (0,4), (=,<) c[i][j] = a[j][8-i] + 4; WAR, (0,6), (=,<) j,1 j,2 j,3 j,4 j,5 j,6 j,7 j a[j][i] = ... ... = a [8-i]... i = 1 a[j][i] = ... ... = a [8-i]... i = 2 i = 3 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 4 i = 5 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 6 i = 7 a[j][i] = ... ... = a [8-i]...

108 Unterteilung von Abhängigkeiten in Schleifen
Schleifenunabhängige Abhängigkeiten: Anweisung t besitzt eine schleifenunabhängige Abhängigkeit von Anweisung s, gdw. t auf Speicherposition m in Iteration u und s auf Speicherposition m in Iteration v zugreift und u = v und es existiert ein nicht leerer Pfad von s nach t. Schleifengetragene Abhängigkeiten: Anweisung t besitzt eine schleifengetragene Abhängigkeit von s, gdw. s auf Speicherposition m in Iteration u und t auf Speicherposition m in Iteration v zugreift und u < v. j,1 j,2 j,3 j,4 j,5 j,6 j,7 j i = 1 a[j][i] = ... ... = a [8-i]... i = 2 a[j][i] = ... ... = a [8-i]... i = 3 a[j][i] = ... ... = a [8-i]... i = 4 a[j][i] = ... ... = a [8-i]... i = 5 a[j][i] = ... ... = a [8-i]... i = 6 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 7

109 Schleifengetragene Abhängigkeiten
Eine schleifengetragene Abhängigkeit von s nach t ist rückwärtsgerichtet, gdw. t im Schleifenkörper vor s steht oder s = t, vorwärtsgerichtet, wenn t hinter s steht. j,1 j,2 j,3 j,4 j,5 j,6 j,7 j i = 1 a[j][i] = ... ... = a [8-i]... i = 2 a[j][i] = ... ... = a [8-i]... i = 3 a[j][i] = ... ... = a [8-i]... i = 4 a[j][i] = ... ... = a [8-i]... i = 5 a[j][i] = ... ... = a [8-i]... i = 6 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 7

110 Level einer schleifengetragenen Abhängigkeit
Der Level einer schleifengetragenen Abhängigkeit von su nach tv ist i, falls, D(u,v)i = < und für alle 1  k < i D(u,v)k = =. Charakterisierung einer Menge von Abhängigkeiten durch die erste Schleife im Schleifennest möglich, für die es verschiedene Iterationen gibt, zwischen denen Abhängigkeiten bestehen. Beispiel: Alle schleifengetragenen Abhängigkeiten haben Richtungsvektor (=,<) und damit den Level 2. j=10 a[j][i] = ... ... = a [8-i]... i = 1 a[j][i] = ... ... = a [8-i]... i = 2 i = 3 a[j][i] = ... ... = a [8-i]... i = 4 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 5 a[j][i] = ... ... = a [8-i]... i = 6 i = 7 a[j][i] = ... ... = a [8-i]... ... j=1 a[j][i] = ... ... = a [8-i]... i = 1 a[j][i] = ... ... = a [8-i]... i = 2 i = 3 a[j][i] = ... ... = a [8-i]... i = 4 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 5 i = 6 a[j][i] = ... ... = a [8-i]... a[j][i] = ... ... = a [8-i]... i = 7

111 Abhängigkeitsgraph s t Abhängigkeitsgraph (N,D,A,O,level):
N ist Knotenmenge; jeder Knoten repräsentiert eine Anweisung, D  N  N Flussabhängigkeiten, A  N  N Antiabhängigkeiten, O  N  N Ausgabeabhängigkeiten, level : D  A  O  () ist Beschriftung der Kanten, wobei k  level(s,t) bedeutet: k = 0: schleifenunabhängige Abhängigkeit, k > 0: schleifengetragene Abhängigkeit mit Level k. Beispiel (von Folie 107): s for j = 1 to 10 step 1 do for i = 1 to 7 step 1 do (s) a[j][i] = b[i][j] * 2; (t) c[i][j] = a[j][8-i] + 4; od {0,2}D {2}A t a[j][i] = b[i][j] * 2; N = {s,t} D = {(s,t)} A = {(t,s)} O =  level((s,t)) = {0,2} level((t,s)) = {2} (=,<) (=,=) (=,<) c[i][j] = a[j][8-i] + 4;

112 Zulässigkeit einer Umordnung
Eine Umordnung der Anweisungen im Quellprogramm kann die Ausführungsreihenfolge der Anweisungsinstanzen eines Schleifennests ändern. Es werden aber nach der Umordnung bei jeder möglichen Programmausführung nur genau die Anweisungsinstanzen ausgeführt, die auch in dem nicht umgeordneten Programm ausgeführt worden sind. Eine Umordnung erhält eine Abhängigkeit, falls nach der Umordnung die Quelle der Abhängigkeit noch vor der Senke ausgeführt wird. Jede Umordnung eines Programms, die jede Abhängigkeit erhält, erhält auch die Bedeutung des Programms. Eine solche Transformation wird zulässig genannt. Ausführungsreihenfolge der Anweisungsinstanzen für |I| = k: ... ... ... ... s11 sn1 s12 sn2 s1k snk s1 ... sn Ausführungsreihenfolge der Anweisungsinstanzen nach Umordnung: ... ... ... ... Zulässig! Umordnung s11 s12 s1k s21 s2k sn1 snk s1 ... Umordnung der Anweisungsinstanzen für |I| = k: sn ... ... ... ... Unzulässig! sn1 snk s21 s2k s11 s1k

113 Schleifentransformationen
Eine Umordnung, die nur die Ausführungsreihenfolge der Iterationen der Level-k-Schleife ändert, ist zulässig, falls die Level-k-Schleife keine Abhängigkeiten trägt. Folgerungen: Iterationen können auf verschiedenen Prozessoren ohne Synchronisation verteilt werden. Anweisungen verschiedener Iterationen können parallel ausgeführt werden. 1 2 n Iterationen der Level-(k+1)-Schleife Iterationen der Level-k-Schleife Iterationen der Level-(k-1)-Schleife

114 Testen auf Abhängigkeiten
Grundlagen Testen auf Abhängigkeiten

115 Voraussetzung zur Allokation von Feldern
Betrachtung von Feldzugriffen auf n-dimensionale Felder im innersten Schleifenkörper mit n > 0. Dabei gilt: Die Speicherbereiche verschieden benannter Felder sind disjunkt (gilt nicht zwingend für C/C++): int A[100][100]; int B[100][100]; int A[100]; int* B = A; for(i1 = L1; i1 < U1; i1++) for(i2 = L2; i2 < U2; i2++) A[i1][i2] = B[i2][i1]; for(i1 = L1; i1 < U1; i1++) for(i2 = L2; i2 < U2; i2++) A[i1+i2] = B[i2];

116 Voraussetzung für Ausdrücke zur Indizierung
Indizierungen sind affine Ausdrücke in den Indexvariablen: Für eine Schleife der Form for i1 = L1 to U1 step 1 do for i2 = L2 to U2 step 1 do ... for id = Ld to Ud step 1 do Schleifenfreier Schleifenkörper; sind Ausdrücke der Form a0 + a1*i1 + a2*i ad*id affin (lineare Funktion), wobei a0,...,ad Konstanten sind oder invariant in allen d Schleifen. Zum Beispiel nicht zulässig: nicht-lineare Funktionen in den Indexvariablen (es müsste eine allgemeine diophantische Gleichung gelöst werden; unentscheidbar). Indizierungen, die keine Funktion in den Indexvariablen sind; z.B.: a[b[i]].

117 Prinzip des Testens von Abhängigkeiten zwischen Feldzugriffen (Beispiel)
Abhängigkeiten zwischen Anweisungen eines Schleifenkörpers in verschiedenen Iterationen: Ein Schreibzugriff auf eine skalare Variable x erzeugt eine Abhängigkeit zu allen Schreib-/Lesezugriffen auf x in der nächsten Schleifeniteration. Ein Schreibzugriff auf ein Feld erzeugt eine Abhängigkeit zu einem Feldzugriff in einer (möglicherweise folgenden) Iterationen nur dann, wenn dort dasselbe Element indiziert wird. Beispiel: for(i = 0; i < 20; i++) A[2*i] = A[3*i+5] Offensichtlich erfolgt ein Zugriff auf dasselbe Element genau dann, wenn ein i1   und i2   existiert, so dass 2  i1 = 3  i2+5 und 0  i1  19 und 0  i2  19. Finden dieser Belegungen für i1 und i2 entspricht dem Lösen einer linearen diophantischen Gleichung.

118 Prinzip des Testens von Abhängigkeiten zwischen Feldzugriffen
Gegeben sind zwei Anweisungen mit Zugriffen auf Elemente desselben Feldes. Für jedes Paar von Feldzugriffen von denen mindestens ein Zugriff schreibend ist, wird ein Gleichungssystem aus den Ausdrücken aufgestellt, die zur Indizierung verwendet werden. Versuche zu beweisen, dass keine ganzzahlige Lösung des Gleichungssystems innerhalb der Schleifengrenzen existiert; d.h. Für alle u, v  I ist u auf der linken und v auf der rechten Seite eingesetzt, keine Lösung: Gelingt der Beweis, können Datenabhängigkeiten zwischen den Anweisungen des Anweisungspaares ausgeschlossen werden. Kann bewiesen werden, dass eine ganzzahlige Lösung innerhalb der Schleifengrenzen existiert, dann gibt es eine Datenabhängigkeit. Kann weder der Beweis noch der Gegenbeweis erbracht werden, dann wird die konservative Annahme getroffen, dass eine Abhängigkeit existiert.

119 Klassifizierung von Indizierungspaaren
Betrachtung von Paaren von Feldzugriffen der Form (A[e1]...[en], A[e'1]...[e'n]) mit den Indizierungsausdrücken ei und e'i für 1  i  n. ei und e'i werden Indizierungspaar genannt. IV(e) = {i | i ist Indexvariable, die in e vorkommt} Indexpaar (e, e') heißt: ZIV (zero index variable), falls IV(e)  IV(e') = , SIV (single index variable), falls |IV(e)  IV(e')| = 1, MIV (multiple index variable), falls |IV(e)  IV(e')| > 1. Bilden des Gleichungssystem durch: ei = e'i für 1  i  n, wobei alle Indexvariablen in e'1,...,e'n werden durch ein Apostroph so umbenannt werden, dass sich ihre Namen von denen in e1,...,en unterscheiden.

120 Gruppierung von Indexpaaren
Es seien (ei, e'i) mit 1  i  n die Indizierungspaare eines Zugriffs auf ein n-dimensionales Feld. Konstruktion von Gruppen durch: Erzeuge neue Gruppe G mit einem beliebigen Paar (a,b), das noch keiner Gruppe zugeordnet wurde Solange es weitere Paare (a,b) mit (IV(a)  IV(b))  (IV(c)  IV(d))   für (c,d)  G gibt, nimm (a,b) in G auf Wiederhole die ersten zwei Schritte, bis alle Paare einer Gruppe angehören. Ein Paar (a, b) heißt separierbar, falls es einziges Element in seiner Gruppe ist; ansonsten heißen die Paare in der Gruppe gekoppelt. Beispiel: (A[i][j][j],A[i][j][k]) (i,i) ist separierbar, (j,j) und (j,k) sind gekoppelt. Abhängigkeitstest kann separat für jede Gruppe durchgeführt werden. Falls für eine Gruppe gezeigt werden kann, dass das zugehörige Gleichungssystem keine ganzzahlige Lösung besitzt, dann gibt es keine Datenabhängigkeit zwischen dem Indexpaar.

121 Testen von Abhängigkeiten
Für jedes Paar von Feldzugriffen, die eine Abhängigkeit verursachen können: Zerlege die Indizierungen in minimal gekoppelte Gruppen. Klassifiziere die Gruppen nach: ZIV, SIV, MIV. Für jede separate Gruppe: Wende einen geeigneten Abhängigkeitstest (ZIV, SIV, MIV) an, um entweder die Unabhängigkeit zu beweisen oder einen Richtungsvektor für die Indexvariablen in der Gruppe zu erzeugen. Für gekoppelte Gruppe: Wende einen Test für mehrfache Indizierungen an, um Richtungsvektoren zu erzeugen. Falls einer der Tests eine Unabhängigkeit nachweist, sind keine weiteren Tests erforderlich. Kombiniere alle Richtungsvektoren zu einer Menge von Richtungsvektoren für das getestete Paar von Feldzugriffen.

122 Beispiele Minimal gekoppelte Gruppen (jeweils separat):
for j = 1 to 10 step 1 do for i = 1 to 10 step 1 do a[i+1][j] = a[i][j] * 2; od Minimal gekoppelte Gruppen (jeweils separat): ([i+1],[i]) und ([j],[j]) SIV SIV Paar von Feldzugriffen SIV-Test ergibt: ([i+1],[i]) SIV-Test ergibt: ([j],[j]) Quelle Senke Quelle Senke i+1 = i' ergibt Distanz 1; also Richtung: < j = j' ergibt Distanz 0; also Richtung: = Richtungsvektoren: {(=,<)} for i = 1 to 10 step 1 do for j = 1 to 10 step 1 do a[i+1][5] = a[i][n] * 2; od Minimal gekoppelte Gruppen (jeweils separat): ([i+1],[i]) und ([5],[n]) SIV ZIV Paar von Feldzugriffen SIV-Test für ([i+1],[i]) ergibt Richtung < ZIV-Test für ([5],[n]) ergibt: Abhängigkeit für n=5 möglich; also: Alle Richtungen möglich, Richtungsvektoren: {(<,<), (<,=), (<,>)}

123 ZIV-Test für separierbare Paare
Wenn (e,e') ZIV, dann sind beide Ausdrücke in allen Schleifendurchläufen konstant. Falls symbolische Auswertung von e – e' den Wert 0 ergibt, dann besteht eine Abhängigkeit. Aber: Keine Rückschlüsse auf Richtungsvektor einer Indexvariablen möglich. Falls symbolische Auswertung von e – e' einen konstanten Wert verschieden von 0 ergibt, dann besteht keine Abhängigkeit. Ansonsten konnte weder bewiesen noch widerlegt werden, ob eine Abhängigkeit existiert und es muss eine konservative Annahme getroffen werden.

124 Test für starke separierbare SIV Paare
Indexpaar (e,e') ist stark SIV, falls e = a  i + c und e' = a  i + c'. Es ergibt sich durch Umbenennen und Gleichsetzen das Gleichungssystem: a  i + c = a  i' + c' d = i – i' = (c' – c)/a d ist die Anzahl der Schleifeniterationen zwischen zwei Zugriffen auf dasselbe Feldelement (relevant für Modulo-Scheduling). Eine Abhängigkeit existiert genau dann, wenn 0 < |d|  U – L und ganzzahlig ist oder d = 0 und e und e' zu verschiedenen Anweisungen gehören. Falls |d| > 0 ist Richtung für i: < Falls d > 0 ist e Quelle Falls d < 0 ist e' Quelle Falls d = 0 und e und e' zu verschiedenen Anweisungen s und s' gehören, dann ist s genau dann Quelle, wenn s vor s' steht. e, e' ai+c ai'+c' Zugriff auf dasselbe Element d i, i'

125 Test für separierbare weak-zero-SIV Paare
Indexpaar (e,e') ist weak-zero-SIV, falls: es die Form e = 0  i + c und e' = a'  i + c' oder (1) e = a  i + c und e' = 0  i + c' hat. (2) Es ergibt sich o.B.d.A. für (1): i = (c – c')/a' Eine Abhängigkeit existiert genau dann, wenn i ist ganzzahlig und L  i  U. e' a'i+c' c i

126 Test für separierbare weak-crossing-SIV Paare
Indexpaar (e,e') ist weak-crossing-SIV, falls: es die Form e = a  i + c und e' = a'  i + c' hat und a' = -a gilt. Schnittpunkt befindet sich bei i = i'. Wegen a' = a ergibt sich: i = (c' – c)/2a Eine Abhängigkeit existiert genau dann, wenn i ist ganzzahlig oder hat als Nachkommawert eine 0.5 und L  i  U. ai+c e' e = e' e i, i' L i = i' U -ai'+c'

127 Test für separierbare weak-SIV
Indexpaar (e,e') ist weak-SIV, falls es die Form e = a  i + c und e' = a'  i + c' hat. Es ergibt sich durch Umbenennen und Gleichsetzen: a  i + c = a'  i' + c' Eine Abhängigkeit mit Richtung D  {<,=,>} existiert genau dann, wenn ganzzahlige i und i' existieren mit a  i + c = a'  i' + c' und L  i, i'  U und i D i'. e ist Quelle der Abhängigkeit, gdw. D = < oder D = = und e vor e' im Quelltext steht. ai+c a'i'+c' e,e' i, i' L U

128 Test für separierbare MIV Paare
Indexpaar (e,e') ist MIV, falls es die Form e = a1i adid + c und e' = e = a'1i a'did + c' hat. Es ergibt sich durch Gleichsetzen: 0 = a1i adid - a'1i' a'di'd + (c - c') bzw. (a1i1 - a'1i'1) (adid - a'di'd) = c' - c Eine Abhängigkeit für den Richtungsvektor (D1,...,Dn) existiert genau dann, wenn ganzzahlige ik und ik' für obige Gleichung mit 1  k  d existieren und Lk  ik, ik'  Uk und ik Dk ik'. Um zu testen, ob eine Abhängigkeit für einen bestimmten Richtungsvektor existiert, aufzählen aller möglichen Richtungsvektoren und Test für jeden Richtungsvektor ausführen.

129 Testen von Abhängigkeiten mit Richtungsvektor beim MIV-Test
Verbesserung durch schrittweise Präzisierung der Richtungsvektoren möglich. Allgemeinster Richtungsvektor: (*,*,...,*). Richtung * an Position k in D bedeutet, dass beim Testen jede der Relationen ik < ik', ik = ik' bzw. ik > ik' gelten darf. (*,*,*) (<,*,*) (=,*,*) (>,*,*) (<,<,*) (<,=,*) (<,>,*) Falls Test hier schon keine Abhängigkeit entdeckt. try((e,e'),D,k,dvlist) { if not check((e,e'),D)) then return dvlist fi if k = n then return dvlist  {D} for each d  {<, =, >} do Dk := d dvlist := try((e,e'),D,k+1,dvlist) od }

130 GCD-Test (a1i1 - a'1i'1) (adid - a'di'd) = c' - c ist eine lineare diophantische Gleichung; vereinfacht: a1  i1 + a2  i an  in = c. GCD-Test liefert eine notwendige und hinreichende Bedingung zur Existenz einer ganzzahligen Lösung einer linearen diophantischen Gleichung. Damit: Falls GCD-Test erfolgreich, dann existiert eine ganzzahlige Lösung, diese muss aber nicht die untere/obere Schranke Lk/Uk einhalten. Falls GCD-Test fehlschlägt, dann existiert keine Abhängigkeit zwischen den Feldzugriffen aus denen das Indexpaar hervorgegangen ist. Die lineare diophantische Gleichung a1  i1 + a2  i an  in = c besitzt genau dann eine ganzzahlige Lösung in i1,...,in, wenn ggt(|a1|,...,|an|) c teilt. Dabei ist Beispiel: Paar von Feldzugriffen: (A[4*i],A[5-2*i]) Gleichungssystem: 2i' + 4i = 5 ggt(2,4) = 2, wobei 2 nicht 5 teilt. Problem: Oft ist ggt(|a1|,...,|an|) = 1.

131 Test mit Banerjee Ungleichung
Wir betrachten wieder Indexpaare (e,e') der Form f(i1,...,id) = e = a1i adid + c und g(i'1,...,i'd) = e'= a'1i' a'di'd + c'. Abhängigkeit für Richtungsvektor (D1,...,Dn) existiert genau dann, wenn ganzzahlige Werte für i1,...,id,i'1,...,i'd mit Lik  ik  Uik und Li'k  i'k  Ui'k und ik Dk ik' für 1  k  d existieren, so dass: h(i1,...,id,i'1,...,i'd) := f(i1,...,id) - g(i'1,...,i'd) = (a1i1 - a'1i'1) (adid - a'di'd)+ (c - c') = 0 Ganzzahlige Lösung existiert nur dann, wenn es eine Lösung in R gibt. Eine Lösung in R existiert für die stetige Funktion h genau dann, wenn: min{h(i1,...,id,i'1,...,i'd)}  0 und 0  max{h(i1,...,id,i'1,...,i'd)}, wobei Lik  ik  Uik und Li'k  i'k  Ui'k und ik Dk ik' für 1  k  d. Finden des Minimums/Maximums durch separate Minimierung (MINk(Dk)) bzw. Maximierung (MAXk(Dk)) der Terme (akik - a'ki'k) unter Beachtung der Richtung Dk. Bedingung für eine Abhängigkeit:

132 Minimierung/Maximierung mit Richtung =
Minimum MINk(=) von akik - a'ki'k ergibt sich durch: Maximum MAXk(=) von akik - a'ki'k ergibt sich durch:

133 Minimierung/Maximierung mit Richtung *
Minimum MINk(*) von akik - a'ki'k ergibt sich durch: Maximum MAXk(*) von akik - a'ki'k ergibt sich durch:

134 Minimierung mit Richtung <
Minimum MINk(<) von akik - a'ki'k ergibt sich durch: MAXk(<), MINk(>) MAXk(>) von akik - a'ki'k ergeben sich entsprechend.

135 Test für gekoppelte Paare
Jedes Paar (e1,e'1),...,(en,e'n) in einer Gruppe kann zunächst separat mit einem der vorgestellten Tests getestet werden. Falls bereits für ein Paar gezeigt werden kann, dass das zugehörige Gleichungssystem keine ganzzahlige Lösung in den gegebenen Grenzen besitzt, dann besitzt auch das Gleichungssystem zur Gruppe keine solche Lösung. Es existieren also keine Datenabhängigkeiten zwischen den zugehörigen Feldzugriffen. Falls aber Lösungen existieren, dann gemeinsame Betrachtung dieser Lösungen durch Einsetzen des Gleichungssystems zu (ei,e'i) in das Gleichungssystem zu (ej,e'j), falls (IV(ei,)  IV(ej,))  (IV(e'i)  IV(e'j))  .

136 Beispiel anhand des GCD-Tests
Feldzugriffe im Programm: A[x+z][3*x+z] und A[2*x][99-2*x] Notwendige Bedingungen, damit dasselbe Element referenziert wird: (I) x – 2x' + z = 0 (II) 3x + 2x' + z = 99 gcd(1,|-2|,1) = 1 und 1 teilt 0. gcd(3,2,1) = 1 und 1 teilt 99. Zugriff auf dasselbe Element kann nicht ausgeschlossen werden! Gemeinsame Betrachtung der zwei Feldzugriffe durch Einsetzen von (I) in (II): 2x + 4x' = 99 gcd(2,4) = 2 und 2 teilt nicht 99 Zugriff auf dasselbe Element kann ausgeschlossen werden!

137 Komplexeres Beispiel (1)
for i = 1 to 100 step 1 do x[i] = y[i] (s1) for j = 1 to 100 step 1 do b[j] = a[j][N] (s2) for k = 1 to 100 step 1 do a[j+1][k] = b[j] + c[j][k] (s3) od y[i+j] = a[j+1][N] (s4) Feldzugriffspaar Schleifennest Klassifizierung Test (b[j],b[j]) (i,j) starkes SIV Paar d = (c' – c)/a = 0; Richtungen: j = {=}, i = {*} Zusammensetzen: {(<,=),(=,=),(>,=)} Flussabhängigkeit s2 -> s3 mit {(<,=),(=,=)} Antiabhängigkeit s3 -> s2 mit {(<,=)} (a[j][N],a[j+1][k]) (i,j) starkes SIV Paar d = (c' – c)/a = 1; Richtungen: j = {<}, i = {*} weak-zero SIV N = k möglich, damit i = {*}, j = {*} mögliche Richtungen: i = *, j = < Zusammensetzen: {(<,<),(=,<),(>,<)}

138 Komplexeres Beispiel (2)
for i = 1 to 100 step 1 do x[i] = y[i] (s1) for j = 1 to 100 step 1 do b[j] = a[j][N] (s2) for k = 1 to 100 step 1 do a[j+1][k] = b[j] + c[j][k] (s3) od y[i+j] = a[j+1][N] (s4) Feldzugriffspaar Schleifennest Klassifizierung Test (a[j+1][N],a[j+1][k]) (i,j) starkes SIV Paar d = 0 Richtungen: i = {*}, j = {=} d = 0 Richtungen: i = {*}, j = {=} weak-zero SIV N = k möglich, damit i = {*}, j = {*} mögliche Richtungen: i = {*}, j = {=} Zusammensetzen: {(<,=),(=,=),(>,=)} (y[i],y[i+j]) (i) 1i + 0j = 1i' + 1j' Test für (*,*) durch: MIN1(*) = -99, MIN2(*) = -100 MAX1(*) = 99, MAX2(*) = -1 Test für (=,*) durch: MIN1(=) = 0, MIN2(*) = -100 MAX1(=) = 0, MAX2(*) = -1; ...


Herunterladen ppt "Optimierungstechniken in modernen Compilern"

Ähnliche Präsentationen


Google-Anzeigen