Effiziente Java Programmierung Seminar „Implementierung moderner virtueller Maschinen am Beispiel von Java“ SS 2009 von Reinhard Klaus Losse 20. Mai 2009 Gliederung ● Definition Effizienz ● Werkzeuge zum Messen von Effizienz ● Beispiel aus der Praxis ● Mögliche Bösewichte ● Tests 2 1. Definition Effizienz ● Ein Programm ist effizient, wenn: ● seine Rechengeschwindigkeit niedrig ist, ● sein Speicherverbrauch niedrig ist und ● ● die Geschwindigkeit von Ein-/ Ausgabeoperationen niedrig ist. hier: Effizienz auf der Ebene der JVM und des Bytecodes 3 2. Messwerkzeuge ● ● ● ● Windows-Task-Manager äquivalente Tools in Unix/ Linux: top, ps, netstat, vmstat, iostat, sar (System Accounting Reports), truss, strace Profiler, z. B. Hprof Methoden java.lang.System.currentTimeMillis() und java.lang.System.nanoTime() (seit Java 5) 4 Profiler: Motivation ● geben z. B. Auskunft darüber: ● wie viele Objekte vom Typ X existieren, ● wie viel Speicher sie verbrauchen, ● wie Zeit ein Programm in Methode y verbringt und ● wie häufig diese Methode aufgerufen wurde 5 Geschichte von Hprof ● ● war in den Java-Versionen 1.2 bis 5 als Teil des experimentellen Java Virtual Machine Profiler Interface (JVMPI) enthalten wurde in Java 5 auf dem neuen Java Virtual Machine Tool Interface (JVM TI) implementiert, das das JVMPI ersetzte 6 Aufruf von Hprof ● bis Java 5: ● ● ab Java 5: ● ● java -Xrunhprof[:options] ToBeProfiledClass java -agentlib:hprof[=options] ToBeProfiledClass Liste aller Optionen: ● java -agentlib:hprof=help 7 Aufruf von Hprof ● ● Hprof speichert die Ergebnisse des Profilings in eine Datei jede Datei enthält: ● Liste aller Threads ● Liste von nummerierten Stacktraces 8 Aufruf von Hprof ● jede Datei kann darüber hinaus enthalten: ● ● ● ● Heap-Dump Liste aller Objekttypen geordnet nach dem Speicherverbrauch (Sites) Liste aller Methoden geordnet nach zeitlichen Stichproben (CPU Samples) Liste aller Methoden geordnet nach dem Zeitverbrauch (CPU Time) 9 Beispiel aus der Praxis ● String-Variante String halloWelt = “Hallo Welt “; String weite = “weite “; String hallo = halloWelt.substring(0, 7); String welt = halloWelt.substring(7); String halloWeiteWelt = hallo.concat(weite).concat(welt); ● 4 Objekte 10 3. Beispiel aus der Praxis ● StringBuffer-Variante String halloWelt = “Hallo Welt “; String weite = “weite “; StringBuffer stringBuffer = new StringBuffer(halloWelt); stringBuffer.insert(7, weite); String halloWeiteWelt = stringBuffer.toString(); ● 3 Objekte 11 Beispiel aus der Praxis ● String-Variante mit + String halloWelt = “Hallo Welt “; String weite = “weite “; String hallo = halloWelt.substring(0, 7); String welt = halloWelt.substring(7); String halloWeiteWelt = hallo + weite + welt; ● 4 Objekte 12 Beispiel aus der Praxis ● ● Messergebnisse: ● String: 6628217 ns 100% ● StringBuffer: 7568280 ns 114% ● String mit +: 9880585 ns 149% Einfügen von Strings erfolgt in Bezug auf den Zeitbedarf am besten mit der Klasse String 13 4. Mögliche Bösewichte ● Objekt mit vielen Instanzenvariablen ● Arrays mit vielen Elementen ● viele Methodenaufrufe ● häufige Arrayzugriffe ● häufige Feldzugriffe 14 Schleifen ● for (int i = 0; i < list.size(); i++) { Object o = list.get(i); ... } ● for (int i = 0, n=list.size(); i < n; i++) { Object o = list.get(i); ... } 15 Arrays ● private int arrayAccessInLoop() { int[] array = new int[1]; for (int i=0; i < 20000000; i++) array[0] += i; return array[0]; } 16 5. Tests ● ● Testsystem: ● Windows XP ● 1 GB Arbeitsspeicher ● Pentium M 1,86 Ghz ● Java 6 Durchführung der Tests ● 1.000-fache Messung der Zeit über nanoTime() 17 Objekt- und Arrayerzeugung ● ● Erzeugung eines Objekts mit nur einer intInstanzenvariable Erzeugung eines Objekts mit 18 Instanzenvariablen, 9 int-Variablen und 9 String-Variablen ● Erzeugung eines Arrays mit 0 Elementen ● Erzeugung eines Arrays mit 5 Elementen 18 Objekt- und Arrayerzeugung ● Messergebnisse: ● Objekt mit wenigen Elementen: 1955 ns 100% ● Objekt mit vielen Elementen: 2793 ns 143% ● Array mit 0 Elementen: 1676 ns 100% ● Array mit 5 Elementen: 1955 ns 117% 19 Methodenaufrufe ● 100.000-facher Aufrufe folgender Methode: <Modifier> int myMethod(int j) { return j+1; } ● Modifier: ● public ● public static ● private ● private static 20 Methodenaufrufe ● ● Interface: ● Klasse implementiert ein Interface ● Interface enthält die Methode myMethod Oberklasse: ● Klasse erbt von Oberklasse ● 1. Variante: Klasse überschreibt myMethod ● ● 2. Variante: Klasse implementiert die abstrakte Methode myMethod 100.000-facher Aufruf der Methode myMethod 21 Methodenaufrufe ● letzte Variante: inline ● ● for (int i = 0; i < 100000; i++) j = j + 1; direkte Ausführung der Operation statt Methodenaufruf 22 Methodenaufrufe ● Messergebnisse mit inline: ● public: 15029284 ns 279% ● public static: 13376281 ns 249% ● private: 19297983 ns 359% ● private static: 13376281 ns 249% ● abstrakt: 19432637 ns 361% ● überschreiben: 15029284 ns 279% ● Interface: 18030224 ns 335% ● inline: 5377499 ns 100% 23 Methodenaufrufe ● Messergebnisse ohne inline: ● public: 15029284 ns 112% ● public static: 13376281 ns 100% ● private: 19297983 ns 144% ● private static: 13376281 ns 100% ● abstrakt: 19432637 ns 145% ● überschreiben: 15029284 ns 112% ● Interface: 135% 18030224 ns 24 Array- und Feldzugriff ● Zugriff auf das Arrayelement Nr. 10: int[] array = new int[100]; int j = 0; array[10] = 1; for (int i = 0; i < 100000; i++) j = j + array[10]; 25 Array- und Feldzugriff ● ● 100.000-facher Zugriff auf ein Feld einer anderen Klasse: ● public ● public static 100.000-facher Zugriff auf eine lokale Variable 26 Array- und Feldzugriff ● Messergebnisse: ● Arrayzugriff: 6403607 ns 100% ● Feldzugriff (public): 6857296 ns 107% ● Feldzugriff (public static): 6469537 ns 101% ● Feldzugriff (lokal): 6858413 ns 107% 27 Fazit ● ● ● ● ● Erzeugung größerer Objekte und Arrays dauert länger als die kleinerer inline bei Methodenaufrufen mit Abstand am schnellsten, gefolgt von statischen Methodenaufrufen private und abstrakt bei Methodenaufrufen am langsamsten, gefolgt von Interface Arrayzugriffe und statische Feldzugriff schneller als sonstige Feldzugriffe aber: JIT-Compiler bringt alles durcheinander 28 Quellen ● ● ● Performant Java programmieren, Hendrik Schreiber, Addison Wesley 2002 HPROF: A Heap/CPU Profiling Tool in J2SE 5.0, http://java.sun.com/developer/technicalArticles/Programming/HPROF.html, abgerufen am 22.04.2009 JDK 6 Documentation 29