77. XPUG Frankfurt 2011‐11‐09 Algorithmische Performanceoptimierung 2011-11-09 Martin Müller-Rohde SCOOP Software GmbH 1 Inhalt • Ich wollte eigentlich einen BrainDump zu Performance-Optimierung allgemein machen • Das kam mir aber alles so ungeordnet vor • Beim ordnen fand ich – Langweilige Teile – Übliche Teile – Ein spannendes, irgendwie neuen Teil: Optimierung durch Algorithmen 2 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 1 77. XPUG Frankfurt 2011‐11‐09 Inhalt • • • • Warum Performance-Optimierung? Wie geht man systematisch vor? Versuch eine Ordnung zu finden Algorithmische Performance-Optimierung – Pattern – Unterschiede zu den anderen Teilen 3 Warum Performance Optimierung? • Die erste Implementierung ist selten auf Anhieb schnell – Ich bin meist schon froh, wenn die Fehler beseitigt sind • Die Erwartung der Anwender/Kunden hat sich deutlich geändert – nicht mehr „soll der Kunde doch froh sein, dass er das nicht mehr manuell machen muss“ – sondern klare Forderungen • Bedienung wie ein iPhone • viiiieeel schneller als bisheriges System • Ausnutzung der besseren Hardware 4 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 2 77. XPUG Frankfurt 2011‐11‐09 Systematik 1. Eine funktionierende Implementierung (es gibt viele, eine reicht) Funktionalität abgesichert durch gute Tests 2. • • • 3. Zu optimierender Use-Case kann möglichst einfach, isoliert durchgeführt und beliebig oft wiederholt werden (JUnit, Selenium, …) Zeitmessung vor der Optimierung Eine einzige Optimierung probieren und anhand Zeitmessung überprüfen 4. 5. • • 6. Auf fachliche Richtigkeit testen Nicht die inneren Details testen Nicht den Algorithmus testen bei Verbesserung behalten, sonst verwerfen mehrere Optimierungen könnten sich gegenseitig aufheben, oder verschlechtern – welche war gut, welche nicht? GOTO 5 5 Versuch einer Ordnung • Wo steckt Optimierungspotenzial? Java Database Algorithmen 6 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 3 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Java • Wie bei jeder Programmiersprache gibt es eine Lernkurve (die wir vermutlich alle mal durchgemacht haben) – String+= StringBuffer.append() StringBuilder.append() 1/20 ½ – logger.debug(a+“:“+b) if (myLogger.isDebugEnabled()) … – new Integer(x) Integer.valueOf(x) ½ – java.util.Vector java.util.LinkedList java.util.ArrayList – HashTable HashMap – InputStream BufferedInputStream • LANGWEiLiG! statische Code-Analyse – Verwendung•eines SessionPools (grep, CheckStyle, PMD, Bei Java-Entwicklern beliebt, denn man bleibt ja in der wohlvertrauten Umgebung FindBugs,…) • mechanisch durchführbar • geringes Risiko 7 Optimierungpotenzial: Java • Wie bei jeder Programmiersprache gibt es eine Lernkurve (die wir vermutlich alle mal durchgemacht haben) – String+= StringBuffer.append() StringBuilder.append() 1/20 ½ – logger.debug(a+“:“+b) if (myLogger.isDebugEnabled()) … – new Integer(x) Integer.valueOf(x) ½ – java.util.Vector java.util.LinkedList java.util.ArrayList – HashTable HashMap – InputStream BufferedInputStream – Verwendung eines SessionPools • Bei Java-Entwicklern beliebt, denn man bleibt ja in der wohlvertrauten Umgebung 8 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 4 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Java • Das war einfach • Aber: selbst sehr angestrengtes Code-Reading findet viele Probleme oft nicht Profiler • Sammlung und Auswertung von Laufzeitinformationen – Call Stack mit Zeitmessung und/oder mit Sampling 9 Optimierungpotenzial: Java • Das war einfach • Aber: selbst sehr angestrengtes Code-Reading findet viele Probleme oft nicht Profiler • Sammlung und Auswertung von Laufzeitinformationen – Call Stack mit Zeitmessung und/oder mit Sampling – Memory Analyse 10 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 5 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Java • Das war einfach • Aber: selbst sehr angestrengtes Code-Reading findet viele Probleme oft nicht Profiler • Sammlung und Auswertung von Laufzeitinformationen – Call Stack mit Zeitmessung und/oder mit Sampling – Memory Analyse – Concurrency Analyse – Thread Analyse 11 Optimierungpotenzial: Java • Das war einfach • Aber: selbst sehr angestrengtes Code-Reading findet viele Probleme oft nicht Profiler • Sammlung und Auswertung von Laufzeitinformationen – Call Stack mit Zeitmessung und/oder mit Sampling – Memory Analyse – Concurrency Analyse – Thread Analyse • Profiling verzerrt Ergebnisse • Ziel: Hot Spots finden und Ineffizienzen aufdecken – Einzelne Methoden beschleunigen – Überflüssige Aufrufe, schlechte Schleifen, unnötige Allokationen,.. 12 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 6 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Java • Das war einfach • Aber: selbst sehr angestrengtes Code-Reading findet viele Probleme oft nicht Profiler • Sammlung und Auswertung von Laufzeitinformationen – Call Stack mit Zeitmessung und/oder mit Sampling – Memory Analyse – Concurrency Analyse SCHON ETWAS SPANNENDER! Profiling verzerrt Ergebnisse • Erfordert Tools Ziel: Hot Spots finden und Ineffizienzen aufdecken • Schlüsselloch-Perspektive – Einzelne Methoden beschleunigen • Aufrufe, Starkes Refactoring – Überflüssige schlechte Schleifen, unnötigeverstärkt Allokationen,.. Schlüsselloch-Perspektive – Thread Analyse • • 13 Versuch einer Ordnung • Wo steckt Optimierungspotenzial? Java Database Algorithmen 14 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 7 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Database • Das ungeliebte Kind (bei den Entwicklern) „die Datenbank ist langsam“ Tatsächlich: Call: < 1ms (Netzwerk-Latenz!) Query : < 10ms Festplatten IO: > 20.000 IOPS 15 Optimierungpotenzial: Database • Das ungeliebte Kind (bei den Entwicklern) – Erfordert Kenntnisse wie relationale Datenbanken funktionieren • Ziele – Minimierung der Aufrufe (Latenz!) – Erhöhung der Cache-Hits – Richtige Wahl der Indices 16 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 8 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Database • Das ungeliebte Kind (bei den Entwicklern) – Erfordert Kenntnisse wie relationale Datenbanken funktionieren • Ziele – Minimierung der Aufrufe (Latenz!) – Erhöhung der Cache-Hits – Richtige Wahl der Indices 17 Optimierungpotenzial: Database Cache-Hit-Ratio von 97% ist schlecht! • Das ungeliebte Kind (bei den Entwicklern) – Erfordert Kenntnisse wie relationale 98% ist nicht „nur 1% besser“, Datenbanken funktionieren sondern satte 33% besser ! • Ziele – Minimierung der Aufrufe (Latenz!) – Erhöhung der Cache-Hits – Richtige Wahl der Indices 18 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 9 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Database Wie? 1. langweilige, statische Prüfung – Cache-Größe an RAM anpassen – Disk-Performance und RAID-Level prüfen • Logs sind schreib-lastig • Daten sind lese-lastig (Level 5) – Netzwerk-Performance ApplikationDB prüfen – Dynamisches SQL vermeiden (SQL Area Flooding) – Fetch-Size erhöhen 2. schwierigere Untersuchungen dynamisch – Statistiken lesen – Ausführungspläne lesen und optimieren – Relational denken, denn idiomatisches Java führt zu schlechterer Performance – Batching einbauen Entwickler und DBA gemeinsam? 19 Versuch einer Ordnung • Schnittmengen Java Database Algorithmen 20 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 10 77. XPUG Frankfurt 2011‐11‐09 Optimierungpotenzial: Algorithmen • In Java alles optimiert? Die DB optimiert? Alles im Speicher und trotzdem dauert es 20 Sekunden? • Dann kann es noch am Algorithmus liegen! • Aber woher nehmen wir einen besseren Algorithmus (ohne akademische Forschung)? • Idee: wir benutzen Pattern! 21 Pattern für bessere Algorithmen • Pattern #1 • • • • Pattern #2 Pattern #3 Pattern #4 Pattern #5 You can‘t always get what you (Rolling Stones) want Nothing else matters (Metallica) Hanging by a thread(Mike & the Mechanics) (Adele) Fool that I am No, no, no (Destiny‘s child) 22 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 11 77. XPUG Frankfurt 2011‐11‐09 Pattern #2: You can‘t always get what you want (Rolling Stones) • Es gibt keine ultimative Genauigkeit! • Wann ist ein Wert „genau 0.0“? System.out.println(0.1 + 0.2 - 0.3); 10-5? 10-10? 10-100? 10-300? • Aus anderen Disziplinen: – Maschinenbauer messen Bauteile mit Instrumenten die 10* genauer messen können als das Bauteil genau sein muss – Bauingenieure geben einen Sicherheitsaufschlag von „Faktor 2“ auf die Berechnung 23 Pattern #2: You can‘t always get what you want (Rolling Stones) • Beispiele: – Wenn ich als kleinste Dauer ¼ Stunde anbiete, reicht eine Berechnung auf Minuten-Genauigkeit – Für eine Staulänge „in Kfz“ reicht eine Berechnung in dm • Solche GUI Anzeigen „Ihr Termin beginnt um 09:21:33,527 Uhr“ „die Stauprognose für Montag: 264,42516 Kfz“ wirken irgendwie unprofessionell • Vorsicht: „precision lost“ bei floating point Operationen • Aber eine Null-Stelle auf 300 Nachkommastellen zu berechnen ist selten sinnvoll • Der größte „precision lost“ sind oft Eingabefelder und Importformate (CSV, XML, …) 24 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 12 77. XPUG Frankfurt 2011‐11‐09 Pattern #2: Nothing else matters (Metallica) • Hier geht es um Normierung: • Konsequente Festlegung auf ein Koordinatensystem, eine Zeitzone, feste Abstände etc. erspart Umrechnungen • Umrechnungen sollten nur an den Systemgrenzen (GUI, Schnittstellen) stattfinden: – direkt nach Eingabe wird umgewandelt, – direkt vor der Ausgabe wird umgewandelt 25 Pattern #2: Nothing else matters (Metallica) • Beispiel 1: normierte Zeitzonen Alle Datum-Objekte grundsätzlich in UTC – jeder Tag hat jetzt 24 Stunden DST-Wechsel müssen nicht abgefragt werden – Keine Prüfung ob zwei Objekte in der gleichen Zeitzone liegen – Keine Umrechnung innerhalb des Systems CET EDT GUI UTC+7 DB GUI UTC DB Asia/Tokyo 26 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 13 77. XPUG Frankfurt 2011‐11‐09 Pattern #2: Nothing else matters (Metallica) • Beispiel 2: normierte Abtastrate Messung von Verkehrsaufkommen auf einer Autobahn liegen als Kurve vor. – Leider hat die Kurve nur Werte für jeweils 15 Minuten und teilweise Lücken. Die Abtastrate ist also nicht konstant. – Berechnungen mit der Kurve (Staugefahr, Staulänge) erfordert lineare Interpolation Kapazität Stau! Durch einmalige Interpolation und Normierung auf Abtastraten im 1Minuten-Abstand • • keine weiteren Interpolationen zur Nullstellensuche direkt nutzbar zur numerischen Integralberechnung 27 Pattern #3: Hanging by a thread (Mike & the Mechanics) • Früher war Multi-Threading den pthreadCracks auf teuren Multi-CPU-Servern vorbehalten • Heute hat jeder Desktop mehrere CPUKerne, sogar Mobiltelefone ! • Und Java bringt Thread-Unterstützung schon in der Sprache mit Was seriell ausgeführt wird, kann parallel oft deutlich schneller sein 28 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 14 77. XPUG Frankfurt 2011‐11‐09 Pattern #3: Hanging by a thread (Mike & the Mechanics) • Beispiel: void calcLoading() { for (Train train : trains) { calcTrainLoading(train); } } • Parallel mit Hilfe von java.util.concurrent: final static int N_PROCS = Runtime.getRuntime().availableProcessors(); final static ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(N_PROCS, N_PROCS, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50)); private Future<Void> asyncCalcTrainLoading(final Train train) { return EXECUTOR_SERVICE.submit(new Callable<Void>() { @Override public Void call() { calcTrainLoading(train); return null; } }); } 29 Pattern #3: Hanging by a thread (Mike & the Mechanics) • Beispiel: void calcLoading() calcLoadingParallel() { { for (Train train : trains) { calcTrainLoading(train); asyncCalcTrainLoading(train); } } Pattern #3b: • Parallel mit Hilfe von java.util.concurrent: Fire & Forget ! final static int N_PROCS = Runtime.getRuntime().availableProcessors(); final static ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(N_PROCS, N_PROCS, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50)); private Future<Void> asyncCalcTrainLoading(final Train train) { return EXECUTOR_SERVICE.submit(new Callable<Void>() { @Override public Void call() { calcTrainLoading(train); return null; } }); } 30 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 15 77. XPUG Frankfurt 2011‐11‐09 Pattern #3: Hanging by a thread (Mike & the Mechanics) void calcLoadingParallel() void calcLoading() calcLoadingParallel() { { • Beispiel: List<Future<Void>> for (Train train futures : trains) = new ArrayList<Future<Void>>(); { calcTrainLoading(train); asyncCalcTrainLoading(train); // parallele } Berechnungen starten for (Train } train : trains) { futures.add(asyncCalcTrainLoading(train)); } // warten bis alles abgearbeitet (Future<Void> future : futures) { final static int for N_PROCS = Runtime.getRuntime().availableProcessors(); try { final static ExecutorService EXECUTOR_SERVICE future.get(); = new ThreadPoolExecutor( N_PROCS, N_PROCS, 0, TimeUnit.SECONDS, } catch (Exception e) { new ArrayBlockingQueue<Runnable>(50)); System.err.println("failed to execute"); } private Future<Void> } asyncCalcTrainLoading(final Train train) { } .submit(new Callable<Void>() { return EXECUTOR_SERVICE @Override public Void call() { calcTrainLoading(train); return null; } }); } • Parallel mit Hilfe von java.util.concurrent: 31 Pattern #3: Hanging by a thread (Mike & the Mechanics) • Fallstricke – Kommutativ-Gesetz muss gelten, denn Reihenfolge nicht mehr sicher – Thread-safety – Synchronized Methoden & Blöcke • Ausführung nun in mehreren Threads • potentiell blocking –… 32 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 16 77. XPUG Frankfurt 2011‐11‐09 Pattern #3: Hanging by a thread (Mike & the Mechanics) – Schachtelung von Parallelität • Potentiell warten alle Worker-Threads endlos • seperate Queues verwenden main() { f(); g(); } f() { x(); y(); } g() { z(); z(); } Seriell: Main-thread: main, f, x, y, g, z, z Parallel 1: Main-thread: main g,f queue Worker1-thread: Worker2-thread: Parallel 2: Main-thread: main queue g,f z,z,y,x Worker1-thread: Worker2-thread: f, x, y g, z, z f g 33 Pattern #4: Fool that I am (Adele) • Der Idiot, der ich bin, versucht immer wieder Optimierung durch „Caching“ • große Verlockung: Faktor 1 Mio schneller als Network+Disk-Zugriffe • nur auf den ersten Blick eine „einfache“ Optimierung – Cache-Kohärenz Cache 1 update Cache 2 Original 34 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 17 77. XPUG Frankfurt 2011‐11‐09 Pattern #4: Fool that I am (Adele) • Der Idiot, der ich bin, versucht immer wieder Optimierung durch „Caching“ • große Verlockung: Faktor 1 Mio schneller als Network+Disk-Zugriffe • nur auf den ersten Blick eine „einfache“ Optimierung – Cache-Kohärenz – Write-Through-Cache update the Cache Original 35 Pattern #4: Fool that I am (Adele) • Der Idiot, der ich bin, versucht immer wieder Optimierung durch „Caching“ • große Verlockung: Faktor 1 Mio schneller als Network+Disk-Zugriffe update • nur auf den ersten Blick eine „einfache“ Cache 1 Optimierung Original – Cache-Kohärenz – Write-Through-Cache – Cluster-Betrieb: Netzwerk IO Cache 2 36 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 18 77. XPUG Frankfurt 2011‐11‐09 Pattern #4: Fool that I am (Adele) • Der Idiot, der ich bin, versucht immer wieder Optimierung durch „Caching“ • große Verlockung: Faktor 1 Mio schneller In der Datenbank haben wirals Network+Disk-Zugriffe (meist) nicht nur (B)Locking sondern Multi-Versioning • nur auf den ersten Blick eine „einfache“ ! Optimierung – – – – Cache-Kohärenz Write-Through-Cache Cluster-Betrieb: Netzwerk IO Transaktionsserialisierung: ACID • Atomicity, Consistency, Isolation, Durability 37 Pattern #4: Fool that I am (Adele) • Der Idiot, der ich bin, versucht immer wieder Optimierung durch „Caching“ • große Verlockung: Faktor 1 Mio schneller als Network+Disk-Zugriffe • nur auf den ersten Blick eine „einfache“ Optimierung – – – – Cache-Kohärenz Write-Through-Cache Cluster-Betrieb: Netzwerk IO Transaktionsserialisierung: ACID • Atomicity, Consistency, Isolation, Durability – Partitionierung der Daten 38 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 19 77. XPUG Frankfurt 2011‐11‐09 Pattern #5: No, no, no (Destiny‘s child) Mein Lieblingspattern: • Manchmal ist die offensichtliche Lösung für einen Algorithmus schön einfach, übersichtlich und gut lesbar - aber leider langsam • Dann kann es sich lohnen, statt der eigentlichen Lösung die Nicht-Lösung zu suchen und dann die Lösung daraus abzuleiten • Also suche alle Nicht-Treffer und ziehe diese von der Gesamtmenge ab, dann hast Du die gesuchte Treffermenge 39 Pattern #5: No, no, no (Destiny‘s child) Beispiel 1 • Wann überlappen sich zwei Zeitintervalle (start1, end1) und (start2, end2)? • Erste Lösung: (start1 >= start2 && start1 <= end2) || (end1 >= start2 && end1 <= end2) || (start2 >= start1 && start2 <= end1) • Negieren: wann überlappen sie NICHT? (end1 < start2) || (end2 < start1) • Doppelt-Negieren: wann überlappen sie NICHT-Nicht? (end1 >= start2) && (end2 >= start1) 40 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 20 77. XPUG Frankfurt 2011‐11‐09 Pattern #5: No, no, no (Destiny‘s child) Beispiel 2 • Prüfung von Arztrechnungen nach GOÄ: Bestimmte Leistungen (GOÄ-Ziffern) dürfen „nicht neben“ anderen abgerechnet werden. • Aufgabe: welche Rechnungspositionen müssen gestrichen werden, so dass der Arzt die höchstmögliche, GOÄ-konforme Rechnung stellt • Erste Lösung: Optimierung in Anlehnung an Dijksrtras Algorithmus zur Bestimmung kürzester Wege Position A B C GOÄ-Ziffer Betrag [€] 5030 73,87 5031 140,42 5035 52,83 NNZ-Hinweis Nicht neben Pos B, C Nicht neben Pos A, C Nicht neben Pos A, B A B 73,87€ 140,42€ C • Optimierung: doppelte Negierung ! 52,83€ • welche Rechnungspositionen müssen erhalten bleiben, so dass der Arzt die höchstmögliche, GOÄ-konforme Rechnung stellt 41 Nochmal: Pattern für bessere Algorithmen #1 You can‘t always get what you want • nicht präziser als nötig, Abbruchbedingung sinnvoll setzen (iterative Verfahren, numerische Integration, …) #2 Nothing else matters • Normierung erspart Sonderfälle und Umrechnungen #3 Hanging by a thread • Parallelisierung, Ausnutzung der CPU-Kerne #4 Fool that I am • Caching #5 No, no, no • Doppelte Negierung 42 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 21 77. XPUG Frankfurt 2011‐11‐09 Versuch einer Ordnung • Schnittmengen ConnectionPool Java Database Gesammeltes Informatikwissen über Datenstrukturen, Sortierverfahren etc. • java.util • Apache Commons Collections • Google Guava Collections Caching Algorithmen 43 Zusammenfassung • • Alte Regel: um Performance kümmert man sich erst wenn es nötig ist Gegenargumente – – – wenn alles langsam ist, wird späte Performanceoptimierung sehr schwer der Algorithmus ist grundlegend und kann später nur schwer ausgetauscht werden erfahrene Entwickler spüren früh, wenn etwas zu langsam sein wird • tumbes Refactoring versperrt den Blick auf den Algorithmus • langweilige statische Prüfung führen zu Java-Optimierung und DB-Tuning • dynamische Prüfung und Optimierung funktionieren oft auch ohne intime Kenntnisse der Applikation • für Optimierung durch Algorithmen muss man sich tiefer in das (fachliche) Problem eindenken, Schlüsselloch-Perspektive verlassen Änderung des Algorithmus macht den Code oft schwerer verständlich – – – • – oft erkennbar an kleinen Methoden mit vielen Parametern für Anfänger geeignet aber tiefe Java und DB Kenntnisse nötig „da fährt keiner nicht über Grün“ 44 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 22 77. XPUG Frankfurt 2011‐11‐09 Fragen ? 45 © 2011 Martin Müller‐Rohde, SCOOP Software GmbH 23