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