Foliensatz B komplett (animiert), ohne

Werbung
Einführung in die Systemprogrammierung
Prof. Dr. Christoph Reichenbach
Fachbereich 12 / Institut für Informatik
9. Juli 2015
Übersetzer, Interpreter, Dynamischer Übersetzer
I
Verschiedene Modi der Programmausführung:
I
I
I
I
Übersetzer
Interpreter
Dynamischer Übersetzer
Kernunterschied:
I
I
Wird Programm in Maschinencode übersetzt?
Falls ja, wann?
Interpreter
Vor Ausführung
Frontend
Zwischenform
(z.B. CPython, Ruby (ohne JRuby), Perl, PHP)
Interpreter
Vor Ausführung
Während Ausführung
Frontend
Interpretierung
Zwischenform
(z.B. CPython, Ruby (ohne JRuby), Perl, PHP)
Interpreter
Interpreter
Vor Ausführung
Während Ausführung
Frontend
Interpretierung
Zwischenform
Interpreter
CPU
(z.B. CPython, Ruby (ohne JRuby), Perl, PHP)
Keine Übersetzung in Maschinencode
Übersetzer
Vor Ausführung
Frontend+Middle-End
Zwischenform
Backend
(z.B. C, C++, Eiffel, Modula-3, FORTRAN, SML, Haskell)
Übersetzer
Vor Ausführung
Während Ausführung
Frontend+Middle-End
Zwischenform
Backend
Maschinencode
(z.B. C, C++, Eiffel, Modula-3, FORTRAN, SML, Haskell)
Übersetzer
Vor Ausführung
Während Ausführung
Frontend+Middle-End
Zwischenform
Backend
Maschinencode
CPU
(z.B. C, C++, Eiffel, Modula-3, FORTRAN, SML, Haskell)
Übersetzung in Maschinencode vor Ausführung
Vergleich: Übersetzung vs Interpretierung
Eigenschaft
Konstruktion
Laufzeit-Performanz
Programmausführung
Sprachmächtigkeit
?) Übersetzeroptimierungen
Interpreter
einfach
langsam
direkt
hoch
Flexibilität
Übersetzer
schwer
schnell
übersetzen & binden
beschränkt?
Dynamische Übersetzung
I
I
I
Auch: JIT (Just-In-Time)-Übersetzung
Idee: Code-Übersetzung während Programmausführung
Theorie: Verbindet Vorteile von Interpreter und Übersetzer
Dynamische Übersetzung
I
I
I
I
Auch: JIT (Just-In-Time)-Übersetzung
Idee: Code-Übersetzung während Programmausführung
Theorie: Verbindet Vorteile von Interpreter und Übersetzer
Praxis:
I
I
I
Sehr schwer zu bauen
Speicherbedarf steigt
Kann bessere Performanz als traditioneller Übersetzung
erreichen
Dynamischer Übersetzer
Vor Ausführung
Zwischenform
Frontend+Middle-End
(z.B. Java, Scala, AttoL, .NET, JavaScript)
Dynamischer Übersetzer
Vor Ausführung
Während Ausführung
Zwischenform
Frontend+Middle-End
(optional)
Middle-End
& Backend
Interpretierung
Maschinencode
Interpreter
(z.B. Java, Scala, AttoL, .NET, JavaScript)
Dynamischer Übersetzer
Vor Ausführung
Während Ausführung
Zwischenform
Frontend+Middle-End
(optional)
Middle-End
& Backend
Interpretierung
Maschinencode
Interpreter
CPU
(z.B. Java, Scala, AttoL, .NET, JavaScript)
Übersetzung in Maschinencode währen Ausführung
Dynamischer Übersetzer
Vor Ausführung
Während Ausführung
Bytecode
Zwischenform
Frontend+Middle-End
(optional)
Middle-End
& Backend
Interpretierung
Maschinencode
Interpreter
CPU
(z.B. Java, Scala, AttoL, .NET, JavaScript)
Übersetzung in Maschinencode währen Ausführung
Zusammenfassung
I
Sprachimplementierung per:
I
I
I
Separatem Übersetzer
Interpreter
Dynamischem Übersetzer
I
I
Abwägung zwischen:
I
I
I
Kann Interpreter beinhalten
Mächtigkeit der Sprache
Ausführungszeit / Speicherbedarf
Sprachen können verschiedentlich implementiert werden
I
Beispiel: CPython vs. Jython
Bytecode als Zwischensprache
Historischer Hintegrund:
I
I
I
Entwicklung von Pascal-Übersetzern
Portierung von Übersetzern von Architektur A nach B
zeitaufwändig, fehleranfällig
Idee:1
I
I
I
I
1
Wir schreiben eine Übersetzerimplementierung, in Pascal
Zielarchitektur P, eine Stapelmaschine (nicht in
Hardware)
P-Code: möglichst einfach gehalten
Interpreter für P-Code: Einfach zu schreiben
Nori, Ammmann, Jensen, Nägeli: “The Pascal P Compiler: Implementation
notes”, ETH Zürich, Dezember 1974
Bytecode als Zwischensprache
Historischer Hintegrund:
I
I
I
Entwicklung von Pascal-Übersetzern
Portierung von Übersetzern von Architektur A nach B
zeitaufwändig, fehleranfällig
Idee:1
I
I
I
I
I
1
Wir schreiben eine Übersetzerimplementierung, in Pascal
Zielarchitektur P, eine Stapelmaschine (nicht in
Hardware)
P-Code: möglichst einfach gehalten
Interpreter für P-Code: Einfach zu schreiben
Übersetzen den Übersetzer nach P-Code:
⇒ Ausführen des Übersetzers benötigt nur
P-Code-Interpreter
Nori, Ammmann, Jensen, Nägeli: “The Pascal P Compiler: Implementation
notes”, ETH Zürich, Dezember 1974
Bytecode heute
I
I
P-Code ist erstes Beispiel für Bytecode
In unseren Beispielsprachen verwendet von:
I
I
Python
Java
Bytecode heute
I
I
P-Code ist erstes Beispiel für Bytecode
In unseren Beispielsprachen verwendet von:
I
I
I
I
Python
Java
Interpretierung ineffizient (Faktor 15-20 langsamer als
übersetzer Code)
Stattdessen: Übersetzung P-Code nach Maschinencode
Bytecode: Vorteile und Nachteile
Vorteile
I
I
Obfuszierung trotz
Portabilität
Übersetzung Bytecode vs.
Quellcode:
I
I
I
I
Nachteile
I
I
I
I
Sparen uns Parsen,
Namensanalyse
Einfache Interpretierung
Software-Werkzeuge können
einfache
Programmrepräsentierung zur
Analyse nutzen
Oberflächensyntax kann
geändert werden
Aufwand
Bytecode-Schnittstelle:
I
I
I
Spezifizierung
Backend
Frontend
Speicherrepräsentierung
Bytecode-Eingabe, Ausgabe
implementieren
Versionskompatibilität
Andere Formen von Bytecode
I
Dalvik-Bytecode
I
I
I
Common Intermediate Language (CIL)
I
I
I
Verwendet auf Android
Registertransfercode: Ähnelt realistischem Maschinencode
Verwendet von Microsoft .NET
Ähnlich Java-Bytecode
LLVM-Bitcode
I
I
...
Verwendet vom LLVM-Übersetzer
Komplexer Code mit Eigenschaften einer Hochsprache
Zusammenfassung: Bytecode
I
I
I
I
I
Bytecode repräsentiert Zwischenzustand des Übersetzers
Meist weniger komplex als Hochsprache,
Maschinensprache
Plattformunabhangig
Erleichtert Portierung von Programmen
Höhere Implementierungskomplexität
Starke Typisierung
Früh-Entdeckung der folgenden Typprobleme:
I
I
I
Falscher Speicherbereich
Falscher Typ
Array-Grenzen überschritten
Vermeidung: Typfehler „Falscher Speicherbereich“
I
I
I
Stark typisierte Sprachen haben keinen Zeigertyp
Stattdessen: Referenzen
Typvertrag für Referenzen: Referenz zeigt immer auf:
I
I
NULL-Adresse, oder
Anfang eines Objektes
In stark typisierten Sprachen verboten:
void qsort (int *daten , int len) {
...
qsort (daten , links );
qsort ( daten + links + 1, // Zeigerarithmetik
len - links - 1);
}
Vermeidung: Typfehler „Falscher Speicherbereich“
I
I
I
Stark typisierte Sprachen haben keinen Zeigertyp
Stattdessen: Referenzen
Typvertrag für Referenzen: Referenz zeigt immer auf:
I
I
NULL-Adresse, oder
Anfang eines Objektes
In stark typisierten Sprachen verboten:
void qsort (int *daten , int len) {
...
qsort (daten , links );
qsort ( daten + links + 1, // Zeigerarithmetik
len - links - 1);
}
Ohne Zeigertypen keine Zeigerarithmetik
Vermeidung: Typfehler „Falscher Typ“ (1/2)
I
Alle Operationen sind an bestimmte Typen gebunden
I
I
I
Explizite Typhierarchie (Java, C++)
Keine Typhierarchie: Typ definiert sich ‘implizit’ durch
Eigenschaften (Python, AttoL)
Bei jeder Operation: Typprüfung
I
I
Im Übersetzer (statisch)
Zur Laufzeit (dynamisch)
AttoL
obj a = [1, 2, 3]; // Array
a := a + 1; // Fehler: Arithmetik auf Array
Vermeidung: Typfehler „Falscher Typ“ (1/2)
I
Alle Operationen sind an bestimmte Typen gebunden
I
I
I
Explizite Typhierarchie (Java, C++)
Keine Typhierarchie: Typ definiert sich ‘implizit’ durch
Eigenschaften (Python, AttoL)
Bei jeder Operation: Typprüfung
I
I
Im Übersetzer (statisch)
Zur Laufzeit (dynamisch)
AttoL
obj a = [1, 2, 3]; // Array
a := a + 1; // Fehler: Arithmetik auf Array
Nutzt Laufzeit-Typinformationen
Vermeidung: Typfehler „Falscher Typ“ (2/2)
AttoL
$a0
obj a = ...
...
a := a + 1;
⇒
Typdeskriptor
Wert
ld
beqz
ld
li
beq
$a0,
$a0,
$t0,
$v0,
$t0,
li
li
li
jalr
$a0, 0x1c76cb0
$a1, 0x4276e8
$v0, 0x41a77e
$v0
ld
li
add
$a0, 8($a0)
$a1, 0x1
$a0, $a1
0($gp)
fail
0($a0)
0x62f3c0
$v0, succeed
; lade ‘a’
; Typ ‘int’
fail:
succeed:
; fail_at_node
Vermeidung: Typfehler „Falscher Typ“ (2/2)
AttoL
$a0
obj a = ...
...
a := a + 1;
⇒
Typdeskriptor
Wert
ld
beqz
ld
li
beq
$a0,
$a0,
$t0,
$v0,
$t0,
li
li
li
jalr
$a0, 0x1c76cb0
$a1, 0x4276e8
$v0, 0x41a77e
$v0
ld
li
add
$a0, 8($a0)
$a1, 0x1
$a0, $a1
0($gp)
fail
0($a0)
0x62f3c0
$v0, succeed
; lade ‘a’
; Typ ‘int’
fail:
succeed:
; fail_at_node
Vermeidung: Typfehler „Falscher Typ“ (2/2)
AttoL
$a0
obj a = ...
...
a := a + 1;
⇒
⇒
⇒
fail:
Typdeskriptor
Wert
ld
beqz
ld
li
beq
$a0,
$a0,
$t0,
$v0,
$t0,
li
li
li
jalr
$a0, 0x1c76cb0
$a1, 0x4276e8
$v0, 0x41a77e
$v0
ld
li
add
$a0, 8($a0)
$a1, 0x1
$a0, $a1
0($gp)
fail
0($a0)
0x62f3c0
$v0, succeed
succeed:
; lade ‘a’
; Typ ‘int’
; fail_at_node
Vermeidung: Typfehler „Falscher Typ“ (2/2)
AttoL
$a0
obj a = ...
...
a := a + 1;
fail:
⇒
⇒
⇒
⇒
succeed:
Typdeskriptor
Wert
ld
beqz
ld
li
beq
$a0,
$a0,
$t0,
$v0,
$t0,
li
li
li
jalr
$a0, 0x1c76cb0
$a1, 0x4276e8
$v0, 0x41a77e
$v0
ld
li
add
$a0, 8($a0)
$a1, 0x1
$a0, $a1
0($gp)
fail
0($a0)
0x62f3c0
$v0, succeed
; lade ‘a’
; Typ ‘int’
; fail_at_node
Vermeidung: Typfehler „Falscher Typ“ (2/2)
AttoL
$a0
obj a = ...
...
a := a + 1;
Typdeskriptor
$a0 + 8
ld
beqz
ld
li
beq
$a0,
$a0,
$t0,
$v0,
$t0,
li
li
li
jalr
$a0, 0x1c76cb0
$a1, 0x4276e8
$v0, 0x41a77e
$v0
ld
li
add
$a0, 8($a0)
$a1, 0x1
$a0, $a1
0($gp)
fail
0($a0)
0x62f3c0
$v0, succeed
Wert
; lade ‘a’
; Typ ‘int’
fail:
succeed:
⇒
⇒
⇒
; fail_at_node
Vermeidung: Typfehler „Arraygrenzen
überschritten“ (1/2)
Arrayschrankenprüfung:
I
Jeder Arrayzugriff wird geprüft, bevor er erlaubt wird
Java
int[] array = ...;
array[x] += 1;
wird vom Laufzeitsystem transformiert:
Java
int[] array = ...;
if (x < 0 || x >= array.length)
throw new ArrayOutOfBoundsException();
else
array[x] += 1;
Vermeidung: Typfehler „Arraygrenzen
überschritten“ (2/2)
Array-Repräsentierung in AttoVM (ähnlich in Java):
Typdeskriptor
Größe
Element #0
Element #1
...
Vermeidung: Typfehler „Arraygrenzen
überschritten“ (2/2)
Array-Repräsentierung in AttoVM (ähnlich in Java):
Objektreferenz
Typdeskriptor
Größe
Element #0
Element #1
...
Zusammenfassung: Starke Typisierung
I
Starke Typisierung erzwingt Typverträge
I
I
Typprüfungen können stattfinden:
I
I
I
Garantierte Erkennung von Typfehlern
statisch: Übersetzungszeit
dynamisch: Laufzeit
Vermeidung von Fehlern:
I
Falscher Speicherbereich:
I
I
Falscher Typ:
I
I
durch Verbot von Zeigerarithmetik/Zeigertypen
durch Typprüfung (statisch oder dynamisch)
Arraygrenzen überschritten:
I
durch Arrayschrankenprüfung (meist dynamisch)
Manuelle Speicherverwaltung
I
I
Speicher explizit freizugeben (free) ist fehleranfällig
Keine bekannte einfache Methode, Fehler im Allgemeinfall
zu finden
Können wir free automatisieren?
Automatische Speicherverwaltung
I
I
Programm darf Speicher allozieren
Laufzeitsystem dealloziert Speicher automatisch:
I
I
Speicher ist erreichbar gdw das Programm in der Zukunft
darauf zugreifen könnte
Sobald Speicher nicht mehr erreichbar ist, wird er
dealloziert (Speicherbereinigung)
Automatische Speicherverwaltung
I
I
Programm darf Speicher allozieren
Laufzeitsystem dealloziert Speicher automatisch:
I
I
Speicher ist erreichbar gdw das Programm in der Zukunft
darauf zugreifen könnte
Sobald Speicher nicht mehr erreichbar ist, wird er
dealloziert (Speicherbereinigung)
Wie erkennt das Laufzeitsystem, welcher Speicher
erreichbar ist?
Erkennung von erreichbarem Speicher
I
Immer erreichbar:
I
I
I
I
Registerinhalte
Lokale Variablen und Parameter auf dem Stapel
Globale Variablen (.static)
Der Rest ist nicht mehr erreichbar
Erkennung von erreichbarem Speicher
I
Immer erreichbar:
I
I
I
I
I
Registerinhalte
Lokale Variablen und Parameter auf dem Stapel
Globale Variablen (.static)
...und transitiver Abschluß aller erreichbaren Objekte!
Der Rest ist nicht mehr erreichbar
Automatische Speicherbereinigung
I
Vorteile:
I
I
I
Nachteile:
I
I
I
I
(Fast) keine Speicherfehler mehr
Code wird kompakter
Komplexeres Laufzeitsystem nötig
Automatische Deallozierung kann ineffizienter sein
Keine Zeigerarithmetik mehr erlaubt
Ansätze:
1.
2.
Referenzzählen (reference counting)
Verfolgung (tracing)
Referenzzählen (1/3)
(reference counting )
(z.B. Python)
I
Jedes Objekt weiß, wie oft es referenziert wird:
In Referenzzähler gespeichert
Referenzzählen (1/3)
(reference counting )
(z.B. Python)
I
I
Jedes Objekt weiß, wie oft es referenziert wird:
In Referenzzähler gespeichert
Neuer Zeiger auf Objekt:
1.
Referenzzähler++
Referenzzählen (1/3)
(reference counting )
(z.B. Python)
I
I
Jedes Objekt weiß, wie oft es referenziert wird:
In Referenzzähler gespeichert
Neuer Zeiger auf Objekt:
1.
I
Referenzzähler++
Zeiger zeigt nicht mehr auf Objekt:
1.
2.
Referenzzähler-Wenn Referenzzähler == 0: Deallozieren
Referenzzählen (2/3)
Python
⇒ a
b
a
b
=
=
=
=
Obj()
a
None
None
Referenzzählen (2/3)
Python
a
⇒ b
a
b
=
=
=
=
Obj()
a
None
None
a
Typdeskriptor
Ref: 1
...
Referenzzählen (2/3)
Python
a
b
⇒ a
b
=
=
=
=
Obj()
a
None
None
a
Typdeskriptor
b
Ref: 2
...
Referenzzählen (2/3)
Python
a
b
a
⇒ b
=
=
=
=
Obj()
a
None
None
a
Typdeskriptor
b
Ref: 1
...
Referenzzählen (2/3)
Python
a
b
a
b
⇒
=
=
=
=
Obj()
a
None
None
a
Typdeskriptor
b
Ref: 0
...
Referenzzählen (2/3)
Python
a
b
a
b
=
=
=
=
Obj()
a
None
None
a
Typdeskriptor
b
Ref: 0
...
Problem #1: Zusätzliche Speicherzugriffe bei Kopie von
Referenzen
Referenzzählen (3/3)
Python
⇒ a =
b =
a.x
b.x
a =
b =
Obj()
Obj()
= b
= a
None
None
Referenzzählen (3/3)
a
Python
a =
⇒ b =
a.x
b.x
a =
b =
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 1
x
Referenzzählen (3/3)
a
Python
a =
b =
⇒ a.x
b.x
a =
b =
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 1
x
b
Typdeskriptor
Ref: 1
x
Referenzzählen (3/3)
a
Python
a =
b =
a.x
⇒ b.x
a =
b =
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 1
x
b
Typdeskriptor
Ref: 2
x
Referenzzählen (3/3)
a
Python
a =
b =
a.x
b.x
⇒ a =
b =
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 2
x
b
Typdeskriptor
Ref: 2
x
Referenzzählen (3/3)
a
Python
a =
b =
a.x
b.x
a =
⇒ b =
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 1
x
b
Typdeskriptor
Ref: 2
x
Referenzzählen (3/3)
a
Python
a =
b =
a.x
b.x
a =
b =
⇒
Obj()
Obj()
= b
= a
None
None
Typdeskriptor
Ref: 1
x
b
Typdeskriptor
Ref: 1
x
Problem #2: Referenzzyklen verhindern
Speicherfreigabe
Verfolgende Speicherbereinigung
(tracing garbage collection)
Wird „gelegentlich“ aktiviert:
1. Markiere alle erreichbaren Objekte
2. Finde alle nicht markierten (= nicht erreichbaren, ‘toten’)
Objekte und dealloziere sie
Verfolgende Speicherbereinigung
(tracing garbage collection)
Wird „gelegentlich“ aktiviert:
1. Markiere alle erreichbaren Objekte
I
Immer erreichbar: Wurzelmenge
I
I
I
Registerinhalte
Lokale Variablen und Parameter auf dem Stapel
Globale Variablen (Datensegment)
2. Finde alle nicht markierten (= nicht erreichbaren, ‘toten’)
Objekte und dealloziere sie
Verfolgende Speicherbereinigung
(tracing garbage collection)
Wird „gelegentlich“ aktiviert:
1. Markiere alle erreichbaren Objekte
I
Immer erreichbar: Wurzelmenge
I
I
I
I
Registerinhalte
Lokale Variablen und Parameter auf dem Stapel
Globale Variablen (Datensegment)
Beginnend mit Wurzelmenge, berechne transitiven
Abschluß:
I
I
I
Untersuche Objektstruktur durch
Laufzeittypinformationen
Finde Zeiger auf Kindobjekte, markiere
Iteriere/Rekurriere solange möglich
2. Finde alle nicht markierten (= nicht erreichbaren, ‘toten’)
Objekte und dealloziere sie
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
Wir markieren transitiv . . .
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
I
Wir markieren transitiv . . .
Nicht markierten Objekte sind nicht erreichbar
Beispiel: Mark & Sweep
Ein mögliches Verfahren zur automatischen Speicherbereinigung
Wurzelmenge
0
5
1
2
9
3
6
4
7
8
I
I
I
Wir markieren transitiv . . .
Nicht markierten Objekte sind nicht erreichbar
7,8,9 können dealloziert werden
Vergleich der Speicherbereinigungsformen
I
Referenzzählen
⊕
I
Zeitbedarf gut kontrollierbar
Zusätzliche Speicherzugriffe bei jeder Kopie einer Referenz
Zyklen verhindern Speicherfreigabe
Verfolgende Speicherbereinigung
⊕
Vollständiges Freigeben von Speicher
Benötigt eines der Folgenden:
I
I
I
Pausen während der Ausführung (Mark & Sweep)
Zusätzlichen Speicher (andere verfolgende Verfahren)
Zusätzliche Speicherzugriffe (nebenläufige Verfahren)
Zusammenfassung: Automatische
Speicherverwaltung
I
Speicherfehler nehmen folgende Formen an:
I
I
I
Speicherfehler können durch Automatische
Speicherverwaltung vermieden werden:
I
I
I
I
Speicherlecks
Speicherkorruption
Sprache einschränken
Explizites free verbieten/ignorieren
Stattdessen implizit/automatisch deallozieren
(Automatische Speicherbereinigung)
Automatische Speicherbereinigung:
I
I
I
Referenzzählen
Konstante Ausführungszeit, aber nicht ganz vollständig
Mark & Sweep
Gelegentliche Unterbrechungen, aber vollständig
Andere Formen der Verfolgenden Automatischen
Speicherverwaltung
Implementierung der Verfolgenden
Speicherbereinigung
I
Auslöser:
I
I
Wurzelmenge:
I
I
I
I
I
Üblicherweise: Fehlgeschlagene Allozierung (‘kein Platz
mehr frei’)
Statische Variablen
Register
Stapelinhalte
Verfolgen von Referenzen: Felder von ‘lebenden’
Objekten
Freigabe des Speichers von ‘toten’ Objekten
Wurzelmenge finden
I
I
Schlüsselfrage: Welche Speicherstellen zeigen auf
Objekte?
Statische Variablen:
I
I
I
Typ verrät uns, was wir zu tun haben
Objekttyp? → Teil der Wurzelmenge
Anonsten (int etc.): ignorieren
Wurzelmenge finden
I
I
Schlüsselfrage: Welche Speicherstellen zeigen auf
Objekte?
Statische Variablen:
I
I
I
I
Register:
I
I
Typ verrät uns, was wir zu tun haben
Objekttyp? → Teil der Wurzelmenge
Anonsten (int etc.): ignorieren
Variieren je nach Programmstelle
Stapelinhalte:
I
Variieren je nach Programmstelle
Stapel- und Registerinhalte
li
sw
li
jal
move
sw
li
jal
$t0, 3
$t0, -8($fp)
$a0, 8
allocate
$t0, $v0
$v0, -8($fp)
$a0, 16
allocate
; int
; Bytes
; Objekt
; Bytes
Stapel- und Registerinhalte
li
sw
li
jal
move
sw
li
jal
I
In Wurzelmenge:
I
I
...
Nicht in
Wurzelmenge:
I
I
I
$t0
mem[$fp-8]
...
$t0, 3
$t0, -8($fp)
$a0, 8
allocate
$t0, $v0
$v0, -8($fp)
$a0, 16
allocate
; int
; Bytes
; Objekt
; Bytes
Stapel- und Registerinhalte
li
sw
li
jal
move
sw
li
jal
I
In Wurzelmenge:
I
I
$t0, 3
$t0, -8($fp)
$a0, 8
allocate
$t0, $v0
$v0, -8($fp)
$a0, 16
allocate
; int
; Bytes
; Objekt
; Bytes
I
I
...
I
I
Nicht in
Wurzelmenge:
I
I
I
$t0
mem[$fp-8]
...
In Wurzelmenge:
I
I
$t0
$v0
mem[$fp-8]
...
Nicht in
Wurzelmenge:
I
...
Stapel- und Registerinhalte
li
sw
li
jal
move
sw
li
jal
I
In Wurzelmenge:
I
I
$t0, 3
$t0, -8($fp)
$a0, 8
allocate
$t0, $v0
$v0, -8($fp)
$a0, 16
allocate
; int
; Bytes
; Objekt
; Bytes
I
I
...
I
I
Nicht in
Wurzelmenge:
I
I
I
$t0
mem[$fp-8]
...
In Wurzelmenge:
I
I
$t0
$v0
mem[$fp-8]
...
Nicht in
Wurzelmenge:
I
...
Wurzelmenge variiert innerhalb von Subroutinen
Stapelkarten
I
Wenn allocate die Speicherbereinigung
anstößt:
Parameter 1
Parameter 0
I
altes $ra
I
alter $fp
Lokale Var. 0
Lokale Var. 1
Lokale Var. 2
Lokale Var. 3
Parameter 1
Parameter 0
$ra
Stapel: Was ist meine
Rücksprungadresse aus $ra ?
Rücksprungadresse beschreibt
darüberliegenden Aktivierungseintrag:
I
I
Anzahl Elemente auf dem Stapel
Typen der Elemente
Stapelkarten
I
Parameter 1
Parameter 0
ref
ref
altes $ra
Wenn allocate die Speicherbereinigung
anstößt:
I
I
alter $fp
Lokale Var. 0
Lokale Var. 1
Lokale Var. 2
Lokale Var. 3
Parameter 1
Parameter 0
ref
w
ref
w
$ra
Stapelkarte
Stapel: Was ist meine
Rücksprungadresse aus $ra ?
Rücksprungadresse beschreibt
darüberliegenden Aktivierungseintrag:
I
I
I
Anzahl Elemente auf dem Stapel
Typen der Elemente
Komprimiert in Stapelkarte
Stapelkarten
I
Parameter 1
Parameter 0
ref
ref
Wenn allocate die Speicherbereinigung
anstößt:
I
altes $ra
I
alter $fp
Lokale Var. 0
Lokale Var. 1
Lokale Var. 2
Lokale Var. 3
Parameter 1
Parameter 0
ref
w
ref
w
$ra
Stapelkarte
Stapel: Was ist meine
Rücksprungadresse aus $ra ?
Rücksprungadresse beschreibt
darüberliegenden Aktivierungseintrag:
I
I
I
I
Anzahl Elemente auf dem Stapel
Typen der Elemente
Komprimiert in Stapelkarte
Übersetzer baut Abbildung von
Rücksprungadressen auf Stapelkarten
Stapelkarten
I
Parameter 1
Parameter 0
ref
ref
Wenn allocate die Speicherbereinigung
anstößt:
I
altes $ra
I
alter $fp
Lokale Var. 0
Lokale Var. 1
Lokale Var. 2
Lokale Var. 3
Parameter 1
Parameter 0
ref
w
ref
w
$ra
Stapelkarte
Stapel: Was ist meine
Rücksprungadresse aus $ra ?
Rücksprungadresse beschreibt
darüberliegenden Aktivierungseintrag:
I
I
I
I
I
Anzahl Elemente auf dem Stapel
Typen der Elemente
Komprimiert in Stapelkarte
Übersetzer baut Abbildung von
Rücksprungadressen auf Stapelkarten
Rekursion: Betrachte altes $ra vom
Stapel genauso, bis wir am Ende des
Stapels ankommen
Registerkarten
I
Register können analog Registerkarten verwenden
Registerkarten
I
I
Register können analog Registerkarten verwenden
Alternative: Register-Partitionierung
I
I
Register $r0, $r2, $r4 . . . speichern nur Referenzen
Register $r1, $r3, $r5 . . . speichern nur nicht-Referenzen
Registerkarten
I
I
Register können analog Registerkarten verwenden
Alternative: Register-Partitionierung
I
I
I
Register $r0, $r2, $r4 . . . speichern nur Referenzen
Register $r1, $r3, $r5 . . . speichern nur nicht-Referenzen
Weitere Alternativen:
I
I
I
Universelles Verpacken: Alle Register sind 0 oder zeigen
auf Referenz
Tagging: Wir reservieren ein Bit im Register, um zwischen
Referenzen/Nicht-Referenzen zu unterscheiden
Kombinationen der obigen Konzepte
Referenzen Verfolgen
C#
public class Node {
Node next; // verfolgen
int value; // nicht verfolgen
}
I
I
Benötigen Informationen darüber, welche Felder
Referenzen sind
Geeigneter Ablageort:
I
I
Typdeskriptor
Ein Bit pro Feld
Objekte Verfolgen
I
Mark & Sweep:
I
I
I
I
Merken uns ‘Lebendigkeit’ (liveness) als Bit im Objekt
Drehen das Bit während Verfolgung um
Nach der Verfolgung: Verwerfen alle Objekte mit
‘falschem’ Wert im Bit
Einige fortgeschrittene Algorithmen verwenden alternative
Ansätze
Wurzelmenge: Register + Stapel + Statische Variablen
0
5
1
2
3
4
9
6
7
8
Speicher-Recycling
I
Nun haben wir alle ‘lebenden’ Objekte markiert. . .
Speicher-Recycling
I
I
Nun haben wir alle ‘lebenden’ Objekte markiert. . .
. . . aber wie finden wir den Speicher der ‘toten’ Objekte?
I
I
Verschiedene Ansätze möglich
Benötigen meist Datenstruktur mit allen Objekten
I
I
Verkettete Liste zwischen allen Objekten
‘Katalog’: Bits, die sich merken, welche (64-Bit oder
größeren) Speicherbereiche belegt sind
Speicher-Recycling
I
I
Nun haben wir alle ‘lebenden’ Objekte markiert. . .
. . . aber wie finden wir den Speicher der ‘toten’ Objekte?
I
I
Verschiedene Ansätze möglich
Benötigen meist Datenstruktur mit allen Objekten
I
I
I
Verkettete Liste zwischen allen Objekten
‘Katalog’: Bits, die sich merken, welche (64-Bit oder
größeren) Speicherbereiche belegt sind
Spätere Objektallozierung muß den freigegebenen
Speicher nutzen können
Kopierende Speicherbereinigung (z.B. AttoVM 0.4)
I
Verwendet zwei Ablagespeicher:
I
I
Speicher A wird aktiv genutzt
Speicher B gilt als ‘leer’
Speicher A
Speicher B
Kopierende Speicherbereinigung (z.B. AttoVM 0.4)
I
Verwendet zwei Ablagespeicher:
I
I
I
Speicher A wird aktiv genutzt
Speicher B gilt als ‘leer’
Wenn A voll ist:
I
I
Kopiere alle lebendigen Objekte nach B
Korrigiere Zeiger
Speicher A
Speicher B
Kopierende Speicherbereinigung (z.B. AttoVM 0.4)
I
Verwendet zwei Ablagespeicher:
I
I
I
Speicher A wird aktiv genutzt
Speicher B gilt als ‘leer’
Wenn A voll ist:
I
I
I
Kopiere alle lebendigen Objekte nach B
Korrigiere Zeiger
Tausche die Namen der Speicher aus
Speicher B
Speicher A
Kopierende Speicherbereinigung (z.B. AttoVM 0.4)
I
Verwendet zwei Ablagespeicher:
I
I
I
Speicher A wird aktiv genutzt
Speicher B gilt als ‘leer’
Wenn A voll ist:
I
I
I
I
Kopiere alle lebendigen Objekte nach B
Korrigiere Zeiger
Tausche die Namen der Speicher aus
Neuer ‘B’-Speicher gilt wieder als ‘leer’
Speicher B
Speicher A
Zusammenfassung
I
Wurzelmenge finden:
I
I
I
I
Statische Variablen: Statische Variablenkarte
Variablen auf dem Stapel: Stapelkarten
Register: Registerkarten, Registerpartitionierung, . . .
Verfolgung:
I
I
I
Beginnen mit Wurzelmenge
Annahme: Alle Objekte sind ‘tot’
Jedes referenzierte Objekt fragt Typdeskriptor, welche
I
I
Verfolge alle Kinder, markiere als ‘lebendig’
Sobald Verfolgung fertig ist:
I
Finden alle ‘toten’ Objekte zur Wiederverwertung
Herunterladen