«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.