Java Performance Optimieren

header: java performance

Wann und Wie Java Performance steigern

Meistens wirst du in einer Situation sein in der du entweder schnellen Code oder sauberen Code haben kannst und dich entscheiden musst. In den meisten Fällen ist sauberer Code wichtiger, aber es gibt auch Fälle in denen Geschwindigkeit von hoher Relevanz ist und du dich dafür entscheiden wirst Code Qualität und Code Wartbarkeit für eine Performance-Erhöhung aufzuopfern.

Performance-Erhöhung wird relevant, wenn eine Funktion sehr oft aufgerufen wird (wobei die Bedeutung von “oft” von der Größe und Komplexität der Funktion abhängt). Wenn dies der Fall ist solltest du den Code ausführlich kommentieren, da Optimierter Code in aller Regel wesentlich schwerer zu lesen und verstehen ist.

Und nun zu einigen Tipps um Java Code zu Optimieren. Aber merke: Verfrühte Optimierung ist die Wurzel alles Bösen!

Grundsätzliche Tipps zur Verbesserung der Java Performance

  • suche einen besseren Algorithmus (wirklich! Falls du einen finden solltest wird dies mehr bringen als alles was ich hier schreibe).
  • Analysiere und entferne Engpässe. Vermeide es Code zu Optimieren der keinen Engpass darstellt. Es ist es nicht wert schönen Code zu zerstören um einige lausige Prozent Performance Erhöhung zu erreichen.
  • Exceptions sind ausschließlich für außergewöhnliche Fälle. Exceptions sind teuer da ein Exceptions-Objekt erstellt werden muss. Sie sollten nicht verwendet werden um Logik zu implementieren.
  • verwende vorhandene Java Funktionen anstatt deine eigenen Funktionen zu schreiben da sie sich zumeist jahrelang bewährt haben. Wähle außerdem die richtige Funktion für den jeweiligen Fall, zum Beispiel ist der StringBuilder besser als + zum Zusammenfügen von Strings. Verwende stets den richtigen Java Datentyp für die Situation.
  • verwende keine Objekte wenn primitive Datentypen es auch tun (zum Beispiel nicht Integer wenn int ausreichend ist).
  • Wenn möglich verwende Objekte wieder. Es ist zum Beispiel billiger Werte eines bestehenden Objekts zu verändern als ein neues zu erstellen. Ein weiteres Beispiel für das Recyceln von Objekten: anstatt:
    public void oftenCalledMethod() {
        g.setColor(new Color(100,100,100));
        // do something
        g.setColor(new Color(200,200,200));
        // do some more
    }
    speichere die Farbe zwischen (zum Beispiel in einem lokalen (statischen) Feld oder einer Utility-Klasse.
  • Wenn etwas Konstant ist, deklariere es als static constant und generiere es in einem static block. Dies ist vorallem wichtig, wenn es in mehreren Instanzen eines Objekts verwendet wird.

Java Performance in großen Schleifen erhöhen

Reduziere die Anzahl von Methodenaufrufen auf Objekte

Methodenaufrufe haben grundsätzlich einen Overhead, hinzu kommt, dass die Methode selbst ebenfalls etwas kostet. Zum Beispiel kann es sinnvoll sein die Abbruchbedingung für eine Schleife in einer Variable zwischenzuspeichern für den Fall, dass sie sich nicht ändert:

int size = myObject.getSize(); // Größe ändert sich nicht; getSize() ist teuer
for (int i = 0; i < size; i++) {
    // do something
}

Umformulierung mathematischer Aussagen

Wenn du mathematische Aussagen verwendest, überlege ob es möglich ist sie anders zu formulieren. Beachte dabei vorallem wie teuer die verwendeten Operationen sind. Eine gute Faustregel ist: Additionen sind schneller als Multiplikationen, sind schneller als Divisionen sind schneller als Exponenten.

Ein Beispiel. Anstatt:

int[] somethingToCalulate;
int constant;
for (int i = 0; i < giantSize; i++) {
    somethingToCalulate[i] = i * constant;
}

kann es sinnvoll sein folgendes zu verwenden um eine Geschwindigkeitserhöhung zu erreichen:

int[] somethingToCalulate;
int constant;
int accumulated = 0;
for (int i = 0; i < giantSize; i++) {
    somethingToCalulate[i] = accumulated;
    accumulated += constant;
}

Berechne einmal, verwende mehrmals

Eine Generalisierung des vorherigen Tipps: Falls du Dinge doppelt berechnest hör damit auf. Speichere das Ergebnis der Rechnung und verwende es wieder. Dies ist grundsätzlich eine gute Idee, nicht nur in Bezug auf Code Performance sondern auch in Bezug auf die Code Qualität.

Folgender Code:

int[] somethingToCalulate;
int[] somethingOtherToCalulate;
for (int i = 0; i < giantSize; i++) {
    somethingToCalulate[i] = i * (constant - i);
    somethingOtherToCalulate = i * (constant - i) * otherConstant;
}

Kann zum Beispiel durch diesen hier ersetzt werden:

int[] somethingToCalulate;
int[] somethingOtherToCalulate;
for (int i = 0; i < giantSize; i++) {
    int temp = i * (constant - i);
    somethingToCalulate[i] = temp;
    somethingOtherToCalulate = temp * otherConstant;
}

In diesem Beispiel ist es ziemlich offensichtlich, aber es gibt auch weniger auffällige Beispiele.

Unrolling Loops

Für mich brachte unrolling loops keinen Geschwindigkeitsgewinn, aber es resultiert definitiv in unsauberen und schwer zu Verwaltenden Code, von daher würde ich davon abraten.

Netbeans Profiler

Der Netbeans Profiler kann verwendet werden um Performance Probleme zu Identifizieren und Lokalisieren. Dies macht es einfacher sie zu beheben.

Starten des Netbeans Profiler

netbeans profiler

Der Netbeans Profiler ist einfach zu verwenden und sehr informativ. Um ihn zu starten drücke auf den Profile-Knopf (siehe Screenshot links). Falls du diesen Knopf nicht hast gehe zu tools -> plugins und suche nach profiler. Installiere ihn und der Knopf sollte erscheinen.

Falls dein Programm keine Benutzereingabe erwartet warte einfach bis der Profiler beendet ist und sehe den Snapshot an. Falls dein Programm Benutzereingaben erwartet liefere diese. Falls du Einfluss auf die Laufdauer des Programms hast (zum Beispiel durch Benutzereingabe oder Änderung einer Listengröße) wähle eine Größe die weder zu klein noch zu groß ist. Der Profiler verursacht einen Overhead und wenn die Eingabe zu groß ist dauert es zu lange, wenn sie zu klein ist gibt es nicht genug Daten um eine sinnvolle Analyse zu erstellen.

Analyse der Ergebnisse des Netbeans Profilers

netbeans profiler call tree example

Nachdem das Programm beendet ist wirst du einen Snapshot erhalten der dem auf dem Bild links ähneln sollte. Die Methode die einen Thread/ein Programm startet benötigt selbstverständlich 100% der Zeit. Wenn du nun auf das kleine Plus-Zeichen Links klickst wirst du sehen welche weiteren Methoden aufgerufen wurden und wie viel Zeit sie benötigt haben. Wenn du nun eine Methode siehst die überdurchschnittlich viel Zeit benötigt schaue sie dir genauer an. Was ist die Zeit der Methode selbst? Kann diese Zeit reduziert werden? Was ist mit den Methoden die sie aufruft? Brauchen sie lange? Wie oft ruft sie bestimmte Methoden auf? Können diese Aufrufe reduziert werden? Wenn du Antworten auf diese Fragen gefunden hast bist du schon fast mit dem Optimieren fertig.

netbeans profiler hotspots example 

In meinem Beispiel Snapshot kannst du sehen dass die Methode alphaBeta innerhalb einer Methode aufgeführt ist die ebenfalls alphaBeta heißt. Dies bedeutet, dass die Methode sich selbst rekursiv aufruft. Um Engpässe in rekursiven Methoden besser analysieren zu können ist es sinnvoll den Hot Spots Tab zu verwenden der von dem Netbeans Profiler angeboten wird, siehe Beispiel links.

Weiterführende Literatur

Simplenotions: Speed-up your java code (english)
glenmccl: Thirty Ways to Improve the Performance [pdf, english]
Code Complete, Steve McConnell

Das Buch Code Complete hat ein sehr gutes Kapitel über die Optimierung von Code (auch die restlichen Kapitel sind nicht schlecht, aber dieses Kapitel ist wirklich großartig und der Hauptgrund weshalb ich das Buch gekauft habe). Es ist zum Beispiel auf Amazon sowohl auf Deutsch als auch auf Englisch erhältlich.

Ähnliche Artikel:

Leave a Reply

Your email address will not be published.