JIT Compiling - Leibniz Universität Hannover

Transcription

JIT Compiling - Leibniz Universität Hannover
JIT Compiling
hallo
Implementierung moderner virtueller Maschinen am Beispiel von Java
Sommersemester 2009
Nicole Ullmann
Leibniz Universität Hannover
1
Gliederung
●
Motivation
●
Just­In­Time Compiling
●
Java HotSpot Virtual Machine
●
HotSpot Konzept
●
Java Hotspot Client Compiler
●
Kompilierungsphasen
●
Demo und Fazit
2
Motivation
●
Vorteil: Java bzw. Bytecode plattformunabhägig
●
Nachteil: Bytecode muss interpretiert werden
–
–
●
Dispatch Loop
●
Befehl laden
●
Interpretieren
●
Ausführen
Alles zur Laufzeit
Fazit: Performanceverlust
3
Just­in­Time Compiling
●
●
Übersetzung von Bytecode in Maschinencode zur Laufzeit („just in time“)
Gegenteil: Ahead­of­Time (AOT) Compiling
–
●
Kompilierung vor Ausführung des Programms
Wichtig: Kompilierunggeschwindigkeit
–
Kompilierung in „User Time“
–
JIT Compiler müssen schnell arbeiten
4
Java HotSpot Virtual Machine
●
„High­performance“ VM (laut Sun)
●
Verbesserungen im Bereich:
–
Runtime: verbesserte Fehlermeldungen
–
Garbage collection: größere Heaps
–
Thread Synchronisation („ultra fast“)
–
Memory Model („handleless objects“)
–
JIT­Compiling
5
Java Hotspot Compilers
●
2 alternative JIT­Compiler verfügbar
●
●
Unterschiedliche Design­
Entscheidungen
Nutzen dieselbe Virtual Machine
Quelle: http://java.sun.com/products/hotspot/docs/whitepaper/Java_HotSpot_WP_Final_4_30_01.html 6
Client Compiler
●
Optimiert für interaktive Anwendungen, z.B. GUIs
●
Ziel: Hohe Kompilierungsgeschwindigkeit
●
Einfache Analysen und Optimierungen
●
Schnelle Startup­Phase
●
Kleiner Memory Footprint
7
Server Compiler
●
Optimiert für typische Server­Anwedungen
●
Ziel: Hohe Maschinencode­Qualität
●
Hohe Ausführungsgeschwindigkeit
●
Vollständige Optimierung
●
Langsame Startup­Phase
●
Größerer Memory Footprint
8
Das Hotspot Konzept
●
Beobachtung: –
●
Programme verbringen ca. 80 ­ 90% der Rechenzeit damit, nur 10 – 20% ihres Codes auszuführen
Schlußfolgerung: –
Nur performance­kritische Methoden („hot spots“) just­in­time kompilieren
–
Hotspots:
●
Häufig aufgerufene Methoden
●
Methoden mit lang laufenden Schleifen
9
Hotspot Detection (1)
●
●
Bytecode zunächst nur interpretiert und analysiert
Erkennung der Hotspots anhand zweier Schwellwerte:
–
Aufrufhäufigkeit einer Methode (method_entry_counter) –
Anzahl von Schleifendurchläufen in einer Methode (backedge_counter);
10
Hotspot Detection (2)
●
Methode wird kompiliert, falls method_entry_counter > CompileThreshold
●
●
Grundeinstellung CompileThreshold: –
1500 im Client­Compiler
–
10000 im Server­Compiler
–
VM­Option: -XX:CompileThreshold=<WERT>
Beim nächsten Methodenaufruf wird die kompilierte Methode verwendet
11
Hotspot Detection (3)
●
Methode wird kompiliert, falls backedge_counter > CompileThreshold
●
Wechsel zur kompilierten Methode während der Methoden­Interpretation
●
On­Stack­Replacement (OSR)
●
Kompilierte Methode enthält OSR entry point
12
On­Stack­Replacement (OSR)
●
Beispiel*
Stack
nach OSR
Stack
vor OSR
m1
compiled
frame
m1 interpreted
frame
void m1() {
...
while(i < n­1){
mo frame
//hier OSR
...
dead m1
frame
mo frame
}
...
}
Beispiel aus: Client Compiler for the Java HotSpotTM Virtual Machine, Tom Rodriguez et al.
13
Java Hotspot Client Compiler
●
Kompilierung einer Methode in 3 Phasen aufgeteilt:
1.Phase: High­Level Intermediate Representation 2.Phase: Low­Level Intermediate Representation 3.Phase: Maschinencode Generierung
(HIR) Generierung und gleichzeitige bzw. (LIR) Generierung und anschließende Register anschließende Optimierungen
Allocation
Struktur des Java Hotspot Client Compilers*
*Quelle: Design of the Java Hotspot Client Compiler, Kotzmann et al. 2008
14
High­Level Intermediate Representation (HIR)
●
Plattformunabhängiger, graph­basierter Zwischencode
●
Repräsentiert Methoden als Kontrollflußgraph
●
Knoten sind Basis Blöcke
●
Implizite Operanden
●
In Static Single Assignment (SSA) Form
–
Beispiel
y := 1
y := 2
x := y
y1 := 1
y2 := 2
x1 := y2
15
HIR Generierung
●
1 Phase: –
●
Basis Blöcke bestimmen
2 Phase: –
Bytecode­Instruktionen in HIR­Instruktionen umwandeln
–
Basis Blöcke verlinken (Kontrollfluß bilden)
16
HIR­Generierung Beispiel*
0 iconst_1
1 istore_1
2 iconst_1
3 istore_2
4 iload_2
5 iload_0
6 if_icmpgt 19
9 iload_1
10 iload_2
11 imul
12 istore_1
13 iinc 2 1
16 goto 4
19 iload_1
20 ireturn
*Beispiel aus: SSA Form for the Java HotSpotTM Client Compiler, Christian Wimmer 2009
17
HIR Generierung Beispiel (Forts.)
Phase 1: Basis Blöcke bestimmen
0 iconst_1
1 istore_1
2 iconst_1
3 istore_2
4 iload_2
5 iload_0
6 if_icmpgt 19
9 iload_1
10 iload_2
11 imul
12 istore_1
13 iinc 2 1
16 goto 4
19 iload_1
20 ireturn
0 1 2 3 iconst_1
istore_1
iconst_1
istore_2
4 iload_2
5 iload_0
6 if_icmpgt 19
9 iload_1
10 iload_2
11 imul
12 istore_1
13 iinc 2 1
16 goto 4
19 iload_1
20 ireturn
18
HIR Generierung Beispiel (Forts.)
Phase 2: HIR­Instruktionen erzeugen & Kontrollfluß bilden
0 1 2 3 iconst_1
istore_1
iconst_1
istore_2
4 iload_2
5 iload_0
6 if_icmpgt 19
9 iload_1
10 iload_2
11 imul
12 istore_1
13 iinc 2 1
16 goto 4
19 iload_1
20 ireturn
i0 parameter n
i1 1
2 goto B1
B0
i3 phi[i0,i3]
B1
i4 phi[i1,i7]
i5 phi[i1,i9]
6 i5 > i3 ? B3 : B2
i7 i4 * i5
i8 1
i9 i5 + i8
10 goto B1
11 return i4
B2
B3
19
Lokale Optimierungen auf HIR
●
Lokale Optimierungen während der HIR Generierung
●
Beziehen sich auf einzelne Basis Blöcke
●
Konstantenfaltung („constant folding“)
–
Beispiel:
double pi = 3.14159
double u = 2 * pi * r
}
double u = 6.2830 * r
20
Lokale Optimierungen auf HIR (2)
●
Entfernen gemeinsamer Teilausdrücke mittels „local value numbering“
–
Beispiel:
a = b + c;
d = b;
b = a;
e = d + c;
a
b → #1 #3
c → #2
b + c → #1 + #2 → #3
a → #3
d → #1
d + c → #1 + #2 → #3
e → #3
21
Lokale Optimierungen auf HIR (3)
●
Method­Inlining
–
●
●
Methoden kleiner als 35 Byte werden „ge­inlined“
Problem: Java ist objekt­orientiert und dynamisch
–
Methoden­Aufrufe „virtuell“
–
„virtuell“ := potenziell polymorph
Folge: Inlining „falscher“ Methoden
22
Method­Inlining: Problem­Beispiel*
class VirtualCall {
public void foo() {
A a = create();
a.bar();
}
}
public A create() {
if(...){ return new A();}
else {return new B();}
}
class A {
void bar() { ... }
}
class B extends A {
void bar() { ... }
}
●
Klasse B wird nachgeladen
●
Aktive (kompilierte) Methode foo() wird ungültig
●
Deoptimierung *Beispiel aus: Design of the Java Hotspot Client Compiler, Kotzmann et al. 2008
23
Deoptimierung
●
Ausführung des Maschinencodes stoppen
●
Compileroptimierungen rückgängig machen
●
Methodenausführung im Interpreter fortsetzen
Stack
comp. foo() frame [inlined bar()]
bar() interpreted
mo frame
mo frame
foo() interpreted
Beispiel aus: Client Compiler for the Java HotSpotTM Virtual Machine, Tom Rodriguez et al.
24
Globale Optimierungen auf HIR
●
●
Globale Optimierungen nach der HIR Generierung
Beziehen sich auf gesamten HIR­Graphen
–
Entfernen von Laufzeitüberprüfungen („null check elimination“)
–
Entfernen gemeinsamer Teilausdrücke mittels „global value numbering“
–
Entfernen unerreichbarer Zweige („conditional expression elimination“)
25
Low­Level Intermediate Representation (LIR)
●
●
●
●
Aus HIR­Graph generiert
Plattformabhängiger, maschinen­naher Zwischencode
Einige high­level Instruktionen (z.B. Typ­
Überprüfung)
Explizite Operanden in virtuellen Registern
–
Optimal für anschließende Register­Allokation
26
Linear Scan Register Allocation
●
●
Aufgabe: virtuelle Register durch physische Register der Ziel­Architektur ersetzen
Beispiel* mit 2 physischen Registern r1 und r2
v1
v2
v3
v4
v5
(1) v1 = 10
(2) v2 = 20
(3) v3 = v1 + v2
(4) v4 = v2 + v3
(5) v1 = v3 + v4
(6) v5 = v4 + v1
(7) return v1 + v5
*Beispiel aus: Linear Scan Register Allocation for the Java HotSpot Client Compiler, Christian Wimmer 2004
27
Linear Scan Register Allocation (2)
●
Liste aus Live Intervallen konstruieren
–
[v1, v2, v3, v4, v5]
v1 v2
mem1
r1 r2
v3
r1
v4
r2
(1)
v5
r1
Registerzuordnung:
(2)
v1 → mem1
v2 → r2
v3 → r1
v4 → r2
v5 → r1
(3)
(4)
(5)
(6)
(7)
v1
v2 v2
v4
v3
active list: v5
28
Linear Scan Register Allocation (2)
●
Code nach der Register­Allokation
Registerzuordnung:
v1 → mem1
v2 → r2
v3 → r1
v4 → r2
v5 → r1
vorher:
nachher:
(1) v1 = 10
mem1 = 10
(2) v2 = 20
r2 = 20
(3) v3 = v1 + v2
r1 = mem1 + r2
(4) v4 = v2 + v3
r2 = r2 + r1
(5) v1 = v3 + v4
mem1 = r1 + r2
(6) v5 = v4 + v1
r1 = r2 + mem1
(7) return v1 + v5
return mem1 + r1
29
Maschinencode Erzeugung
●
Aus LIR­Graph generiert
●
Vorgefertigte Maschinencode­Schablonen
–
●
1­2 Assemblerbefehle pro LIR­Instruktion
–
●
aus Architecture­Description Files
high­level Instruktionen benötigen mehrere
Native Method Object
–
Maschinencode
–
Meta Daten für Deoptimierung und GC
30
Meta Daten
●
●
Debug Information
–
Mapping zwischen kompilierten Code und Interpreter­Status
–
Speichert Status aller lokalen Variablen und Operanden­Stack
Oop Maps (Ordinary Object Pointers):
–
Informationen über Pointer zu Heap­Objekten
31
Demo
●
Compile­Threshold ändern
–
●
JIT­Compiler ausschalten: –
●
­Xint
JIT­Compiler Zeit: –
●
­XX:CompileThreshold=<WERT>
­XX:+CITime
JIT­Compiler Ausgabe
–
­XX:+PrintCompilation
32
Fazit
●
Bytecode­Interpretation ist langsam
●
JIT schafft Abhilfe, muss aber schnell sein
●
Hotspot Client Compiler vs. Server Compiler
●
Nur Hotspots kompilieren
●
Struktur des Client Compiler
●
Kompilierung in 3 Phasen
●
Demo
33