Grundlagen der Übersetzung und Optimierung von Programmiersprachen

Slides:



Advertisements
Ähnliche Präsentationen
Vorlesung Compilertechnik Sommersemester 2008
Advertisements

CPI Der einzelne Befehl braucht immer noch 5 Zyklen (stimmt nicht ganz, einige brauchen weniger!) Was verbessert wird, ist der Durchsatz = #Befehle /
Vorlesung Compilertechnik Sommersemester 2009 Optimierung M. Schölzel.
Schnelle Matrizenoperationen von Christian Büttner
Informatik 12 | DAES Compilerbau Wintersemester 2010 / 2011 Dr. Heiko Falk Technische Universität Dortmund Lehrstuhl Informatik 12 Entwurfsautomatisierung.
Technische Universität Dortmund
Technische Universität Dortmund
Qualitätssicherung von Software (SWQS)
Gliederung 1. Grundlagen der Bottom-Up-Syntaxanalyse
Suche in Texten (Stringsuche )
Imperative Programmierung
Software-Engineering II Eingebettete Systeme, Softwarequalität, Projektmanagement Prof. Dr. Holger Schlingloff Institut für Informatik der Humboldt.
Parser generieren Yet Another Compiler – Compiler YACC.
Java: Dynamische Datentypen
Indirekte Adressierung
Anfragesprachen – Dipl. Ing. Ulrich Borchert / FH Merseburg1/7 Datenbanken werden als Anhäufung von Werten eines Wertebereiches aufgefasst und Datenbankabfragen.
Algorithmentheorie 04 –Hashing
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
Imperative Programmierung
Agenda Motivation Formale Sprachen Compiler Compilerentwicklung
PKJ 2005/1 Stefan Dissmann Zusammenfassung Vorwoche Programm besteht aus mehreren Bestandteilen: Schlüsselwörter Sonderzeichen Bezeichner Kommentare Texte.
Grundlegende Analysen & Zwischendarstellungen
Semantische Fehler Seminar im Grundstudium WS2002/2003:
Die Skriptsprache Perl (2) Wolfgang Friebel DESY Zeuthen.
A. Zündorf, SE Group Reverse Engineering K2 1 Ziele Heute Compilerbau Nachlese Ausnutzung von Laufzeittypinformation.
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
DVG Ablaufsteuerung
Marcus Haller & René Schulze
Von der Sprache zum Programm
Programmiersprachen II Integration verschiedener Datenstrukturen
Visualisierung funktionaler Programme
Java programmieren mit JavaKara
Maschinenunabhängige Codeoptimierung
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Effiziente Algorithmen
Betrieb von Datenbanken Marco Skulschus & Marcus Wiederstein Datenmanipulation Lehrbuch, Kapitel 4.
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Effiziente Algorithmen
Efficient Alias Set Analysis Using SSA Form Proseminar Programmanalyse WS 11/12 André Hunke.
Formale Sprachen Teil 3 Klaus Becker Syntax und Semantik.
Vom Umgang mit Daten. public void myProgram() { int[] saeulenWerte = new int[world.getSizeX()]; for (int i = 0; i < saeulenWerte.length; i++) { saeulenWerte[i]
Was ist eine Funktion? Eine Zuordnung,
C-Einstieg. Agenda 1Vorbereitung 2Aufbau eines Programms 2.1Header 2.2 Methoden 2.3Main 3Datentypen & Variablen 4Operatoren(+, -, *, /) 5Logik 5.1IF 5.2Switch.
Grundkonzepte des Programmierens (mit ActionScript)
Unterprogramme in JAVA
Purga - Scriptengine Ein Einblick.
Automaten, formale Sprachen und Berechenbarkeit II SoSe 2004 Prof. W. Brauer Teil 1: Wiederholung (Vor allem Folien von Priv.-Doz. Dr. Kindler vom WS 2001/02.
Die Idee hinter Copying Garbage Collection (1) Aufteilung des Heaps in zwei Teile: To-Space und From-Space Nutzung eines Teiles durch das Programm Ist.
Agenda für heute, 7. April, 2005 Bedingte ProgrammausführungBedingte Programmausführung Algorithmische Grundlagen Vergleichsoperatoren, Wahrheitswerte.
Systemsoftware und Betriebssysteme
PHP: Operatoren und Kontrollstrukturen
Variablen. var meineZahl:Number = 7; meineZahl 7 Name TypWert = Zuweisung von Variablen.
Hochschule Fulda – FB ET Sommersemester 2014
Parallelisierung für Multiprozessor-Maschinen
Code-Optimierung Philipp Bergener Seminar „Übersetzung künstlicher Sprachen“
SFZ FN Sj. 13/14 Python 3 Rekursion Inf K1/2 Sj 13/14
Agenda Motivation und Einordnung Syntaxgerichtete Übersetzung
Mensch – Maschine - Kommunikation
Inhalt Einordnung und Funktion der lexikalische Analyse Grundlagen
Semantische Analyse und attributierte Grammatiken
2 Grundlagen In diesem Abschnitt werden die Grundbegriffe und Methoden der Theorie der formalen Sprachen und der Automaten wiederholt, soweit diese ben.
Modellbasierte Software- Entwicklung eingebetteter Systeme Prof. Dr. Holger Schlingloff Institut für Informatik der Humboldt Universität und Fraunhofer.
Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar?
Datenflussanalyse - Klassisch Seminar “Progammanalyse” (SS 2009) Referent: Lorenz Schauer Vortrag:
 Sortigkeit oder Arität
Unterschiedliche Kontrollstrukturen
 Präsentation transkript:

Grundlagen der Übersetzung und Optimierung von Programmiersprachen Seminar: Techniken der Code-Optimierung für moderne Rechnerarchitekturen Grundlagen der Übersetzung und Optimierung von Programmiersprachen Betreuer: Martin Schulz Referent: Markus Ibba

Übersicht Grundlagen der Übersetzung eines Programmes Konzepte zur Optimierung Ausblick Die Grundlagen bieten eine Einsicht in die allgemeine Funktionsweise eines Compilers. Hierbei werden noch keine Optimierungstechniken berücksichtigt. Die Konzepte der Optimierung erklären den Ablauf eines optimierenden Compilers. Anhand eines Beispiels wird eine einfache Codetransformation durchgeführt. Schließlich werden einige gängige Optimierungen besprochen.

Grundlagen der Übersetzung eines Programmes Übersicht eines Übersetzungsvorganges Analyse des Quellprogramms Lexikalische Analyse Syntaxanalyse Semantische Analyse Fehlerbehandlung In der Übersicht sehen wir uns einen Graphen an, welcher einen Überblick über die einzelnen Phasen des Compilers gibt Die Analysearten werden im Folgenden näher besprochen. Neben der Symboltabelle werden wir auch kurz auf Fehler eingehen, welche während der Compilierung auftreten können. Die Fehlerbehandlung zeigt Möglichkeiten, auf gefundene Fehler während der Analyse zu reagieren.

Übersicht eines Übersetzungsvorganges Die Symboltabelle ist die wichtigste Datenstruktur die der Compiler verwendet. Sie wird während der lexikalischen Analyse aufgebaut, und steht für die restliche Compiler-Phasen zur Verfügung. Die Schnittstelle zum Betriebssystem beinhaltet alle IO-Operationen, welche während der Compilierung durchgeführt werden.

Analyse des Quellprogramms Lexikalische Analyse (Scanner) (=Lineare Analyse) Lesen des Zeichenstromes Aufteilung in Symbole (tokens) Aufbau der Symboltabelle Während der lexikalischen Analyse wird der Zeichenstrom (Quellprogramm) von links nach rechts gelesen und in Symbole (tokens) aufgeteilt. Die Leerzeichen werden dabei im allgemeinen entfernt. Hier erfolgt der Aufbau der Symboltabelle.

Lexikalische Analyse (Scanner) Aufbau der Symboltabelle position = initial + rate * 100 EOS steht hier für End Of String. Die einzelnen Bezeichner werden auch Lexeme genannt. In die Symboltabelle wird für jedes Symbol ein Pointer auf das entsprechende Lexem eingetragen. Dies ist eine stark vereinfachte Darstellung dieser Tabelle. Für die effiziente interne Verwaltung werden üblicherweise Hash-Funktionen verwendet. Gespeichert werden hier unter anderem verschiedene Attribute über das Symbol, wie der Name, den Typ (z.B. Integer) und die Größe in Bytes. Lexikalische Fehler treten z.B.: bei falsch geschriebenen Schlüsselwörtern und Operatoren auf.

Analyse des Quellprogramms Syntaxanalyse (Parser) (= hierarchische Analyse) Beschreibung der Syntax der Programmiersprache (kontextfreie Grammatiken, BNF) Zusammenfassen der Symbole zu grammatikalischen Sätzen Über kontextfreie Grammatiken kann relativ einfach die hierarchische Struktur einer Programmiersprache beschrieben werden. Auf die Grammatiken und die BNF wird hier nicht näher eingegangen, da diese Bestandteil des Grundstudiums sind und für dieses Seminar als bekannt vorausgesetzt werden. Der Compiler faßt in dieser Phase die Symbole zu grammatikalischen Sätzen zusammen, welche durch einen Parse-Baum dargestellt werden. Hierbei werden die Symbole der Reihe nach (von links nach rechts) gelesen. In den meisten Fällen werden die Top-Down oder Bottom-Up Methoden verwendet, d.h. der Compiler baut den Parse-Baum entweder von der Wurzel nach unten auf, oder arbeitet sich von den Blättern zur Wurzel hoch. Syntaxfehler können zum Beispiel falsch geklammerte arithmetische Ausdrücke sein.

Syntaxanalyse (Parser) Der Parse-Baum position = initial + rate * 100 Dies ist ein Beispiel eines aufgebauten Parse-Baumes. Da die Multiplikation vor der Addition ausgeführt wird, bildet rate * 100 hier eine Einheit.

Analyse des Quellprogramms Semantische Analyse Konsistenz zwischen Deklaration und Definition (Methoden, Variablen) Statische Überprüfungen Typüberprüfung Überprüfung des Kontrollflusses Überprüfung auf Eindeutigkeit Die semantische Analyse benutzt die hierarchische Struktur, welche in der vorhergehenden Phase erzeugt wurde, um für die verschiedenen Anweisungen und Ausdrücke die Operatoren und Operanden zu bestimmen. Konsistenz zwischen Deklarationen und Definitionen von Variablen und Methoden bedeutet, daß es für jeden auftretenden Bezeichner eine explizite Deklaration geben muß, daß diese Deklaration korrekt ist und daß es z.B.: keine Doppeldeklarationen geben darf. Die Typüberprüfung ermittelt, ob Operanden eines Operators zulässig sind. Ist dies nicht der Fall (z.B.: Wenn eine Float-Zahl zur Indizierung eines Arrays verwendet wird), so meldet der Compiler einen Fehler. Überprüfung des Kontrollflusses: Anweisungen, durch welche der Kontrollfluß eines Konstrukts verlassen wird, wie z.B: die Break-Anweisung, durch welche ein While-Konstrukt verlassen werden kann, müssen sich innerhalb eines solchen Konstruktes befinden. Existiert eine solche Anweisung nicht, tritt ein Fehler auf. (in C) Überprüfung auf Eindeutigkeit: z.B: dürfen Elemente eines Aufzählungstypen nicht identisch sein. (in Pascal, in C)

Fehlerbehandlung Recovery-Strategien Panische Recovery Konstrukt-orientierte Recovery Fehlerproduktion Globale Korrektur Alle Fehler, die während der Analysephase des Compilers auftreten, müssen in irgendeiner Art und Weise behandelt und dem Benutzer mitgeteilt werden. Natürlich ist es wünschenswert, daß der Compiliervorgang nicht schon nach einem gefundenen Fehler abbricht. Hierfür gibt es verschiedene Recovery-Strategien: Panische Recovery: Hierbei werden bei einem Fehler alle weiteren Symbole überlesen, bis ein sogenanntes synchronisierendes Symbol auftritt (In C z.B. Der Strichpunkt). Konstrukt-orientierte Recovery: Hier wird versucht lokale Fehler zu korrigieren. Typische Korrekturen wären die Ersetzung eines Kommas durch ein Semikolon oder Einfügen von fehlenden Semikolon. Hierbei besteht die Gefahr durch zu häufiges Einfügen in eine Endlos-Schleife zu geraten. Fehlerproduktion: Bei häufig auftretenden Fehlern, welche schon bekannt sind, kann in die Grammatik eine Produktion für diese Fehler einbaut werden, welche dann während des Parsens zur Anwendung kommt, und das entsprechende Konstrukt als falsch erkennt. Globale Korrektur: Hier werden Algorithmen verwendet, welche versuchen einen Eingabestring global mit minimalen Änderungen zu korrigieren. Dies ist momentan nur theoretisch, da solche Algorithmen zu kostspielig sind.

Konzepte zur Optimierung -> Ziel: Laufzeit und/oder Größe eines Programmes reduzieren Übersicht eines Übersetzungsvorganges eines optimierenden Compilers Analyse Codetransformation Optimierungstechniken Die Übersicht bietet einen Graphen für den Übersetzungsvorgang eines optimierenden Compilers. Wir werden kurz auf erweiterte Analysen und schließlich auf die Code-Transformation selber eingehen.

Idee einer Optimierung Anwendungsprogrammierer braucht kein umfangreiches Wissen über die Maschinenarchitektur auf der er programmiert. Hardwarehersteller brauchen nur noch Schnittstelle für den Compiler selber zu designen. Dies sind zwei grundsätzliche Ideen, warum Optimierungen durchgeführt werden sollen. Diese sind allerdings so noch nicht realisiert und stellen somit noch unerreichte Ziele dar.

Übersicht eines Übersetzungsvorganges Der erste Kasten umfaßt die Analysearten, welche auf den vorhergehenden Folien besprochen wurden. Dies bezeichnen wir als ‚Front-End‘ eines optimierenden Compilers. In der eigentlichen Analyse des optimierenden Compilers wird der Kontrollfluß des Programmes untersucht, und dabei der sogenannte ‚Control Flow Graph‘ (CFG) aufgebaut, welchen wir uns an einem Beispiel-Programm kurz anschauen werden. Die Datenfluß- und Abhängigkeitsanalyse wird hier nur kurz erwähnt. Das Resultat ist der ‚Program Dependence Graph‘ (PDG) und die ‚Static Single-Assignment‘ (SSA) Form. Der Kasten Code-Transformation repräsentiert die Optimierungen, welche die Datenflußanalyse benötigen. ‚Back-End‘ bezeichnet die Codeerzeugung bis hin zum ausführbaren Programm.

‚Control dependence‘ 1: if (3 == a) 2: b = 10; Abhängigkeit zwischen Ausdruck 1 und 2. -> Generierung des ‚Control Flow Graph‘ (CFG). Der Compiler benötigt ein umfassendes Verständnis des zu optimierenden Programmes, um die passenden Codestücke und dafür geeignete Optimierungen zu finden. Hierzu ist es nötig den Kontrollfluß des Programmes, das heißt jede Kontrollabhängigkeit zwischen den Codestücken, zu erkennen. In unserem Beispiel besteht eine Abhängigkeit zwischen Zeile 1 und 2, da für den Fall daß 3 == a der Kontrollfluß an Zeile 2 (b = 10) übergeben wird. Der CFG ist ein Graph der die Kontrollabhängigkeiten eines Programmes darstellt.

CFG - Control Flow Graph Beispiel eines CFG. Die Knoten repräsentieren Basis-Blöcke. Ein Basis-Block ist ein Codestück, in das der Programmablauf nur an einer festen Stelle (Beginn des Blockes) eintreten und das er nur an einer festen Position (Ende des Blockes) verlassen kann. Jeder Knoten hat eine Kante zu jedem anderen, zu dem er die Kontrolle übergeben kann.

‚Data dependence‘ Die Datenflußanalyse wird im 3. Vortrag behandelt a = c * 10; d = 2 * a + c; ‚flow dependence‘ e = f * 4 + g; g = 2 * h; ‚anti dependence‘ a = b * c; a = d + e; ‚output dependence‘ Die Datenflußanalyse wird im 3. Vortrag behandelt Hier werden nur kurz drei Arten der Datenabhängigkeit vorgestellt. 1) ‚flow dependence‘, wird auch ‚true dependence‘ genannt. In diesem Beispiel besteht eine Abhängigkeit zwischen den beiden Zeilen, da in a erst der Wert geschrieben wird, bevor er für die Variable d gelesen wird. 2) ‚anti dependence‘: Hier wird g für die Variable e zuerst gelesen, und danach überschrieben. 3) ‚output dependence‘: Beide Anweisungen schreiben in dieselbe Variable. Diese kurze Vorstellung soll hier reichen, da die Datenflußanalyse und die Abhängigkeiten im dritten Vortrag genauer besprochen werden. Für die meisten Optimierungen ist die Datenflußanalyse dringend notwendig, damit der Compiler Kenntnis erlangt, inwieweit sich die Werte der Variablen während des Programmablaufes an welcher Stelle ändern.

Codetransformation -> Bestandteil jeder Optimierung Allgemeiner Ablauf einer Transformation Beispiel einer Transformation Jede Optimierung nimmt eine bestimmte Art Transformation an einem Codestück vor. Bevor wir verschiedene Optimierungstechniken kennenlernen werden, wenden wir uns kurz dem Ablauf einer Transformation und einem kleinen Beispiel zu.

Allgemeiner Ablauf einer Transformation Programmstück finden, welches sich für die Optimierung eignet Sicherstellen, daß die Transformation die Semantik des Programmes nicht ändert Programm transformieren

Beispiel einer Transformation Dies ist ein relativ einfaches Programmbeispiel. Es interessiert momentan nur, welche Transformation augenscheinlich durchgeführt werden könnte, ohne daß wir genau wissen welche Optimierung dies wäre. Bei der Besprechung der Optimierungen wird nochmal auf dieses Beispiel verwiesen.

Beispiel einer Transformation Der schleifen-invariante Code b[k] + 2.0 wird vor die Schleife gestellt, damit er nur einmal ausgeführt werden muß. Obwohl diese Transformation augenscheinlich korrekt ist, und die Semantik des Programmes anscheinend nicht verändert, ist sie fehlerhaft und dürfte nicht durchgeführt werden.

Beispiel einer Transformation Fehler 1: Überlauf Eingabe: b[k] = max. Floatzahl - 1.0; a[1] = -2.0; Überlauf bei C = b[k] + 2.0; Fehler 2: Ergebnisabweichung Unterschiede im Ergebnis durch die Vertauschung der Additionsreihenfolge Bei einer Eingabe von b[k] = maximale Floatzahl - 1.0 und a[1] = - 2.0 kommt es bei dem transformierten Programm auf jeden Fall zu einem Überlauf. Dieser würde zwar beim Originalprogramm auch auftreten, aber an einer anderen Stelle. Würde sich eine printf-Anweisung zwischen der Zuweisung und der Benutzung von C befinden, würde diese Transformation die Ausgabe des Programmes ändern. Da die Float-Zahlen nur Näherungen für die Real-Zahlen sind, kann die Vertauschung der Additionsreihenfolge eine Ergebnisänderung zur Folge haben.

Beispiel einer Transformation Fehler 3: Zugriffsfehler Eingabe: k = m + 1; n = 0; Referenz zu b[k] nicht definiert. Tritt im Original durch n = 0 nicht auf. Bei einer Eingabe von k = m + 1 würde man mit b[m+1] auf eine Element außerhalb der Grenzen zugreifen. Ist nun n = 0, so würde das Originalprogramm nicht in die Schleife kommen, und somit keinen Zugriffsfehler melden. Beim transformierten Programm steht der Zugriff vor der Schleife und wird damit auch bei n = 0 durchgeführt.

Optimierungstechniken Partial Evaluation (Early Optimizations) -> nur bei einigen Datenflußanalyse notwendig Redundancy Elimination -> Entfernung redundanter Codestücke -> Daten- und Kontrollflußanalyse notwendig Hier lernen wir einige Optimierungstechniken kennen, welche sowohl Größe als auch Laufzeit eines Programmstückes beeinflussen können. Es ist allerdings möglich, daß sich diese beiden Faktoren nicht zum Positiven verändern. In seltenen Fällen kann eine Optimierung (meist mehrere) auch die Größe und/oder Laufzeit negativ beeinflussen. Die Standardoptimierungstechniken, welche im Folgenden erläutert werden, können in zwei Gruppen aufgeteilt werden. Die Gruppe ‚Partial Evaluation‘ stellt Optimierungen vor, bei denen Berechnungen verschiedener Ausdrücke schon zur Kompilierungszeit durchgeführt werden. Einige davon brauchen keine Datenflußanalyse. ‚Redundancy Elimination‘ umfaßt Optimierungen, welche redundante Codestücke identifizieren und diese entfernen. Hierzu sind Daten- und Kontrollflußanalyse notwendig.

Partial Evaluation Constant Folding Algebraic Simplification Reassociation Constant Propagation Copy Propagation Statement Substitution Induction Variable Elimination Function Cloning

Constant Folding Konstante Ausdrücke werden zur Übersetzungszeit ausgerechnet. X = 3 * 2; -> X = 6; -> Es ist keine Datenflußanalyse notwendig Bei dieser Optimierung wird festgestellt, ob alle Operanden eines Ausdrucks konstant sind, um dann durch ihren Wert ersetzt werden zu können. Für Boolsche Werte ist diese Optimierung immer durchführbar. Handelt es sich bei den Operanden um Integer-Zahlen, so kann sie fast immer ausgeführt werden. Ausnahmen bilden hier die Fälle, welche beim Programmablauf Fehlermeldungen erzwingen, wie z.B. Division durch Null. In diesem Fall kann der Compiler eine Warnung ausgeben. Probleme können bei Float-Zahlen auftreten. Es muß sichergestellt sein, daß die Floating-Point Arithmetik des Compilers mit der des Prozessors übereinstimmt. Ansonsten kann eine Berechnung zur Kompilierzeit ein anderes Ergebnis liefern als zur Laufzeit des Programmes. Diese Optimierung ist zwar nicht von der Datenflußanalyse abhängig, ist aber viel effizienter, wenn sie in Kombination mit der Optimierung ‚Constant Propagation‘ durchgeführt wird.

Algebraic Simplification Vereinfachung algebraischer Ausdrücke X = (Y + 1) / N; wobei N = 1; zu X = (Y + 1); -> Keine Datenflußanalyse notwendig Mit Hilfe dieser Optimierung können arithmetische Ausdrücke durch Anwendung algebraischer Regeln vereinfacht werden. Diese Optimierung ist unabhängig von der Datenflußanalyse.

Reassociation Umgruppierung von Additionen und Multiplikationen in einem Ausdruck Kann die Anzahl der ‚Common Subexpressions‘ vergrößern. Keine Datenflußanalyse notwendig Mit Hilfe von Assoziativ-, Kommutativ-, und Distributivgesetzen werden Ausdrücke, welche aus verschiedenen Summen und Multiplikationen bestehen umgeschrieben. Diese Optimierung wird meist in Zusammenhang mit der Vereinfachung von Index-Ausdrücken verwendet. Dies werden wir an einem Beispiel bei ‚Induction Variable Elimination‘ sehen. Diese Technik erhöht meist die ‚Common Subexpressions‘ im Code.

Constant Propagation Konstanten im Programm werden durch ihren eigentlichen Wert ersetzt. Hierzu wird das Ergebnis der Datenflußanalyse benötigt. Diese Optimierung erfordert die Datenflußanalyse. Hierbei werden Konstanten durch das gesamte Programm ‚verfolgt‘ und dann durch ihren eigentlichen Wert ersetzt. Dies ist eine sehr wichtige Optimierung, und wird von nahezu jedem Compiler durchgeführt. Wie schon erwähnt, ist es sinnvoll ‚Constant Folding‘ in Zusammenhang mit dieser Optimierung anzuwenden. Interessant wird sie vor allem bei Schleifen, dessen Grenzen durch Konstante bestimmt werden. Kennt ein Compiler diese Grenzen können eventuell weitere Optimierungen durchgeführt werden. Beispielcode:

Copy Propagation Eliminiert redundante Kopien einer Variable. -> Benötigt die Ergebnisse der Datenflußanalyse. -> Es wird eine Tabelle verwendet, um die einzelnen Instruktionen (copy instructions) zu speichern. Diese Optimierung entfernt redundante Kopien einer Variable. Hierbei werden die Variablennamen durch den gesamten Code ‚verfolgt‘ um redundante Kopien zu erkennen. Der Compiler verwendet dazu eine Tabelle, welche auf der kommenden Folie gezeigt wird. Hier kurz ein Beispiel dazu:

Copy Propagation - ACP Table of available copy instructions. In Spalte 2 wird der Code vor der Transformation gezeigt. Spalte 4 enthält die Codestücke nach der Optimierung. In Spalte 3 (ACP) verwaltet der Compiler alle verfügbaren ‚Kopier-Instruktionen‘. Der Code in Position 1 kopiert b nach a. Somit merkt sich der Compiler <b,a> als copy instruction. Diese kann er dann an Position 2 anwenden, und b durch das ‚Original‘ a ersetzen. In Position 3 kommt eine neue Instruktion hinzu. Die Variable b wird nach d kopiert, und gleich durch a ersetzt. Die ACP erhält einen neuen Eintrag <d,a>. Im Code bei Position 4 wird d durch a ersetzt. Da b nun überschrieben wird, wird der Eintrag <b,a> aus den ‚available copy instructions‘ gestrichen. Position 5 kopiert d nach b. Hier kann wieder d durch a ersetzt werden, und der Eintrag <b,a> kommt wieder zur Spalte 3 hinzu.

Statement Substitution (Forward Substitution) Ersetzung einer Variable durch ihre Definition. -> Allgemeine Form von ‚Copy Propagation‘ -> Kann die Analyse von Ausdrücken innerhalb einer Schleife vereinfachen Statement Substitution ist eine verallgemeinerte Form von ‚Copy Propagation‘. Eine Variable wird durch ihre Definition ersetzt. Diese Optimierung kann die Analyse von Ausdrücken in einer Schleife vereinfachen. Dies werden wir an folgendem Beispielcode nachvollziehen.

Induction Variable Elimination -> Entfernen der Index-Variable - Folgt meist auf ‚Strength Reduction of Induction Variable Expressions‘ Als ‚Induction Variable‘ bezeichnet man eine Variable, deren Wert nur durch die Anzahl der Iterationen der einschließenden Schleife bestimmt wird. ‚Strength Reduction‘ ist eine Standard-Optimierungstechnik, welche aufwendige (kostspielige) Operationen durch ‚billigere‘ ersetzt. Eine der häufigsten Anwendungen ist die hier erwähnte Optimierung: ‚Strength Reduction of Induction Variable Expressions‘. Sie wird häufig zusammen mit der Optimierung ‚Induction Variable Elimination‘ angewandt, welche in folgendem Beispiel gezeigt wird.

Function Cloning Konstante Argumente einer Funktion werden in einer Kopie durch ihren Wert ersetzt. -> Constant Propagation Dies erhöht zwar die Codegröße, die kopierten Funktionen können aber zum Teil wesentlich schneller ausgeführt werden. Wird eine Funktion mit einem konstanten Wert als Parameter aufgerufen, so kann der Compiler diese Funktion kopieren, und den entsprechenden Parameter entfernen. Alle Vorkommen von diesem werden durch diesen konstanten Wert ersetzt. Wiederum ist es sinnvoll, zuvor die Optimierung ‚Constant Propagation‘ durchzuführen. Durch diese Transformation wird natürlich die Codegröße erhöht, allerdings läßt sich trotzdem ein zum Teil erheblicher Geschwindigkeitsgewinn erreichen, wie das folgende Beispiel auf der nächsten Folie zeigt.

Function Cloning - Beispiel Die Prozedur potenziert jedes Element des Array x mit p. Würde die Funktion der linken Seite mit dem konstanten Parameter p = 2 aufgerufen werden, so kann dieser Parameter entfernt werden, und alle Vorkommen von p werden durch eine 2 ersetzt. Dies würde bedeuten, daß alle Elemente x[i] quadriert werden. Der Compiler kann das erkennen, und der Ausdruck x[i] = x[i] ** 2 wird dann auch noch durch eine einzige Multiplikation ersetzt. Dies bringt bei diesem Beispiel eine ca. 10 - fache Geschwindigkeitssteigerung.

Redundancy Elimination Loop-invariant Code Motion Unreachable Code Elimination Useless Code Elimination Dead Variable Elimination Common-Subexpression Elimination

Loop-invariant Code Motion Ausdrücke, welche ihren Wert in keiner Iteration ändern, werden außerhalb der Schleife gestellt. -> Datenflußanalyse notwendig Bei dieser Optimierung werden Ausdrücke, welche innerhalb einer Schleife stehen, und ihren Wert nicht ändern, vor die Schleife gestellt. Hierfür ist die Datenflußanalyse notwendig. Die fehlerhafte Transformation, welche wir zu Beginn betrachtet haben, wäre ein Beispiel für eine solche Optimierung. Ein gültiges Beispiel ist Folgendes:

Unreachable Code Elimination Ein Codestück wird unerreichbar durch: Bedingungen,welche immer true oder immer false sind Schleifen, welche nie durchlaufen werden -> Datenflußanalyse notwendig Werden Codestücke gefunden, welche während des Programmablaufes nie erreicht werden können, so kann der Compiler diese entfernen. Beispiele wären hierfür Bedingungen, welche einen festen Wert haben, immer false oder immer true, oder auch Schleifen, welche nie durchlaufen werden. Hierzu ist natürlich die Datenflußanalyse notwendig. Wie wir an dem Beispiel bei ‚Dead Variable Elimination‘ sehen werden, ist es sinnvoll, zuvor andere Optimierungen, wie ‚Constant Propagation‘ durchzuführen.

Useless Code Elimination ‚Unnütze‘ Codestücke werden zum Teil durch andere Optimierungen (z.B.: Unreachable Code Elimination) verursacht. -> Datenflußanalyse notwendig Als Ursache der vorhin besprochenen ‚Unreachable Code Elimination‘ können Codestücke auftreten, welche ‚unnütz‘ sind, also keine Bedeutung für das restliche Programm besitzen. Diese werden auch durch den Compiler entfernt.

Dead Variable Elimination Nicht benutzte Variablen werden entfernt. Folgt meist auf die Optimierungen: Unreachable Code Elimination Useless Code Elimination Durch die Datenflußanalyse kann festgestellt werden, ob die Variable noch benutzt wird. Diese Optimierung entfernt sogenannte ‚tote‘ Variablen. Dies sind solche, welche deklariert, aber nie mehr benutzt werden. Der häufigste Grund ist eine zuvor durchgeführte ‚Unreachable Code Elimination‘ und eine ‚Useless Code Elimination‘. Folgendes Beispiel veranschaulicht die letzten drei Optimierungen.

Common-Subexpression Elimination (CSE) -> Identische Ausdrücke (Berechnungen) können gespeichert werden, damit der entsprechende Wert nicht neu berechnet werden muß. -> Datenflußanalyse notwendig Ein Ausdruck wird als ‚common subexpression‘ bezeichnet, wenn ein anderes Auftreten dieses Ausdrucks vorher berechnet wird, und die Operanden sich nicht ändern. Identische Ausdrücke können vom Compiler gespeichert werden, und bei nochmaligem Auftreten durch diesen gespeicherten Wert ersetzt werden. Dies vermeidet eine Neuberechnung. Die nächste Folie zeigt ein Beispiel einer CSE anhand eines CFG.

Common-Subexpression Elimination (CSE) - Beispiel Hier wird der Ausdruck a+2 zweimal verwendet. Dieser kann in t1 gespeichert werden, und somit braucht in B1 der Variable b und in B3 der Variable d nur noch t1 zugewiesen werden.

Ausblick Weitere Optimierungen Laufzeitsysteme (Vortrag 8) Function Inlining Schleifen-Optimierungen (Vortrag 4) Laufzeitsysteme (Vortrag 8) Literatur: 1) Advanced Compiler Design & Implementation, Steven S. Muchnick, 1997, Morgan Kaufmann Publishers, INC. San Francisco, Kalifornien 2) Compilerbau Teil 1, Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullmann, 2 Nachdruck 1992, Addison-Wesley GmbH 3) Übersetzerbau Theorie, Konstruktion, Generierung, R.Wilhelm, D.Maurer, 2. Auflage 1996/1997, Springer Verlag 4) Parallelizing Compilers, Implementation and Effectiveness, Karen L. Piper, Tech Report CSL-TR_91-XXX 5) Compiler Transformations for High-Performance Computing, David F. Bacon, Susan L. Graham, Oliver J. Sharp, Computer Science Division, University of California, Berkeley, Kalifornien 94720, Technical Report No. UCB/CSD-93-781