Einführung in die Systemprogrammierung

Ähnliche Dokumente
Einführung in die Systemprogrammierung

Technische Informatik 1 Übung 2 Assembler (Computerübung) Matthias Meyer

1 Maschinenunabhängige Optimierungen. Maschinenunabhängige Optimierungen Wintersemester 2008/09 1 / 17

Einführung in die Systemprogrammierung

Programmiersprachen Einführung in C

Informatik 1 ( ) D-MAVT F2010. Schleifen, Felder. Yves Brise Übungsstunde 5

Einführung in die Systemprogrammierung

Beispiel einer Übersetzung

Grundlagen der OO- Programmierung in C#

OpenCL. Programmiersprachen im Multicore-Zeitalter. Tim Wiersdörfer

Programmiersprachen und Übersetzer

Einführung in die Systemprogrammierung 0C

Übersicht. Speichertypen. Speicherverwaltung und -nutzung. Programmieren in C

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C

Unterprogramme. Unterprogramme

C/C++-Programmierung

Technische Informatik 1 Übung 2 Assembler (Rechenübung) Georgia Giannopoulou (ggeorgia@tik.ee.ethz.ch) 22./23. Oktober 2015

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Welche Informatik-Kenntnisse bringen Sie mit?

Deklarationen in C. Prof. Dr. Margarita Esponda

6. Funktionen, Parameterübergabe

Konzepte der Programmiersprachen

Assembler - Adressierungsarten

Definition Compiler. Bekannte Compiler

Einführung in die Programmiersprache C

Einführung in die Programmiersprache C

Crashkurs C++ - Teil 1

Speicherklassen (1) Lokale Variablen

7 Funktionen. 7.1 Definition. Prototyp-Syntax: {Speicherklasse} {Typ} Name ({formale Parameter});

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

Assembler - Einleitung

C++ - Objektorientierte Programmierung Konstante und statische Elemente

Programmierung 2. Übersetzer: Das Frontend. Sebastian Hack. Klaas Boesche. Sommersemester

Zwischencode-Erzeugung. 2. Juni 2009

Compiler: Einführung

Just-In-Time-Compiler (2)

Elementare Konzepte von

Just-In-Time-Compiler (2)

Vorlesung Programmieren

Integer Integer Integer (Voreinstellung) Integer Gleitkomma Gleitkomma leer/unbestimmt Integer ohne Vorzeichen Integer (explizit) mit Vorzeichen

Prof. W. Henrich Seite 1

Programmieren in Java -Eingangstest-

Institut für Informatik Prof. Dr. D. Hogrefe Dipl.-Inf. R. Soltwisch, Dipl.-Inform. M. Ebner, Prof. Dr. D. Hogrefe Informatik II - SS 04.

1. Referenzdatentypen: Felder und Strings. Referenz- vs. einfache Datentypen. Rückblick: Einfache Datentypen (1) 4711 r

1. Referenzdatentypen: Felder und Strings

Einführung in die Programmierung I. 1.0 EBNF 2.0 Einfache Java Programme. Thomas R. Gross. Department Informatik ETH Zürich

Übungsblatt 10 (Block C 2) (16 Punkte)

U4-1 Aufgabe 3: einfache malloc-implementierung

Institut für Programmierung und Reaktive Systeme. Java 2. Markus Reschke

Tag 4 Repetitorium Informatik (Java)

10 Die Programmiersprache C99: Zusammenfassung

Informatik II - Tutorium 4

Felder, Zeiger und Adreßrechnung

AuD-Tafelübung T-B5b

Einführung in die Programmierung zusammengesetzte Datentypen, dynamischer Speicher

Klassenvariablen, Klassenmethoden

Algorithmen und Datenstrukturen

Schachtelung der 2. Variante (Bedingungs-Kaskade): if (B1) A1 else if (B2) A2 else if (B3) A3 else if (B4) A4 else A

C- Kurs 08 Zeiger. Dipl.- Inf. Jörn Hoffmann leipzig.de. Universität Leipzig Ins?tut für Informa?k Technische Informa?

DAP2-Programmierpraktikum Einführung in C++ (Teil 2)

Methoden und Wrapperklassen

Motivation und Überblick

Lösungsvorschlag zur 3. Übung

Variablen. Deklaration: «Datentyp» «Variablenname» Datentyp bestimmt Größe in Bytes: sizeof Beispiel: long int v; Größe: 4 Bytes

Einleitung. Informationsquellen: - Webseite zur Vorlesung, Abschnitt Informationen zu C und C++ Kurzeinführung in C/C++

Programmieren in C. Speicher anfordern, Unions und Bitfelder. Prof. Dr. Nikolaus Wulff

Einleitung Entwicklung in C Hello-World! Konstrukte in C Zusammenfassung Literatur. Grundlagen von C. Jonas Gresens

2 Eine einfache Programmiersprache

C- Kurs 09 Dynamische Datenstrukturen

Grundlagen der Programmentwicklung

Speicher und Adressraum

Themen der Übung. Methoden und Wrapperklassen. Vorteile von Methoden. Methoden. Grundlagen

Zeiger. C-Kurs 2012, 2. Vorlesung. Tino Kutschbach 10.

Objektorientierte Programmierung. Kapitel 22: Aufzählungstypen (Enumeration Types)

Algorithmen und Datenstrukturen

Präzedenz von Operatoren

Technische Informatik 1 - Übung 3 3. & 4. November Philipp Miedl

einlesen n > 0? Ausgabe Negative Zahl

2 Teil 2: Nassi-Schneiderman

8. Referenzen und Zeiger

Die Programmiersprache C99: Zusammenfassung

Einführung in die C-Programmierung

Pass by Value Pass by Reference Defaults, Overloading, variable Parameteranzahl

Propädeutikum. Dipl.-Inf. Frank Güttler

Lambda-Ausdrücke in Programmiersprachen 76

Systempraktikum im Wintersemester 2009/2010 (LMU): Vorlesung vom Foliensatz 2

Teil 5: Zeiger, Felder, Zeichenketten Gliederung

Übungs- und Praktikumsaufgaben zur Systemprogrammierung Dipl.-Ing. H. Büchter (Lehrbeauftragter) FH-Dortmund WS 2001/2002 / SS 2002

RO-Tutorien 15 und 16

Inhalt. 1 Einstieg in die Welt von C Erste Schritte in C 31. Vorwort... 15

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java

EINFÜHRUNG IN DIE PROGRAMMIERUNG

Algorithmen und Datenstrukturen

Java: Eine kurze Einführung an Beispielen

Exercise 6. Compound Types and Control Flow. Informatik I für D-MAVT. M. Gross, ETH Zürich, 2017

Einführung in den Einsatz von Objekt-Orientierung mit C++ I

Programmieren in C. Funktionen mit Zeigern und Adressen. Prof. Dr. Nikolaus Wulff

2. Programmierung in C

Transkript:

Einführung in die Systemprogrammierung Prof. Dr. Christoph Reichenbach Fachbereich 12 / Institut für Informatik 9. Juli 2015

Sprachen vs. Übersetzer Sprache C11 Übersetzer GNU C Compiler Intel C Compiler LLVM/Clang Sprachen können verschiedene Implementierungen haben: Übersetzer Interpreter Laufzeit-Übersetzer

Sprachen vs. Übersetzer Sprache C11 Übersetzer GNU C Compiler Intel C Compiler LLVM/Clang Sprachen können verschiedene Implementierungen haben: Übersetzer Interpreter Laufzeit-Übersetzer Sprachdefinition vereinigt Sprachimplementierungen

Die Struktur von Programmiersprachen Sprachdefinition gibt an: Lexikalische Sprachstruktur (Lexeme/Tokens) Grammatische Sprachstruktur (Syntax) Sprachsemantik: Statische Semantik Semantik der Namen: Welcher Name bindet an welche Definition? Semantik der Typen: Welche Typen können wie miteinander verknüpft werden? Dynamische Semantik Laufzeitverhalten des Programmes Beinhaltet meist: Reihenfolge und Inhalt von Ein- und Ausgaben Beinhaltet meist nicht: Ausführungsgeschwindigkeit, benötigter Speicherplatz Optimierungen müssen Sprachsemantik erhalten!

Der Übersetzer C-Programm Frontend Middle-End Backend Optimierung Assembler-Code

Der Übersetzer C-Programm Frontend Middle-End Backend Optimierung Assembler-Code

Der Übersetzer C-Programm Frontend Lexikalische Analyse Syntaxanalyse Namensanalyse Typüberprüfung Zwischenform Middle-End Optimierung Backend Fehlermeldungen Assembler-Code

Der Übersetzer C-Programm Frontend Lexikalische Analyse Syntaxanalyse Namensanalyse Typüberprüfung Zwischenform Middle-End Optimierung Backend Fehlermeldungen Assembler-Code

Der Übersetzer C-Programm Frontend Lexikalische Analyse Syntaxanalyse Namensanalyse Typüberprüfung Zwischenform Middle-End Optimierung Backend Registerauswahl Befehlsauswahl Codeerzeugung Fehlermeldungen Assembler-Code

Der Übersetzer: Frontend: Lexikalische Analyse Eingabe: { int x = 17 + 3L; char *c = x;

Der Übersetzer: Frontend: Lexikalische Analyse Eingabe: { int x = 17 + 3L; char *c = x; Lexikalische Analyse zerlegt Eingabe in Tokens/Lexeme Erzeugt vom Lexer (Tokenizer, Scanner) Ignoriert Leerzeichen, Zeilenumbrüche etc. (in C und C-artigen Sprachen)

Der Übersetzer: Frontend: Lexikalische Analyse Eingabe: { int x = 17 + 3L; char *c = x; Lexikalische Analyse zerlegt Eingabe in Tokens/Lexeme Erzeugt vom Lexer (Tokenizer, Scanner) Ignoriert Leerzeichen, Zeilenumbrüche etc. (in C und C-artigen Sprachen) Tokens und Lexeme: punctuator { int int identifier x punctuator = constant 17 punctuator + constant 3 long-suffix L punctuator ; char char punctuator * identifier c punctuator = identifier x punctuator ; punctuator

Der Übersetzer: Frontend: Parser { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; char * c = x ;

Der Übersetzer: Frontend: Parser declaration type-specifier declarator { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; char * c = x ;

Der Übersetzer: Frontend: Parser declaration initializer type-specifier declarator int-constant int-constant { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; char * c = x ;

Der Übersetzer: Frontend: Parser declaration initializer type-specifier declarator int-constant int-constant { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; declaration type-specifier declarator initializer char * c = x ;

Der Übersetzer: Frontend: Parser declaration compound-statement initializer type-specifier declarator int-constant int-constant { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; declaration type-specifier declarator initializer char * c = x ;

Der Übersetzer: Frontend: Parser declaration compound-statement initializer type-specifier declarator int-constant int-constant { int x = 17 + 3 L ; { int x = 17 + 3L; char *c = x; declaration type-specifier declarator initializer char * c = x ; Parser erzeugt AST (Abstract Syntax Tree)

Frontend: Namensanalyse declaration compound-statement initializer type-specifier declarator int-constant int-constant int x 17 + 3 L declaration type-specifier char declarator * c initializer x

Frontend: Namensanalyse declaration compound-statement initializer type-specifier declarator int-constant int-constant int x 17 + 3 L declaration type-specifier char declarator * c initializer x Verwendungen von Namen werden an ihre Definitionen gebunden

Frontend: Typanalyse (1/2) declaration compound-statement initializer type-specifier int declarator x int int-constant int 17 + int-constant long 3 L declaration type-specifier char declarator * c char* initializer x int

Frontend: Typanalyse (1/2) declaration compound-statement initializer type-specifier int declarator x int int-constant int 17 + int-constant long 3 L declaration type-specifier char declarator * c char* initializer x int Typen werden an Namen gebunden

Frontend: Typanalyse (2/2) declaration compound-statement initializer type-specifier int declarator x int int-constant int 17 + int-constant long 3 L Γ τ(declarator) declaration = τ(initializer) type-specifier char declarator * c char* initializer x int

Frontend: Typanalyse (2/2) declaration compound-statement initializer type-specifier int declarator x int int-constant int-constant int long 17 + 3 L Γ τ(declarator) declaration = τ(initializer) type-specifier Typfehler: char* int declarator initializer char * c char* x int Typregeln erzwingen Typkorrektheit

Der Übersetzer: Middle-End Frontend erzeugt Zwischenform des Programmes

Der Übersetzer: Middle-End Frontend erzeugt Zwischenform des Programmes Zwischenform hilft bei Programmanalyse: Welche Programmteile hängen voneinander ab? (Kontrollfluß/Daten) Welche Programmteile benötigen welche Ressourcen?

Der Übersetzer: Middle-End Frontend erzeugt Zwischenform des Programmes Zwischenform hilft bei Programmanalyse: Welche Programmteile hängen voneinander ab? (Kontrollfluß/Daten) Welche Programmteile benötigen welche Ressourcen? Middle-End optimiert Programmrepräsentierung Optimierte Zwischenform geht an Backend

Der Übersetzer: Optimierungen Optimierung = Programmanalyse a + Transformation t Gegeben Programm p. Falls a(p): Ergebnis von p = Ergebnis von t(p)

Der Übersetzer: Optimierungen Optimierung = Programmanalyse a + Transformation t Gegeben Programm p. Falls a(p): Ergebnis von p = Ergebnis von t(p) Beispiel: a[0] = 0; // A0 a[1] = a[0] ; // A1 f(); a[2] = a[0] ; // A2

Der Übersetzer: Optimierungen Optimierung = Programmanalyse a + Transformation t Gegeben Programm p. Falls a(p): Ergebnis von p = Ergebnis von t(p) Beispiel: a[0] = 0; // A0 a[1] = a[0] ; // A1 f(); a[2] = a[0] ; // A2 A1 auf a[1] = 0 änderbar Korrekte Programmanalysen sind konservativ: Wenn ich nicht weiß, ob ich darf, darf ich nicht

Der Übersetzer: Optimierungen Optimierung = Programmanalyse a + Transformation t Gegeben Programm p. Falls a(p): Ergebnis von p = Ergebnis von t(p) Beispiel: a[0] = 0; // A0 a[1] = a[0] ; // A1 f(); a[2] = a[0] ; // A2 A1 auf a[1] = 0 änderbar A2 nur auf a[2] = 0 änderbar, wenn f() nicht nach a[0] schreibt Korrekte Programmanalysen sind konservativ: Wenn ich nicht weiß, ob ich darf, darf ich nicht

Der Übersetzer: Backend Backend erzeugt Assembler- oder Maschinencode Unterstützt oft verschiedene Prozessoren und Aufrufkonventionen Wesentliche Aufgaben:

Der Übersetzer: Backend Backend erzeugt Assembler- oder Maschinencode Unterstützt oft verschiedene Prozessoren und Aufrufkonventionen Wesentliche Aufgaben: Registerauswahl: Welche Variablen in welche Register? (gesichert, temporär, Spezialregister) Andere Variablen in den Stapelspeicher

Der Übersetzer: Backend Backend erzeugt Assembler- oder Maschinencode Unterstützt oft verschiedene Prozessoren und Aufrufkonventionen Wesentliche Aufgaben: Registerauswahl: Welche Variablen in welche Register? (gesichert, temporär, Spezialregister) Andere Variablen in den Stapelspeicher Befehlsauswahl: Effiziente Befehle suchen, um Ausdrücke abzubilden (Direkt-Operation, Register-Operation usw.)

Der Übersetzer: Backend Backend erzeugt Assembler- oder Maschinencode Unterstützt oft verschiedene Prozessoren und Aufrufkonventionen Wesentliche Aufgaben: Registerauswahl: Welche Variablen in welche Register? (gesichert, temporär, Spezialregister) Andere Variablen in den Stapelspeicher Befehlsauswahl: Effiziente Befehle suchen, um Ausdrücke abzubilden (Direkt-Operation, Register-Operation usw.) Architekturspezifische Optimierungen

Zusammenfassung: Der Übersetzer Drei Übersetzerphasen: Frontend liest Programm ein Typanalyse und Fehlersuche Erzeugt Zwischenrepräsentation Middle-End optimiert Zwischenrepräsentation Kann übersprungen werden (schnelle Übersetzung, langsamer Code) Backend bildet Zwischenrepräsentation auf Assembler/Maschinencode ab Evtl. kleinere maschinenspezifische Optimierungen

Qualifizierer in C extern int sprintf(char * restrict s, const char * restrict format,...);

Qualifizierer in C extern int sprintf(char * restrict s, const char * restrict format,...); Typen in C können qualifiziert werden Qualifizierer wie const sind Schlüsselwörter, die als Teil von Typspezifikationen angegeben werden Geben Hinweise auf geplante Verwendung von Variablen und Funktionen Wirkung: Einfluß auf Programmoptimierung: Helfen oder blockieren Programmanalysen für Optimierungen Einfluß auf Semantik: Typanalyse kann Fehler markieren, wenn Hinweise auf Verwendung der tatsächlichen Verwendung widersprechen

Übersicht: Qualifizierer const Stellt sicher, daß Wert nicht verändert werden kann Erlaubt zusätzliche Optimierungen inline Erbittet Einbetten ( inlining ) von Funktionskörpern in Aufrufer Kann u.u. Programm beschleunigen restrict (auf Zeiger) Verspricht, daß kein aliasing stattfinded Erleichtert Zwischenspeichern von Werten Kann u.u. Programm beschleunigen volatile Zwingt Übersetzer, Variable im Speicher zu halten Einsatzgebiete: Gerätetreiber, nicht-lokaler Kontrollfluß

Qualifizierer: const Variablen, die nach Initialisierung nicht beschrieben werden, sind Konstanten Qualifizierer const const int i = v + 1; Eigenschaft wird von Typüberprüfung erzwungen: Versuchte Zuweisung ist Programmfehler const int x = 5; int f(int y) { return x * y;

Qualifizierer: const Variablen, die nach Initialisierung nicht beschrieben werden, sind Konstanten Qualifizierer const const int i = v + 1; Eigenschaft wird von Typüberprüfung erzwungen: Versuchte Zuweisung ist Programmfehler const int x = 5; int f(int y) { return x * y; // Mit Konstante int f(int y) { return 5 * y;

Qualifizierer: const C-Quellcode const int x = 5; int f(int y) { return x * y; const ermöglicht Optimierungen im Übersetzer

Qualifizierer: const C-Quellcode const int x = 5; int f(int y) { return x * y; mit const: f:sll $v0, $a0, 2 jr $ra addu $v0, $v0, $a0 const ermöglicht Optimierungen im Übersetzer

Qualifizierer: const C-Quellcode const int x = 5; int f(int y) { return x * y; mit const: f:sll $v0, $a0, 2 jr $ra addu $v0, $v0, $a0 ohne const: f:lw $v0, x($gp) nop mult $a0, $v0 mflo $v0 jr $ra nop const ermöglicht Optimierungen im Übersetzer

Qualifizierer: const C-Quellcode const int x = 5; int f(int y) { return x * y; mit const: Mit const: effizienter Ohne const: generischer f:sll $v0, $a0, 2 jr $ra addu $v0, $v0, $a0 ohne const: f:lw $v0, x($gp) nop mult $a0, $v0 mflo $v0 jr $ra nop const ermöglicht Optimierungen im Übersetzer

Qualifizierer: const und Zeiger Zwei relevante Zugriffsrechte für int *p: p += 1: Zeigerarithmetik *p += 1: Inhalt der referenzierten Speicherstelle erhöhen Optionen: int *p Wir dürfen sowohl p als auch *p modifizieren

Qualifizierer: const und Zeiger Zwei relevante Zugriffsrechte für int *p: p += 1: Zeigerarithmetik *p += 1: Inhalt der referenzierten Speicherstelle erhöhen Optionen: int *p Wir dürfen sowohl p als auch *p modifizieren const int *p int const *p Wir dürfen *p modifizieren, aber nicht p

Qualifizierer: const und Zeiger Zwei relevante Zugriffsrechte für int *p: p += 1: Zeigerarithmetik *p += 1: Inhalt der referenzierten Speicherstelle erhöhen Optionen: int *p Wir dürfen sowohl p als auch *p modifizieren const int *p int const *p Wir dürfen *p modifizieren, aber nicht p int * const p Wir dürfen p modifizieren, aber nicht *p

Qualifizierer: const und Zeiger Zwei relevante Zugriffsrechte für int *p: p += 1: Zeigerarithmetik *p += 1: Inhalt der referenzierten Speicherstelle erhöhen Optionen: int *p Wir dürfen sowohl p als auch *p modifizieren const int *p int const *p Wir dürfen *p modifizieren, aber nicht p int * const p Wir dürfen p modifizieren, aber nicht *p const int * const p int const * const p Wir dürfen weder p noch *p modifizieren

Qualifizierer: inline Funktionsaufrufe können hohe Kosten haben: Stapelspeicher beschreiben Registerwahl einschränken Stapelspeicher lesen Inlining ist Optimierung, die Funktionskörper einbettet: int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) args[i] = f(args[i]);

Qualifizierer: inline Funktionsaufrufe können hohe Kosten haben: Stapelspeicher beschreiben Registerwahl einschränken Stapelspeicher lesen Inlining ist Optimierung, die Funktionskörper einbettet: int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) args[i] = f(args[i]); // Mit Inlining void g(int argc, int *args) { for (int i = 0; i < argc; i++) args[i] = 2 + (args[i]);

Qualifizierer: ohne inline int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) { args[i] = f(args[i]);

Qualifizierer: ohne inline int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) { args[i] = f(args[i]); g: addiu $sp, $sp, -40 sw $ra, $s0-$s3...($sp) move $s2, $a0 blez $a0, L6 move $s3, $a1 move $s1, $zero L5:sll $s0, $s1, 2 addu $s0, $s3, $s0 lw $a0, 0($s0) jal f addiu $s1, $s1, 1 slt $v1, $s1, $s2 bne $v1, $zero, L5 sw $v0, 0($s0) L6:lw $ra, $s0-$s3...($sp) jr $ra addiu $sp, $sp, 40

Qualifizierer: mit inline inline int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) { args[i] = f(args[i]);

Qualifizierer: mit inline inline int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) { args[i] = f(args[i]); g: blez $a0, L9 move $a3, $a0 move $a2, $zero L5:sll $v0, $a2, 2 addu $v0, $a1, $v0 lw $v1, 0($v0) addiu $a2, $a2, 1 addiu $v1, $v1, 2 slt $a0, $a2, $a3 bne $a0, $zero, L5 sw $v1, 0($v0) L9:jr $ra nop

Qualifizierer: mit inline inline int f(int x) { return 2 + x; void g(int argc, int *args) { for (int i = 0; i < argc; i++) { args[i] = f(args[i]); g: blez $a0, L9 move $a3, $a0 move $a2, $zero L5:sll $v0, $a2, 2 addu $v0, $a1, $v0 lw $v1, 0($v0) addiu $a2, $a2, 1 addiu $v1, $v1, 2 slt $a0, $a2, $a3 bne $a0, $zero, L5 sw $v1, 0($v0) L9:jr $ra nop Kein expliziter f-aufruf Stapelspeicherverwaltung unnötig

Inlining in der Praxis Inlining ist eine der wichtigsten Optimierungen Übersetzer inlininen meist heuristisch: Funktionen mit kleinem Funktionskörper (kleiner als Inlining-Schwelle) (C, C++) Funktionen, die als Funktionszeiger übergeben werden (OCaml) Funktionen, die häufig von der gleichen Stelle aufgerufen werden (Java)

Inlining in der Praxis Inlining ist eine der wichtigsten Optimierungen Übersetzer inlininen meist heuristisch: Funktionen mit kleinem Funktionskörper (kleiner als Inlining-Schwelle) (C, C++) Funktionen, die als Funktionszeiger übergeben werden (OCaml) Funktionen, die häufig von der gleichen Stelle aufgerufen werden (Java) C11 6.7.4.6: Exakte Bedeutung von inline ist implementierungsabhängig gcc: Der inline-qualifizierer erhöht die Inlining-Schwelle für diese Funktion

Inlining in der Praxis Inlining ist eine der wichtigsten Optimierungen Übersetzer inlininen meist heuristisch: Funktionen mit kleinem Funktionskörper (kleiner als Inlining-Schwelle) (C, C++) Funktionen, die als Funktionszeiger übergeben werden (OCaml) Funktionen, die häufig von der gleichen Stelle aufgerufen werden (Java) C11 6.7.4.6: Exakte Bedeutung von inline ist implementierungsabhängig gcc: Der inline-qualifizierer erhöht die Inlining-Schwelle für diese Funktion Meist ist explizites inline nicht nötig

Qualifizierer: restrict int maxsum(int *summe, int *a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; if (a[len] > max) max = a[len]; return max; Eingabe: Array a mit len Elementen Berechnet: *summe = a[0] +... + a[len-1] return max(a[0]... a[len-1])

Qualifizierer: ohne restrict int maxsum(int *summe, int *a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; //A1 if (a[len] > max) //A2 max = a[len]; //A3 return max; sw $zero, 0($a0) beq $a2, $zero, L5 addiu $v1, $a2, -1 move $a2, $zero move $v0, $zero L4:sll $a3, $v1, 2 addu $a3, $a1, $a3 lw $t0, 0($a3) # lade a[len] nop addu $a2, $a2, $t0 sw $a2, 0($a0) # schreibe *summe lw $a3, 0($a3) # lade a[len] nop slt $t0, $v0, $a3 beq $t0, $zero, L3 addiu $t1, $v1, -1 move $v0, $a3 L3:bne $v1, $zero, L4 move $v1, $t1 jr $ra nop L5:jr $ra move $v0, $zero

Qualifizierer: restrict int maxsum(int *summe, int *a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; //A1 if (a[len] > max) //A2 max = a[len]; //A3 return max; Was, wenn summe in *a zeigt? aliasing: Mehrere Zeiger zeigen auf gleiches Objekt int a[3]; int x = maxsum(&a[1], a, 3); a = summe summe ist Alias von (a+1) Bestimmte Optimierungen werden durch konservative Annahmen des Übersetzers verhindert

Qualifizierer: ohne restrict int maxsum(int *summe, int *a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; //A1 if (a[len] > max) //A2 max = a[len]; //A3 return max; sw $zero, 0($a0) beq $a2, $zero, L5 addiu $v1, $a2, -1 move $a2, $zero move $v0, $zero L4:sll $a3, $v1, 2 addu $a3, $a1, $a3 lw $t0, 0($a3) # lade a[len] nop addu $a2, $a2, $t0 sw $a2, 0($a0) # schreibe *summe lw $a3, 0($a3) # lade a[len] nop slt $t0, $v0, $a3 beq $t0, $zero, L3 addiu $t1, $v1, -1 move $v0, $a3 L3:bne $v1, $zero, L4 move $v1, $t1 jr $ra nop L5:jr $ra move $v0, $zero

Qualifizierer: mit restrict int maxsum(int * restrict summe, int * restrict a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; if (a[len] > max) max = a[len]; return max;

Qualifizierer: mit restrict int maxsum(int * restrict summe, int * restrict a, int len) { *summe = 0; int max = 0; while (len--) { *summe += a[len]; if (a[len] > max) max = a[len]; return max; sw $zero, 0($a0) beq $a2, $zero, L5 addiu $v1, $a2, -1 move $a3, $zero move $v0, $zero L4:sll $a2, $v1, 2 addu $a2, $a1, $a2 lw $a2, 0($a2) # lade a[len] addiu $t1, $v1, -1 addu $a3, $a3, $a2 # 1 lw, 2 nop entfernt slt $t0, $v0, $a2 beq $t0, $zero, L3 sw $a3, 0($a0) # schreibe *summe move $v0, $a2 L3:bne $v1, $zero, L4 move $v1, $t1 jr $ra nop L5:jr $ra move $v0, $zero

Qualifizierer: restrict restrict verspricht, daß Aliasing unmöglich ist

Qualifizierer: restrict restrict verspricht, daß Aliasing unmöglich ist Compilerannahme: Aliasing zwischen Zeigern τ *a und τ *b möglich: a restrict a b ja ja restrict b ja nein

Qualifizierer: restrict restrict verspricht, daß Aliasing unmöglich ist Compilerannahme: Aliasing zwischen Zeigern τ *a und τ *b möglich: a restrict a b ja ja restrict b ja nein Kein Aliasing: Von Zeigern geladene Werte können länger in Registern zwischengespeichert werden... = *a; // *a lesen *b = z; // *b schreiben... = *a; // *a zwischengespeichert gdw restrict a und b

Qualifizierer: volatile Optimierer darf normalerweise: Variablen in Register legen Speicherzugriffreihenfolge umordnen Beide Änderungen haben normalerweise keine Auswirkung auf das sichtbare Programmverhalten

Qualifizierer: volatile Optimierer darf normalerweise: Variablen in Register legen Speicherzugriffreihenfolge umordnen Beide Änderungen haben normalerweise keine Auswirkung auf das sichtbare Programmverhalten Problematisch für: Gerätetreiber: Speicheradressen auf Geräteregister abgebildet Zugriffreihenfolge gemäß Hardwareprotokoll Plötzliche Sprünge (Ausnahmebehandlung): Registerinhalte gehen verloren

Qualifizierer: volatile volatile: Variable wird nicht in Registern gesichert Reihenfolge der volatile-zugriffe wird immer beibehalten int c; volatile int va, vb; va = 0; c = 1; vb = 2; // nach va

Qualifizierer: volatile volatile: Variable wird nicht in Registern gesichert Reihenfolge der volatile-zugriffe wird immer beibehalten int c; volatile int va, vb; va = 0; c = 1; vb = 2; // nach va Mögliche Schreibreihenfolgen va = 0; c = 1; va = 0; c = 1; va = 0; vb = 2; vb = 2; vb = 2; c = 1;

Qualifizierer: volatile volatile: Variable wird nicht in Registern gesichert Reihenfolge der volatile-zugriffe wird immer beibehalten int c; volatile int va, vb; va = 0; c = 1; vb = 2; // nach va Mögliche Schreibreihenfolgen va = 0; c = 1; va = 0; c = 1; va = 0; vb = 2; vb = 2; vb = 2; c = 1; Vorsicht: volatile alleine reicht nicht, um nebenläufige Prozesse zu synchronisieren

Automatische Optimierungen Übersetzer können viele Arten von Optimierungen durchführen: Inlining Auswertung konstanter Ausdrücke Rückhaltung von Speicherinhalten...

Automatische Optimierungen Übersetzer können viele Arten von Optimierungen durchführen: Inlining Auswertung konstanter Ausdrücke Rückhaltung von Speicherinhalten... Programmierer müssen sich nicht um Optimierungen kümmern, die der Übersetzer durchführen kann

Grundlegende Optimierungen Konstantenfaltung (constant folding) Konstantenpropagierung (constant propagation) Eliminierung gemeinsamer Teilausdrücke (common subexpression elimination) Eliminierung von totem Code (dead code elimination) Schleifeninvariante Codeverschiebung (loop-invariant code motion)

Konstantenfaltung Vor Optimierung: int x = 3 + 7;

Konstantenfaltung Vor Optimierung: int x = 3 + 7; Nach Optimierung: int x = 10; Berechnungen über Konstanten werden ausgewertet

Konstantenpropagierung Vor Optimierung: int x = 7;... // x wird nicht verändert for (int i = 0; i < x; i++) {... // x wird nicht verändert

Konstantenpropagierung Vor Optimierung: int x = 7;... // x wird nicht verändert for (int i = 0; i < x; i++) {... // x wird nicht verändert Nach Optimierung: int x = 7;... for (int i = 0; i < 7; i++) {... Variablen mit konstantem Inhalt werden substituiert

Eliminierung gemeinsamer Teilausdrücke Vor Optimierung: int x = v * v + 17; int y = v * v + 23;

Eliminierung gemeinsamer Teilausdrücke Vor Optimierung: int x = v * v + 17; int y = v * v + 23; Nach Optimierung: const int v2 = v * v; int x = v2 + 17; int y = v2 + 23; Mehrfach hintereinander verwendete Teilausdrücke werden extrahiert und vorberechnet

Eliminierung von totem Code Vor Optimierung: char *s = "C99 oder neuer"; if ( STDC_VERSION < 199901L) { s = "vor C99";

Eliminierung von totem Code Vor Optimierung: char *s = "C99 oder neuer"; if ( STDC_VERSION < 199901L) { s = "vor C99"; Nach Optimierung: char *s = "C99 oder neuer"; // Eliminiert Blöcke mit nicht erfüllbaren Vorbedingungen werden entfernt

Abhängigkeiten zwischen Optimierungen Optimierungen beeinflussen sich gegenseitig: Eliminierung von totem Code muß Bedingungen auswerten: Abhängigkeit: Konstantenfaltung / Konstantenpropagierung Konstantenfaltung muß Konstanten sehen: Abhängigkeit: Konstantenpropagierung Konstantenpropagierung muß wissen, daß Ausdrücke konstant sind: Abhängigkeit: Eliminierung von totem Code (De)aktivierung einer Optimierung kann andere Optimierungen indirekt beeinflussen

Schleifeninvariante Codeverschiebung Vor Optimierung: for (int i = 0; i <= 1000; i++) { int v = p * p; a[i] += v;

Schleifeninvariante Codeverschiebung Vor Optimierung: for (int i = 0; i <= 1000; i++) { int v = p * p; a[i] += v; Nach Optimierung: int v = p * p; for (int i = 0; i <= 1000; i++) { a[i] += v; Berechnungen, die in jedem Schleifenschritt das gleiche Ergebnis produzieren, werden vor die Schleife geschoben

Komplexe Optimierungen Schleifenteilung (loop splitting) Schleifenabwicklung (loop unrolling) Schleifenfusion (loop fusion) Stärkereduktion (strength reduction) Stapel-inlining (stack inlining) Inlining

Schleifenteilung Vor Optimierung: int v = 1; for (int i = 0; i <= 1000; i++) { a[i] += v; v = p * p; //A v ist nicht schleifen-invariant: A kann nicht verschoben werden

Schleifenteilung Vor Optimierung: int v = 1; for (int i = 0; i <= 1000; i++) { a[i] += v; v = p * p; //A v ist nicht schleifen-invariant: A kann nicht verschoben werden Nach Optimierung: int v = 1; for (int i = 0; i == 0; i++) { a[i] += v; v = p * p; //A for (int i = 1; i <= 1000; i++) { a[i] += v; v = p * p; //A Schleifen werden in Teilschleifen aufgespalten, um weitere Optimierungen zu erlauben (hier: Verschieben von A)

Schleifenabwicklung (1/2) Vor Optimierung: for (int i = 0; i < 4; i++) { a[i] += i; Nach Optimierung:

Schleifenabwicklung (1/2) Vor Optimierung: for (int i = 0; i < 4; i++) { a[i] += i; Nach Optimierung: a[0] += 0; a[1] += 1; a[2] += 2; a[3] += 3; Schleifenkörper wird kopiert, um Vergleiche/Sprünge zu sparen und weitere Optimierungen zu ermöglichen

Schleifenabwicklung (2/2) Vor Optimierung: for (int i = 0; i < 4000; i++) { a[i] += i;

Schleifenabwicklung (2/2) Vor Optimierung: for (int i = 0; i < 4000; i++) { a[i] += i; Nach Optimierung: for (int i = 0; i < 4000; i+=4) { a[i] += i; a[i+1] += i+1; a[i+2] += i+2; a[i+3] += i+3; Partielle Schleifenabwicklung für lange Schleifen, insbesondere zusammen mit Schleifenteilung

Schleifenfusion Vor Optimierung: for (int i = 0; i < 4000; i++) { a[i] += i; for (int i = 0; i < 4000; i++) { b[i] += a[i];

Schleifenfusion Vor Optimierung: for (int i = 0; i < 4000; i++) { a[i] += i; for (int i = 0; i < 4000; i++) { b[i] += a[i]; Nach Optimierung: for (int i = 0; i < 4000; i++) { a[i] += i; // Zusammengefuegt b[i] += a[i]; Schleifenkörper, die über die gleichen Daten iterieren, werden zusammengefügt

Stärkereduktion Vor Optimierung: for (int i = 0; i < 1000; i++) { a[i * 13] = 0;

Stärkereduktion Vor Optimierung: for (int i = 0; i < 1000; i++) { a[i * 13] = 0; Nach Optimierung: int i13 = 0; for (int i = 0; i < 1000; i++) { a[ i13] = 0; i13 += 13; Ersetze teure Operationen in Schleife durch billigere inkrementelle Operationen

Stapel-Inlining Vor Optimierung: int *a = (int *) malloc(sizeof(int) * 4); lies_daten(a); int i = a[0] + a[3]; free(a);

Stapel-Inlining Vor Optimierung: int *a = (int *) malloc(sizeof(int) * 4); lies_daten(a); int i = a[0] + a[3]; free(a); Nach Optimierung: int a[4]; lies_daten(a); int i = a[0] + a[3];

Stapel-Inlining Vor Optimierung: int *a = (int *) malloc(sizeof(int) * 4); lies_daten(a); int i = a[0] + a[3]; free(a); Nach Optimierung: int a[4]; lies_daten(a); int i = a[0] + a[3]; Einschränkung: Allozierung muß klein sein, wenn Stapelspeicher beschränkt ist

Stapel-Inlining Vor Optimierung: int *a = (int *) malloc(sizeof(int) * 4); lies_daten(a); int i = a[0] + a[3]; free(a); Nach Optimierung: int a[4]; lies_daten(a); int i = a[0] + a[3]; Einschränkung: Allozierung muß klein sein, wenn Stapelspeicher beschränkt ist Allozierung auf Ablagespeicher wird in Allozierung auf Stapelspeicher umgewandelt

Zusammenfassung: Optimierungen (1/2) Konstantenfaltung: Berechnungen auf Konstanten werden fertiggerechnet Konstantenpropagierung: Konstante Variablen werden substituiert Eliminierung gemeinsamer Teilausdrücke: Mehrfach verwendete Teilausdrücke werden extrahiert, nur ein Mal berechnet Eliminierung von totem Code: Nicht ausführbare Programmteile werden gelöscht Schleifeninvariante Codeverschiebung: Unveränderliche Berechnungen in Schleifen werden aus Schleife gehoben

Zusammenfassung: Optimierungen (2/2) Schleifenteilung: Schleife wird in mehrere Schleifen gespalten Schleifenabwicklung: Mehrere Kopien des Schleifenkörpers werden ausgeführt Schleifenfusion: Schleifen über gleiche Daten werden zusammengeklebt Stärkereduktion: Berechnungen mit teuren Operationen in Schleifen werden mit billigeren Operationen inkrementalisiert Stapel-inlining: malloc wird auf den Stapelspeicher verlegt Inlining: Funktionskörper werden in Aufrufstelle kopiert

Die Grenzen von Optimierungen Einschränkungen der Implementierung Fehlende Unterstützung Berechenbarkeit Programmgleicheit ist unentscheidbar Sprachsemantik Programmverhalten muß Sprachspezifikation entsprechen Überspezifizierung Programmierer-Spezifikation schränkt Optimierer unnötig ein Beschränktes Wissen Übersetzer sieht nicht genug Informationen

Optimierungsgrenzen durch Berechenbarkeitsgrenzen Gegeben: zwei Sortierfunktionen quicksort bubble_sort Beide sind semantisch äquivalent: gleiche Wirkung, soweit durch Sprachsemantik definiert Kann der Übersetzer automatisch eine durch die andere Funktion ersetzen? Zur automatischen Optimierung muß Übersetzer berechnen können, daß die Funktionen äquivalent sind

Optimierungsgrenzen durch Berechenbarkeitsgrenzen Gegeben: zwei Sortierfunktionen quicksort bubble_sort Beide sind semantisch äquivalent: gleiche Wirkung, soweit durch Sprachsemantik definiert Kann der Übersetzer automatisch eine durch die andere Funktion ersetzen? Zur automatischen Optimierung muß Übersetzer berechnen können, daß die Funktionen äquivalent sind Programmgleichheit ist nicht berechenbar: Diese Optimierung ist i.a. nicht möglich

Optimierungsgrenzen durch Sprachsemantik (1/2) a struct { short a; int w1; short b; int w2; s; // 2 Bytes // 4 Bytes // 2 Bytes // 4 Bytes w1 b w2

Optimierungsgrenzen durch Sprachsemantik (1/2) a a b struct { short a; int w1; short b; int w2; s; // 2 Bytes // 4 Bytes // 2 Bytes // 4 Bytes w1 b w1 w2 w2? Wir könnten Speicher sparen, wenn wir die Felder umsortieren würden

Optimierungsgrenzen durch Sprachsemantik (2/2) Darf der Übersetzer diese Umsortierung selbst durchführen? C11 Sprachspezifikation, 6.7.2.1.15: Within a structure object, the [...] members [...] have addresses that increase in the order in which they are declared. [...]

Optimierungsgrenzen durch Sprachsemantik (2/2) Darf der Übersetzer diese Umsortierung selbst durchführen? C11 Sprachspezifikation, 6.7.2.1.15: Within a structure object, the [...] members [...] have addresses that increase in the order in which they are declared. [...] Sprachspezifikation verbietet Optimierung

Optimierungsgrenzen durch Überspezifikation Überspezifikation: Programm beinhaltet Details, die 1. Das Berechnungsergebnis nicht beeinflussen, aber 2. Zur Berechnung nicht nötig sind Überspezifikation verhindert Optimierungen for (volatile int x = 1; x < 10; x++) { a[x] += 2; // kein Grund für volatile

Optimierungsgrenzen durch Überspezifikation Überspezifikation kann indirekt stattfinden: int helfer(int i) { return... int f(int i) {...helfer... Inlining von helfer möglich

Optimierungsgrenzen durch Überspezifikation Überspezifikation kann indirekt stattfinden: int helfer(int i) { return... int f(int i) {...helfer... Inlining von helfer möglich Aber: helfer kann nach inlining nicht eliminiert werden: Ein externes Modul könnte auf helfer zugreifen helfer ist nicht static, damit global im Sinne des Binders

Optimierungsgrenzen durch Überspezifikation Überspezifikation kann indirekt stattfinden: int helfer(int i) { return... int f(int i) {...helfer... Inlining von helfer möglich Aber: helfer kann nach inlining nicht eliminiert werden: Ein externes Modul könnte auf helfer zugreifen helfer ist nicht static, damit global im Sinne des Binders Überspezifikation durch fehlende Angaben möglich

Zusammenfassung: Einschränkungen der Optimierungen Implementierungsbeschränkungen Berechenbarkeit Sprachsemantik und Überspezifizierung Beschränktes Wissen

Wissen des Optimierers Optimierung: Programmanalyse a + Transformation t Wissen von a: t ist korrekt Optimierung t ist inkorrekt keine Optimierung nicht genug Informationen keine Optimierung

Wissen des Optimierers Optimierung: Programmanalyse a + Transformation t Wissen von a: t ist korrekt Optimierung t ist inkorrekt keine Optimierung nicht genug Informationen keine Optimierung Desto mehr der Optimierer weiß, desto besser kann er optimieren

Modul-Optimierung Modul-Optimierung: Normalfall Jedes Modul (Programmdatei) wird einzeln optimiert Wissen des Optimierers: Inhalt eines Moduls Modul-0 Modul-1 Modul-2 Übersetzer Übersetzer Übersetzer Binder Ausführung

Gesamtprogrammoptimierung whole-program optimisation Alle Module werden gleichzeitig optimiert Wissen des Optimierers: Inhalt aller Module Das Programm ist vollständig Modul-0 Modul-1 Modul-2 Übersetzer Binder Ausführung Wissen, daß das Programm vollständig ist, erlaubt viele neue Optimierungen, die sonst nur mit const oder static möglich wären

Bindezeit-Optimierung Modul-0 Modul-1 Modul-2 link-time optimisation Binder optimiert Module mit Informationen aus anderen Modulen Wissen des Optimierers: Teile des Modulwissens Übersetzer Übersetzer Übersetzer Binder Ausführung

Optimierung mit Laufzeitprofil profile-directed optimisation feedback-driven optimisation Übersetzer verwenden Laufzeitmessungen, um Programme zu beschleunigen Zusätzlich zu Modul- /Ganzprogrammoptimierung Wissen des Optimierers: Schleifendurchlaufstatistiken Sprungwahrscheinlichkeiten Oft ausgeführter Code und alles, was er vorher wußte Modul-0 Modul-1 Modul-2 Übersetzer Übersetzer Übersetzer Binder Ausführung Programmprofil

Optimierung: Zusammenfassung Optimierungen können zu verschiedenen Zeitpunkten stattfinden: Optimierung bei Modulübersetzung: Vorteile: Relativ schnelle Übersetzung Nachteile: Nur beschränktes Wissen Ganzprogramm-Optimierung: Vorteile: Sehr effektiv Nachteile: Sehr langsam, manchmal unmöglich: Unterstützt keine dynamisch geladenen Module Optimierung braucht viel Arbeitsspeicher Bindezeit-Optimierung: Vorteile: Optimierung jenseits von Modulgrenzen Nachteile: Große Objektdateien, langsames Binden Optimierung mit Laufzeitprofil: Vorteile: Muß Programmverhalten nicht raten Nachteile: Typischer Programmlauf nötig, hoher Aufwand