Java >> Java tutorial >  >> JDK

Benchmark JDK-samlinger vs Eclipse-samlinger

1. Introduktion

I dette selvstudie skal vi sammenligne ydeevnen af ​​traditionelle JDK-samlinger med Eclipse-samlinger. Vi opretter forskellige scenarier og udforsker resultaterne.

2. Konfiguration

Bemærk først, at vi i denne artikel bruger standardkonfigurationen til at køre testene. Ingen flag eller andre parametre vil blive sat på vores benchmark.

Vi bruger følgende hardware og biblioteker:

  • JDK 11.0.3, Java HotSpot(TM) 64-bit server VM, 11.0.3+12-LTS.
  • MacPro 2,6 GHz 6-core i7 med 16 GB DDR4.
  • Eclipse Collections 10.0.0 (senest tilgængelig i skrivende stund)
  • Vi vil udnytte JMH (Java Microbenchmark Harness) til at køre vores benchmarks
  • JMH Visualizer til at generere diagrammer fra JMH-resultater

Den nemmeste måde at oprette vores projekt på er via kommandolinjen:

mvn archetype:generate \
  -DinteractiveMode=false \
  -DarchetypeGroupId=org.openjdk.jmh \
  -DarchetypeArtifactId=jmh-java-benchmark-archetype \
  -DgroupId=com.baeldung \
  -DartifactId=benchmark \
  -Dversion=1.0

Derefter kan vi åbne projektet ved hjælp af vores foretrukne IDE og redigere pom.xml for at tilføje Eclipse Collections afhængigheder:

<dependency>
    <groupId>org.eclipse.collections</groupId>
    <artifactId>eclipse-collections</artifactId>
    <version>10.0.0</version>
</dependency>
<dependency>
    <groupId>org.eclipse.collections</groupId>
    <artifactId>eclipse-collections-api</artifactId>
    <version>10.0.0</version>
</dependency>

3. Første benchmark

Vores første benchmark er simpelt. Vi ønsker at beregne summen af ​​en tidligere oprettet Liste af heltal .

Vi tester seks forskellige kombinationer, mens vi kører dem i serie og parallelt:

private List<Integer> jdkIntList;
private MutableList<Integer> ecMutableList;
private ExecutorService executor;
private IntList ecIntList;

@Setup
public void setup() {
    PrimitiveIterator.OfInt iterator = new Random(1L).ints(-10000, 10000).iterator();
    ecMutableList = FastList.newWithNValues(1_000_000, iterator::nextInt);
    jdkIntList = new ArrayList<>(1_000_000);
    jdkIntList.addAll(ecMutableList);
    ecIntList = ecMutableList.collectInt(i -> i, new IntArrayList(1_000_000));
    executor = Executors.newWorkStealingPool();
}

@Benchmark
public long jdkList() {
    return jdkIntList.stream().mapToLong(i -> i).sum();
}

@Benchmark
public long ecMutableList() {
    return ecMutableList.sumOfInt(i -> i);
}

@Benchmark
public long jdkListParallel() {
    return jdkIntList.parallelStream().mapToLong(i -> i).sum();
}

@Benchmark
public long ecMutableListParallel() {
    return ecMutableList.asParallel(executor, 100_000).sumOfInt(i -> i);
}

@Benchmark
public long ecPrimitive() { 
    return this.ecIntList.sum(); 
}

@Benchmark
public long ecPrimitiveParallel() {
    return this.ecIntList.primitiveParallelStream().sum(); 
}

For at køre vores første benchmark skal vi udføre:

mvn clean install
java -jar target/benchmarks.jar IntegerListSum -rf json

Dette vil udløse benchmark på vores IntegerListSum klasse og gem resultatet i en JSON-fil.

Vi måler gennemløbet eller antallet af operationer pr. sekund i vores test, så jo højere jo bedre:

Benchmark                              Mode  Cnt     Score       Error  Units
IntegerListSum.ecMutableList          thrpt   10   573.016 ±    35.865  ops/s
IntegerListSum.ecMutableListParallel  thrpt   10  1251.353 ±   705.196  ops/s
IntegerListSum.ecPrimitive            thrpt   10  4067.901 ±   258.574  ops/s
IntegerListSum.ecPrimitiveParallel    thrpt   10  8827.092 ± 11143.823  ops/s
IntegerListSum.jdkList                thrpt   10   568.696 ±     7.951  ops/s
IntegerListSum.jdkListParallel        thrpt   10   918.512 ±    27.487  ops/s

Ifølge vores tests havde Eclipse Collections parallelle liste over primitiver den højeste gennemstrømning af alle. Det var også det mest effektive med en ydeevne næsten 10 gange hurtigere end Java JDK, der også kørte parallelt.

Selvfølgelig kan en del af det forklares med, at når vi arbejder med primitive lister, har vi ikke omkostningerne forbundet med boksning og unboxing.

Vi kan bruge JMH Visualizer til at analysere vores resultater. Diagrammet nedenfor viser en bedre visualisering:

4. Filtrering

Dernæst vil vi ændre vores liste for at få alle elementer, der er multiple af 5. Vi genbruger en stor del af vores tidligere benchmark og en filterfunktion:

private List<Integer> jdkIntList;
private MutableList<Integer> ecMutableList;
private IntList ecIntList;
private ExecutorService executor;

@Setup
public void setup() {
    PrimitiveIterator.OfInt iterator = new Random(1L).ints(-10000, 10000).iterator();
    ecMutableList = FastList.newWithNValues(1_000_000, iterator::nextInt);
    jdkIntList = new ArrayList<>(1_000_000);
    jdkIntList.addAll(ecMutableList);
    ecIntList = ecMutableList.collectInt(i -> i, new IntArrayList(1_000_000));
    executor = Executors.newWorkStealingPool();
}

@Benchmark
public List<Integer> jdkList() {
    return jdkIntList.stream().filter(i -> i % 5 == 0).collect(Collectors.toList());
}

@Benchmark
public MutableList<Integer> ecMutableList() {
    return ecMutableList.select(i -> i % 5 == 0);
}


@Benchmark
public List<Integer> jdkListParallel() {
    return jdkIntList.parallelStream().filter(i -> i % 5 == 0).collect(Collectors.toList());
}

@Benchmark
public MutableList<Integer> ecMutableListParallel() {
    return ecMutableList.asParallel(executor, 100_000).select(i -> i % 5 == 0).toList();
}

@Benchmark
public IntList ecPrimitive() {
    return this.ecIntList.select(i -> i % 5 == 0);
}

@Benchmark
public IntList ecPrimitiveParallel() {
    return this.ecIntList.primitiveParallelStream()
      .filter(i -> i % 5 == 0)
      .collect(IntLists.mutable::empty, MutableIntList::add, MutableIntList::addAll);
}

Vi udfører testen ligesom før:

mvn clean install
java -jar target/benchmarks.jar IntegerListFilter -rf json

Og resultaterne:

Benchmark                                 Mode  Cnt     Score    Error  Units
IntegerListFilter.ecMutableList          thrpt   10   145.733 ±  7.000  ops/s
IntegerListFilter.ecMutableListParallel  thrpt   10   603.191 ± 24.799  ops/s
IntegerListFilter.ecPrimitive            thrpt   10   232.873 ±  8.032  ops/s
IntegerListFilter.ecPrimitiveParallel    thrpt   10  1029.481 ± 50.570  ops/s
IntegerListFilter.jdkList                thrpt   10   155.284 ±  4.562  ops/s
IntegerListFilter.jdkListParallel        thrpt   10   445.737 ± 23.685  ops/s

Som vi kan se, var Eclipse Collections Primitive vinderen igen. Med en gennemstrømning, der er mere end 2x hurtigere end JDK-parallellisten.

Bemærk, at for filtrering er effekten af ​​parallel behandling mere synlig. Opsummering er en billig operation for CPU'en, og vi vil ikke se de samme forskelle mellem seriel og parallel.

Det præstationsløft, som Eclipse Collections primitive lister fik tidligere, begynder også at forsvinde, efterhånden som arbejdet på hvert element begynder at opveje omkostningerne ved boksning og unboxing.

For at afslutte, kunne vi se, at operationer på primitiver er hurtigere end objekter:

5. Konklusion

I denne artikel har vi lavet et par benchmarks for at sammenligne Java-samlinger med Eclipse-samlinger. Vi har udnyttet JMH til at forsøge at minimere miljøbias.

Som altid er kildekoden tilgængelig på GitHub.


Java tag