Перейти к основному содержимому

Сравнение коллекций JDK и коллекций Eclipse

· 5 мин. чтения

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

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

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

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

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

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

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

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 для анализа наших результатов. На приведенной ниже диаграмме показана лучшая визуализация:

./acd7364b91d019de2f90a4949ec197a2.png

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, начинает испаряться, поскольку работа, выполняемая над каждым элементом, начинает перевешивать затраты на упаковку и распаковку.

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

./02c175833f75009be48d6c1c2073c2eb.png

5. Вывод

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

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