«1. Введение

В этом руководстве мы сравним производительность традиционных коллекций JDK с коллекциями Eclipse. Мы создадим различные сценарии и изучим результаты.

2. Конфигурация

Во-первых, обратите внимание, что в этой статье мы будем использовать конфигурацию по умолчанию для запуска тестов. В нашем бенчмарке не будут установлены флаги или другие параметры.

Мы будем использовать следующее оборудование и библиотеки:

    JDK 11.0.3, Java HotSpot™ 64-разрядная виртуальная машина сервера, 11.0.3+12-LTS. MacPro 2,6 ГГц, 6-ядерный процессор i7 с 16 ГБ памяти DDR4. Eclipse Collections 10.0.0 (последняя доступная на момент написания) Мы будем использовать JMH (Java Microbenchmark Harness) для запуска наших тестов JMH Visualizer для создания диаграмм на основе результатов JMH

Самый простой способ создать наш проект — с помощью команды -line:

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

После этого мы можем открыть проект с помощью нашей любимой IDE и отредактировать pom.xml, чтобы добавить зависимости коллекций Eclipse:

<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. Первый тест

Наш первый тест просто. Мы хотим вычислить сумму ранее созданного списка целых чисел.

Мы протестируем шесть различных комбинаций, запуская их последовательно и параллельно:

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(); 
}

Чтобы запустить наш первый тест, нам нужно выполнить:

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

Это вызовет тест в нашем классе IntegerListSum и сохранить результат в файл JSON.

В наших тестах мы будем измерять пропускную способность или количество операций в секунду, поэтому чем выше, тем лучше:

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

Согласно нашим тестам, параллельный список примитивов Eclipse Collections имел самую высокую пропускную способность из всех. Кроме того, он был наиболее эффективным: производительность почти в 10 раз выше, чем у Java JDK, работающего также параллельно.

Конечно, частично это можно объяснить тем фактом, что при работе с примитивными списками у нас нет затрат, связанных с упаковкой и распаковкой.

Мы можем использовать JMH Visualizer для анализа наших результатов. Диаграмма ниже показывает лучшую визуализацию:

4. Фильтрация

Далее мы изменим наш список, чтобы получить все элементы, кратные 5. Мы повторно используем большую часть нашего предыдущего теста и функцию фильтра :

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);
}

Выполним тест, как и раньше:

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

И результаты:

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

Как мы видим, Eclipse Collections Primitive снова стал победителем. С пропускной способностью более чем в 2 раза быстрее, чем параллельный список JDK.

Обратите внимание, что для фильтрации эффект параллельной обработки более заметен. Суммирование — дешевая операция для ЦП, и мы не увидим таких же различий между последовательным и параллельным.

Кроме того, прирост производительности, полученный ранее для примитивных списков Eclipse Collections, начинает испаряться, поскольку работа, проделанная над каждым элементом, начинает перевешивать затраты на упаковку и распаковку.

В завершение мы могли видеть, что операции с примитивами выполняются быстрее, чем с объектами:

5. Заключение

В этой статье мы создали несколько тестов для сравнения коллекций Java с коллекциями Eclipse. Мы использовали JMH, чтобы попытаться свести к минимуму предвзятость среды.

Как всегда, исходный код доступен на GitHub.