Intressante Benchmark-Ergebnisse mit Listen. Weiss jemand wie man diese erklaeren kann?

sirbender

Top Contributor
Hi,

ich habe einen Benchmark geschrieben der ueber Listen iteriert. Hierbei unterscheide ich zwei Arten von Iterationen:

1. via Listen-Index
2. via implizitem Iterator (also foreach-Schleife)

Auch teste ich ob es einen Unterschied macht ob die Benchmark-Methode den Original-Typ entgegennimmt (also z.B. ArrayList) oder stattdessen den Typ "List" akzeptiert.

Hier die Ergebnisse in Millisekunden und den Benchmark-Code den man sich anschauen sollte um es vollstaendig zu kapieren:

indexedIterationArrayList: 67917
indexedIterationCopyOnWriteArrayList: 119210
indexedIterationAList: 59484
indexedIterationCList: 119701
indexedIterationUnmodifiableList: 57325

implicitIiteratorArrayList: 80103
implicitIiteratorCopyOnWriteArrayList: 61323
implicitIiteratorAList: 89433
implicitIiteratorCList: 67278
implicitIiteratorUnmodifiableList: 79405

Am schnellsten ist UnmodifiableList bei indexed-Iteration.

Wie man sieht ist index-Iteration schneller als foreach bei ArrayList. Lustigerweise macht es einen Unterschied ob die Benchmark-Methode ArrayList oder List entgegennimmt. Beim indexed-Iterator ist es schneller wenn man List anstatt ArrayList in der Signatur stehen hat. Beim foreach-Iterator ist es genau anderstrum.

CopyOnWriteArrayList ist langsamer als ArrayList beim indexed-Iterator (was mich ueberrascht hat). Beim foreach-Iterator ist CopyOnWriteArrayList schneller als ArrayList. Auch sehr ueberraschend. UnmodifiableList ist nicht schneller als ArrayList.

Wie soll man das alles erklaeren?




Java:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

public class PerformanceComparison {

    private static final boolean DEBUG = true;
    
    static int indexedIterationArrayList   = 0;
    static int indexedIterationCopyOnWriteArrayList = 0;
    static int indexedIterationAList = 0;
    static int indexedIterationCList = 0;
    static int indexedIterationUnmodifiableList = 0;
    
    static int implicitIiteratorArrayList = 0;
    static int implicitIiteratorCopyOnWriteArrayList = 0;
    static int implicitIiteratorAList = 0;
    static int implicitIiteratorCList = 0;
    static int implicitIiteratorUnmodifiableList = 0;
    

    public static void main(String[] args) {
        int repeats = 1000;
        final int size = 100000000;
        ArrayList<Integer> arraylist = new ArrayList<>(size);
        Random random = new Random();
        for (int i = 0; i < size; i++) {
            arraylist.add(random.nextInt(100));
        }
        System.out.println(arraylist.size());
        
        CopyOnWriteArrayList<Integer> cow = new CopyOnWriteArrayList<>(arraylist);
        List<Integer> unmodifiableList = Collections.unmodifiableList(arraylist);
        
        for (int i = 0; i < repeats; i++) {
            indexedIterationArrayList(arraylist);
            indexedIterationCopyOnWriteArrayList(cow);
            indexedIterationAList(arraylist);
            indexedIterationCList(cow);
            indexedIterationUnmodifiableList(unmodifiableList);
            
            implicitIiteratorArrayList(arraylist);
            implicitIiteratorCopyOnWriteArrayList(cow);
            implicitIiteratorAList(arraylist);
            implicitIiteratorCList(cow);
            implicitIiteratorUnmodifiableList(unmodifiableList);
        }
        
        System.out.println("indexedIterationArrayList: " + indexedIterationArrayList);
        System.out.println("indexedIterationCopyOnWriteArrayList: " + indexedIterationCopyOnWriteArrayList);
        System.out.println("indexedIterationAList: " + indexedIterationAList);
        System.out.println("indexedIterationCList: " + indexedIterationCList);
        System.out.println("indexedIterationUnmodifiableList: " + indexedIterationUnmodifiableList);
        
        System.out.println("implicitIiteratorArrayList: " + implicitIiteratorArrayList);
        System.out.println("implicitIiteratorCopyOnWriteArrayList: " + implicitIiteratorCopyOnWriteArrayList);
        System.out.println("implicitIiteratorAList: " + implicitIiteratorAList);
        System.out.println("implicitIiteratorCList: " + implicitIiteratorCList);
        System.out.println("implicitIiteratorUnmodifiableList: " + implicitIiteratorUnmodifiableList);
    }
    
    
    // indexedIteration
    
    public static void indexedIterationArrayList(ArrayList<Integer> list) {
        long start = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        indexedIterationArrayList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void indexedIterationCopyOnWriteArrayList(CopyOnWriteArrayList<Integer> list) {
        long start = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        indexedIterationCopyOnWriteArrayList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void indexedIterationAList(List<Integer> list) {
        long start = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        indexedIterationAList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void indexedIterationCList(List<Integer> list) {
        long start = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        indexedIterationCList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void indexedIterationUnmodifiableList(List<Integer> list) {
        long start = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        indexedIterationUnmodifiableList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }

    
    
    // foreach (a.k.a. implicit iterator)
    
    public static void implicitIiteratorArrayList(ArrayList<Integer> list) {       
        long start = System.currentTimeMillis();
        int sum = 0;
        for (Integer a : list) {
            sum += a;
        }       
        implicitIiteratorArrayList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void implicitIiteratorCopyOnWriteArrayList(CopyOnWriteArrayList<Integer> list) {       
        long start = System.currentTimeMillis();
        int sum = 0;
        for (Integer a : list) {
            sum += a;
        }       
        implicitIiteratorCopyOnWriteArrayList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    
    public static void implicitIiteratorAList(List<Integer> list) {       
        long start = System.currentTimeMillis();
        int sum = 0;
        for (Integer a : list) {
            sum += a;
        }       
        implicitIiteratorAList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void implicitIiteratorUnmodifiableList(List<Integer> list) {       
        long start = System.currentTimeMillis();
        int sum = 0;
        for (Integer a : list) {
            sum += a;
        }       
        implicitIiteratorUnmodifiableList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    public static void implicitIiteratorCList(List<Integer> list) {       
        long start = System.currentTimeMillis();
        int sum = 0;
        for (Integer a : list) {
            sum += a;
        }       
        implicitIiteratorCList += System.currentTimeMillis() - start;
        if (DEBUG) System.out.println(sum);
    }
    
    
}
 

httpdigest

Top Contributor
Microbenchmarks korrekt zu schreiben ist schwer. Sehr schwer! Für verlässliche Microbenchmarks bietet sich hier JMH an. Ein Problem von deinem Benchmark ist, dass für dieselbe JVM Session/Laufzeit alle Klassen geladen und alle Methode ausgeführt werden. Das kann die Inlining-Entscheidung der JVM beeinflussen, weil deine äußere for-schleife relativ groß ist und die Methoden darin auch nochmal ihre eigene for-Schleife besitzen.
Hier mal ein JMH-Äquivalent (für einige deiner Testfälle):
Java:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
public class Bench {

    private List<Integer> listInteger;
    private List<Integer> unmodifiableList;

    @Setup(Level.Iteration)
    public void setup() {
        Random random = new Random();
        int N = 1000000;
        this.listInteger = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            this.listInteger.add(random.nextInt(100));
        }
        this.unmodifiableList = Collections.unmodifiableList(listInteger);
    }

    @Benchmark
    public void List_get(Blackhole bh) {
        int sum = 0;
        for (int i = 0; i < listInteger.size(); i++) {
            sum += listInteger.get(i);
        }
        bh.consume(sum);
    }

    @Benchmark
    public void List_iterator(Blackhole bh) {
        int sum = 0;
        for (Integer a : listInteger) {
            sum += a;
        }
        bh.consume(sum);
    }

    @Benchmark
    public void UnmodifiableList_get(Blackhole bh) {
        int sum = 0;
        for (int i = 0; i < unmodifiableList.size(); i++) {
            sum += unmodifiableList.get(i);
        }
        bh.consume(sum);
    }

    @Benchmark
    public void UnmodifiableList_iterator(Blackhole bh) {
        int sum = 0;
        for (Integer a : unmodifiableList) {
            sum += a;
        }
        bh.consume(sum);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Bench.class.getName())
                .forks(1)
                .build();
        new Runner(opt).run();
    }
}
Ergebnis:
Code:
Benchmark                         Mode  Cnt  Score   Error   Units
Bench.List_get                   thrpt    5  2,170 ± 0,018  ops/ms
Bench.List_iterator              thrpt    5  1,638 ± 0,017  ops/ms
Bench.UnmodifiableList_get       thrpt    5  2,163 ± 0,056  ops/ms
Bench.UnmodifiableList_iterator  thrpt    5  1,633 ± 0,041  ops/ms
 

mrBrown

Super-Moderator
Mitarbeiter
Hab's so mit JMH umgesetzt (gemessen in ns/op statt wie @httpdigest ops/ms):

Java:
@Benchmark
public int indexedIterationArrayList() {
    return indexedIterationArrayList(arraylist);
}
public static int indexedIterationArrayList(ArrayList<Integer> list) {
    int sum = 0;
    for (int i = 0; i < list.size(); ++i) {
        sum += list.get(i);
    }
    return sum;
}

Ergibt etwa dies:
11626

Unterschiede zwischen statischen Parameter-Typen gibts keine relevanten, erstaunlich finde ich nur die Ausreißer bei CopyOnWriteList, bei beiden Interationsarten...
 

sirbender

Top Contributor
Vielen Dank @httpdigest und @mrBrown : echt toll :D

Ich schau mir die Details eurer Loesungen heute Abend mal genauer an.

Also meine Unterschiede waren alle nur meinem unsauberem Benchmarking geschuldet?

CopyOnWriteArrayList funzt glaube ich so, dass bei einem Write der interne Array durch einen neuen Array ersetzt wird. Alle vorher erzeugten Iteratoren sind weiterhin gueltig da sie auf den alten Array verweisen. D.h. wenn ich mit einem Iterator (foreach erzeugt implizit einen unsichtbaren Iterator) arbeite muss ich mir keine Gedanken machen (also auch nicht nachpruefen) ob mein Iterator noch gueltig ist. Optimierungen durch die JVM sind in dieser Situation wohl einfacher.

Bei einer Index-basierten Iteration muss ich bei jedem Schritt nachschauen ob der array auf den ich zugreife noch aktuell ist. Dieser check verhindert eventuell Optimierungen durch die JVM.

So meine aktuelle Theorie ohne mir die Interna von CopyOnWriteArrayList angeschaut zu haben. Was die JVM macht weiss sowieso keiner ;)

Was haltet ihr von der Interpretation?

P.S.: @mrBrown kannst du deinen ganzen Benchmark-Sourcecode hier posten, damit ich es 1:1 nachvollziehen kann. Das Bild ist Teil des Benchmark-Output?
 
Zuletzt bearbeitet:

httpdigest

Top Contributor
Bei einer Index-basierten Iteration muss ich bei jedem Schritt nachschauen ob der array auf den ich zugreife noch aktuell ist. Dieser check verhindert eventuell Optimierungen durch die JVM.
Spot on. Absolut korrekt!

Wenn man sich CopyOnWriteArrayList mal genauer ansieht, sieht man die Deklaration von dem Array:
Java:
private transient volatile Object[] array; // <- VOLATILE!
Das heißt, bei jedem Zugriff auf das Array wird es zwingend ein Memory Barrier sein und der Zugriff muss über den Arbeitsspeicher und nicht über gecachte Registerzugriffe erfolgen. Der Zugriff darf trotzdem noch über den L1-Cache gehen (was er auch wird, x86 ist eine Architektur mit Cache Coherency), aber eben nicht sich einmal im Register gemerkt - was schneller ist. Das muss ja auch so sein bei der copy-on-write Semantik, denn das Array darf ja auch ausgetauscht werden. Die CopyOnWriteArrayList ist ja hauptsächlich für konkurrierende Zugriffe in Multithreading-Anwendungen gedacht.
Daher kommt der Performanceeinbruch.
Bei allen anderen Listenklassen ist das nicht so (kein volatile) und die JVM darf sich den Zugriff auf das Array in einem Register merken.

Wenn man sich jetzt noch die Methode iterator() anschaut, sieht man, dass diese sich einmal das Array holt und dann auf dieser (fixen) Version des Arrays arbeitet. Das ist dann wieder schnell. Zumindest bei mir ist die CopyOnWriteArrayList iterator Variante genauso schnell wie alle anderen Iteratoren-Varianten.
 
Zuletzt bearbeitet:

sirbender

Top Contributor
CopyOnWriteArrayList muss also bei der Index-Iteration immer den Array aus dem Arbeitsspeicher holen.

Bei der Iterator-Iteration erfolgt das anderst nehme ich an: hier zeigt CopyOnWriteArrayList naemlich die beste Performance. Wie ist das zu erklaeren? Weil der Array gecached ist und zusaetzlich keine ConcurrentModification-Checks mehr noetig sind? Komisch, dass UnmodifiableList hier performancemaessig nicht abraeumt.
 

httpdigest

Top Contributor
CopyOnWriteArrayList muss also bei der Index-Iteration immer den Array aus dem Arbeitsspeicher holen.

Bei der Iterator-Iteration erfolgt das anderst nehme ich an: hier zeigt CopyOnWriteArrayList naemlich die beste Performance. Wie ist das zu erklaeren?
Wenn du `theCopyOnWriteArrayList.get(i)` aufrust, muss der JIT Compiler Code generieren, der immer effektiv `this.array[ i ]` (this = die liste) ausführt. Das heißt, es muss immer zuerst der Zeiger/die Adresse des Arrays aus dem Arbeitsspeicher (dem Cache) für die CopyOnWriteArrayList Instanz geholt werden. Erst dann kann auf das Array an dem gegebenen Index zugegriffen werden.
Anders ist das bei einer normalen ArrayList. Der Unterschied kommt zum Tragen, wenn man eine lange for-loop über viele Indizes hat und immer und immer wieder per get(i) auf die Liste zugreift. Hier darf der JIT Compiler bei einer normalen ArrayList den Zugriff zum Ermitteln des Arrays aus der for-Schleife herausziehen ([to] hoist), da er annehmen darf, dass sich währenddessen dieses array-Feld nicht ändert. Diese Annahme darf er bei der CopyOnWriteArrayList nicht tun.

Wie gesagt, bei dem Iterator von der CopyOnWriteArrayList holt sich die iterator() Methode das Array nur einmal und besitzt es dann und darf es auch im Register cachen.
 

mrBrown

Super-Moderator
Mitarbeiter
Das heißt, bei jedem Zugriff auf das Array wird es zwingend ein Memory Barrier sein und der Zugriff muss über den Arbeitsspeicher und nicht über gecachte Registerzugriffe erfolgen.
Hätte erwartet, dass der JIT das wegoptimieren kann, gibt ja nur einen Thread und keinen konkurrierende Zugriffe.
Bei der Iterator-Iteration erfolgt das anderst nehme ich an: hier zeigt CopyOnWriteArrayList naemlich die beste Performance. Wie ist das zu erklaeren? Weil der Array gecached ist und zusaetzlich keine ConcurrentModification-Checks mehr noetig sind?
Ja, dürfte im wesentlichen das sein.
Im Vergleich zu anderen Listen braucht es die ConcurrentModification-Checks nicht, und im Verglich zu CopyOnWriteArrayList#get kann das Array gecached werden.

Komisch, dass UnmodifiableList hier performancemaessig nicht abraeumt.
Die ist nur ein Wrapper um die eigentliche Liste, alle Aufrufe werden einfach nur weiter gegeben.
 

mrBrown

Super-Moderator
Mitarbeiter
P.S.: @mrBrown kannst du deinen ganzen Benchmark-Sourcecode hier posten, damit ich es 1:1 nachvollziehen kann. Das Bild ist Teil des Benchmark-Output?

Das Bild ist eben selbst geplottet aus den Ergebnissen, die Ausgabe von JMH sieht so aus, wie in @httpdigest's Beispiel (wobei auch cdv möglich ist).

Im Code ist nur eine Methode, die anderen sehen genauso aus, mit entsprechend angepasster Iteration & Liste, den Teil hast du ja in deinem Code schon.

Java:
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
@Threads(1)
@Warmup(iterations = 5, time = 1)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ListBenchmark {

    private ArrayList<Integer> arraylist;

    private CopyOnWriteArrayList<Integer> cow;

    private List<Integer> unmodifiableList;

    @Setup(Level.Iteration)
    public void setup() {
        final int size = 100_000;
        this.arraylist = new ArrayList<>(size);
        Random random = new Random();
        for (int i = 0; i < size; i++) {
            arraylist.add(random.nextInt(100));
        }

        this.cow = new CopyOnWriteArrayList<>(arraylist);
        this.unmodifiableList = Collections.unmodifiableList(arraylist);
    }


    @Benchmark
    public int indexedIterationArrayList() {
        return indexedIterationArrayList(arraylist);
    }
  
    public static int indexedIterationArrayList(ArrayList<Integer> list) {
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list.get(i);
        }
        return sum;
    }
}
 

httpdigest

Top Contributor
Hätte erwartet, dass der JIT das wegoptimieren kann, gibt ja nur einen Thread und keinen konkurrierende Zugriffe.
Wenn man die JVM startet (zumindest Oracle oder OpenJDK), dann werden mindestens 6 Threads gestartet (5 davon von internen JDK Klassen oder der JVM für Aufräumzwecke).
Es gibt eine Optimierung in HotSpot, wenn es auf single-core Rechnern läuft. Dann werden alle Memory Barriers und Locks entfernt, also alle volatile und synchronized. Da JDK-Klassen aber auch intern vom JDK selbst verwendet werden, weiß die JVM gar nicht mehr, welche Threads nun auf welche Objekte von CopyOnWriteArrayList zugreifen. Das wird nicht getracked.
Als weitere Technik gibt es aber die Escape Analysis, die aktuell nach diesem Paper implementiert ist: https://www.cc.gatech.edu/~harrold/6340/cs6340_fall2009/Readings/choi99escape.pdf
Zum Beispiel werden synchronized Blöcke entfernt, wenn garantiert wird, dass das Objekt, auf das synchronisiert wird, wirklich nur vom aktuellen Thread gesehen werden kann. Das gilt aber auch nur für Methoden-lokale Variablen. Felder werden per Definition als "escaped" angenommen, also potenziell von mehreren Threads geschreiben/gelesen - es sei denn, dass Inlining (welches Vorbedingung für Escape Analysis ist) Feldzugriffe eliminiert, weil wiederum das Objekt nur innerhalb derselben Methode gesehen werden kann.
 

sirbender

Top Contributor
Zum Beispiel werden synchronized Blöcke entfernt, wenn garantiert wird, dass das Objekt, auf das synchronisiert wird, wirklich nur vom aktuellen Thread gesehen werden kann. Das gilt aber auch nur für Methoden-lokale Variablen. Felder werden per Definition als "escaped" angenommen, also potenziell von mehreren Threads geschreiben/gelesen - es sei denn, dass Inlining (welches Vorbedingung für Escape Analysis ist) Feldzugriffe eliminiert, weil wiederum das Objekt nur innerhalb derselben Methode gesehen werden kann.

https://developer.android.com/training/articles/perf-tips.html#Loops

Google empfiehlt eine lokale Referenz auf ein Array bzw. Liste zu erzeugen:
Foo[] localArray = array;
int len = localArray.length;

Recht intressant deren Performance-Tips.
 

httpdigest

Top Contributor
Bedenke auch, dass die Android VM (Dalvik bzw. ART) wiederum nicht HotSpot ist, und ART fehlen sehr viele Performanceoptimierungen, die HotSpot im Gegenzug besitzt. ART macht noch nicht einmal Inlining geschweige denn Escape Analysis. Deswegen zahlen sich auch Tools wie ProGuard für Android auch hinsichtlich Performance aus. Es ist für ein Smartphone einfach viel zu teuer und battery-draining, zur Laufzeit den Code zu profilen und zu optimieren. Was ART macht, ist, zur Installationszeit der App den Code einmal statisch zu optimieren. Aber wie gesagt, das ist statische Code-Optimierung. HotSpot kann sehr viel besser Code zur Laufzeit optimieren.
 

sirbender

Top Contributor
@mrBrown

ich hab mal mit deinen Settings den Benchmark wiederholt:

Code:
Benchmark                                           Mode  Cnt       Score       Error  Units
ListBenchmark.implicitIiteratorCList                avgt    5   61006.970 ±   736.219  ns/op
ListBenchmark.implicitIteratorAList                 avgt    5   74464.287 ±  4757.725  ns/op
ListBenchmark.implicitIteratorArrayList             avgt    5   65617.936 ± 32984.372  ns/op
ListBenchmark.implicitIteratorCopyOnWriteArrayList  avgt    5   60084.404 ±  7499.311  ns/op
ListBenchmark.implicitIteratorUnmodifiableList      avgt    5   70273.350 ± 10808.919  ns/op
ListBenchmark.indexedIterationAList                 avgt    5   56253.735 ± 12071.223  ns/op
ListBenchmark.indexedIterationArrayList             avgt    5   54433.160 ±  1178.585  ns/op
ListBenchmark.indexedIterationCList                 avgt    5  110983.103 ±  3078.220  ns/op
ListBenchmark.indexedIterationCopyOnWriteArrayList  avgt    5  111012.147 ±  2141.272  ns/op
ListBenchmark.indexedIterationUnmodifiableList      avgt    5   64317.196 ± 19146.534  ns/op
 

sirbender

Top Contributor
Ich hab den Benchmark mit den beiden Faellen int[] und Integer[] erweitert.

Code:
Benchmark                                           Mode  Cnt       Score       Error  Units
ListBenchmark.implicitIiteratorCList                avgt    5   62046.837 ±  1967.252  ns/op
ListBenchmark.implicitIteratorAList                 avgt    5   72680.076 ± 17202.119  ns/op
ListBenchmark.implicitIteratorArrayList             avgt    5   66162.684 ± 32837.931  ns/op
ListBenchmark.implicitIteratorCopyOnWriteArrayList  avgt    5   56285.750 ±  7283.079  ns/op
ListBenchmark.implicitIteratorIntegerArray          avgt    5   30309.159 ±   401.980  ns/op
ListBenchmark.implicitIteratorPrimitiveArray        avgt    5   27356.872 ±   207.205  ns/op
ListBenchmark.implicitIteratorUnmodifiableList      avgt    5   70845.114 ± 12396.253  ns/op
ListBenchmark.indexedIterationAList                 avgt    5   56722.937 ± 11451.124  ns/op
ListBenchmark.indexedIterationArrayList             avgt    5   55129.788 ±  1023.167  ns/op
ListBenchmark.indexedIterationCList                 avgt    5  111718.145 ±  1550.038  ns/op
ListBenchmark.indexedIterationCopyOnWriteArrayList  avgt    5  112017.742 ±  1930.462  ns/op
ListBenchmark.indexedIterationIntegerArray          avgt    5   30450.654 ±   312.811  ns/op
ListBenchmark.indexedIterationPrimitiveArray        avgt    5   27589.216 ±   552.054  ns/op
ListBenchmark.indexedIterationUnmodifiableList      avgt    5   65142.970 ± 19182.177  ns/op

Die "Fehlerbalken" ist auch intressant. Bei manchen Listentypen scheint die JVM da deutlich "unstabiler" zu sein was Optimierungen angeht...z.B. bei der ArrayList mit dem Implicit-Iterator (a.k.a. foreach). Wie das wohl zu erklaeren ist?
 
Zuletzt bearbeitet:

mrBrown

Super-Moderator
Mitarbeiter
Die "Fehlerbalken" ist auch intressant. Bei manchen Listentypen scheint die JVM da deutlich "unstabiler" zu sein was Optimierungen angeht...z.B. bei der ArrayList mit dem Implicit-Iterator (a.k.a. foreach). Wie das wohl zu erklaeren ist?
Liegt fast immer am System, nicht an den Optimierungen, die sollten nach dem Warmup zum Großteil abgeschlossen sein (kannst du überprüfen, wenn du dir zB den Assemblercode ausgeben lässt).
Kann z.B. sein, dass währenddessen irgendein anderer Prozess kurz CPU brauchte, schon brechen die Werte ein.
 

Ähnliche Java Themen

Neue Themen


Oben