LinkedList 33x schneller als ArrayList oder “Kenne deine API!”

Für meine Arbeit schreibe ich zum Test gerade ein kleines Plugin für ImageJ, indem ich unter anderem einen Region-Fill Algorithmus brauche – leider tut’s der filler von ImageJ hier nicht – also schnell was selbst zusammengehackt:

Startpunkt initialisieren und in eine Liste damit
while (Liste nicht leer) {
   hole ersten Punkt P aus der Liste
   prüfe Eigenschaften von p.x/p.y auf dem Bild
   if (Eigenschaften erfüllt){
      füge der Liste die Punkte darüber, darunter, rechts und links hinzu
   }
}

Vorher eine ArrayList entsprechend groß initialisiert, damit die nicht dynamisch wachsen muss und gut ist’s.

Moment – in vielen Iterationen werden hinten an der Liste 4 Punkte angefügt. Das geht mit der ArrayList schön schnell, wie man im ArrayList Quellcode unschwer erkennt (wenn die Liste groß genug ist). Aber in jeder Iteration wird vorne das erste Element entnommen. – Ein Blick in den ArrayList-Quellcode offenbart ein “System.arraycopy(elementData, index+1,  elementData, index, numMoved);“. Oha – das riecht eigentlich eher nach einer LinkedList.

Also: Methode profilen, Listentyp ändern, nochmal profilen (mit dem NetBeans Profiler ja kein Thema). Ergebnis: LinkedList ist wie erwartet viel schneller. Ohne meinen ganzen sonstigen Code reduziert sich der Testfall in etwa auf folgende Testklasse:

public class ListTest {

    public static void main(String[] args) {
        List al = new ArrayList(31000);
        List ll = new LinkedList();
        long a = System.currentTimeMillis();
        testWith(al);
        long b = System.currentTimeMillis();
        testWith(ll);
        long c = System.currentTimeMillis();

        System.out.println("ArrayList: "+(b - a) + " ms");
        System.out.println("LinkedList: "+(c - b) + " ms");
    }

    private static void testWith(List list) {
        list.add(new Point(0, 0));
        int i = 10000;
        Point p;
        while (!list.isEmpty()) {
            p = list.remove(0);
            if (i-- > 0) {
                list.add(new Point(p.x, p.y - 1));
                list.add(new Point(p.x - 1, p.y));
                list.add(new Point(p.x + 1, p.y));
                list.add(new Point(p.x, p.y + 1));
            }
        }
    }
}

Das Speedstepping am Notebook nicht vergessen, sonst fährt die CPU am anfang ja noch nicht auf Vollgas und schon rennt der Test. Die ArrayList habe ich auch extra noch mit mehr Elementen initialisiert, als jemals reinkommen. Ausserdem geht die Initialisierung auch nicht in die Messung ein (ist im Vergeich mit dem Rest zwar eh vernachlässigbar klein, aber damit niemand auf die Idee kommt, der Vergleich wäre unfair …).

Ausgabe:
ArrayList: 860 ms  = 100%
LinkedList: 31 ms  = 3,6%

Sieht man sich die API und den Quellcode der beiden Klassen an, verwundert das Ergebnis nicht wirklich. Der unterschied skaliert natürlich auch mit der Anzahl der Iterationen.

Fazit

Ist ArrayList also schlecht? – Klares NEIN! Nur für genau diesen Anwendungfall (vielfaches Einfügen am Ende, vielfaches Herausnehmen am Anfang, kein Zugriff auf Elemente irgendwo dazwischen nötig) ist die ArrayList der Speicherstruktur der LinkedList einfach unterlegen, die genau auf diesen Punkten optimal funktioniert.

Ergo: Know your API! Man sollte sein Handwerkszeug kennen und nicht blindlings einfach das verwenden was man sonnst auch immer nimmt und hoffen, dass Java ein allmächtiges Werkzeug ist, das einem alle Arbeit abnimmt (und dann nacher groß jammern, dass in C++ ja eh alles viel schneller geht). Und wenn man Zweifel hat, ruhig mal einen Blick in den Sourcecode werfen und ausprobieren 😉

3 thoughts on “LinkedList 33x schneller als ArrayList oder “Kenne deine API!””

  1. Nicht umsonst implementiert man deswegen gegen das Interface (java.util.List) und kann dann auch noch nachträglich die konkrete Implementierung der Liste anpassen.

    Noch besser ist’s wenn man gegen java.util.Map implementiert und es einem dann frei steht, mal auf eine java.util.HashMap oder eine java.util.LinkedHashMap zu zurückzugreifen. Die LinkedHashMap bietet den Vorteil, dass die Indizes bei getKeys in der eingebrachten Reihenfolge zurückgeliefert werden.

    Es gibt noch tausende Beispiele, bei denen eine Implementierung immer gegen das Interface erfolgen soll und die konkrete Implementierung dahinter als BlackBox dient.

    Aber das wusstest Du ja schon 😉

  2. Cooles Beispiel das zeigt, dass man sich auch mal mit den Innereien einer API beschäftigen sollte. Gerade wenn es um das Thema Performance geht ist es enorm wichtig zu wissen was hinter den Kulissen passiert. Gut finde ich, dass Du nicht daraus schließt das ArrayList schlecht ist, denn LinkedList benötigt nur eine konstante Zeit zum Hinzufügen bzw. Löschen von Elementen an den Enden. Wahlfreier Zugriff ist bei ArrayList in konstanter Zeit möglich.

Comments are closed.