Автор оригинала: Rodrigo Graciano.
1. введение
В этом уроке мы сравним производительность традиционных коллекций JDK с коллекциями Eclipse. Мы создадим различные сценарии и изучим результаты.
2. Конфигурация
Во-первых, обратите внимание, что в этой статье мы будем использовать конфигурацию по умолчанию для запуска тестов. Никакие флаги или другие параметры не будут установлены на нашем бенчмарке.
Мы будем использовать следующее оборудование и библиотеки:
- JDK 11.0.3, 64-разрядная серверная виртуальная машина Java HotSpot(TM), 11.0.3+12-LTS.
- Mac Pro 2,6 ГГц 6-ядерный i7 с 16 ГБ памяти DDR4.
- Коллекции Eclipse 10.0.0 (последняя версия доступна на момент написания статьи)
- Мы будем использовать JMH (Java Microbenchmark Harness) для запуска наших тестов
- Визуализатор JMH для создания диаграмм из результатов JMH
Самый простой способ создать наш проект-через командную строку:
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 :
org.eclipse.collections eclipse-collections 10.0.0 org.eclipse.collections eclipse-collections-api 10.0.0
3. Первый контрольный показатель
Наш первый ориентир прост. Мы хотим вычислить сумму ранее созданного Списка целых чисел .
Мы протестируем шесть различных комбинаций, выполняя их последовательно и параллельно:
private ListjdkIntList; private MutableList 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
Это вызовет тест в нашем Целочисленном списке некоторого класса и сохранит результат в файл 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 имел самую высокую пропускную способность из всех. Кроме того, он был наиболее эффективным с производительностью почти в 10 раз быстрее, чем Java JDK, работающий также параллельно.
Конечно, отчасти это можно объяснить тем фактом , что при работе с примитивными списками у нас нет затрат, связанных с боксом и распаковкой.
Мы можем использовать визуализатор JMH для анализа наших результатов. На приведенной ниже диаграмме показана лучшая визуализация:
4. Фильтрация
Затем мы изменим наш список, чтобы получить все элементы, кратные 5. Мы повторно используем большую часть нашего предыдущего бенчмарка и функцию фильтра:
private ListjdkIntList; private MutableList 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 jdkList() { return jdkIntList.stream().filter(i -> i % 5 == 0).collect(Collectors.toList()); } @Benchmark public MutableList ecMutableList() { return ecMutableList.select(i -> i % 5 == 0); } @Benchmark public List jdkListParallel() { return jdkIntList.parallelStream().filter(i -> i % 5 == 0).collect(Collectors.toList()); } @Benchmark public MutableList 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 снова стал победителем. С пропускной способностью более чем в 2 раза быстрее, чем параллельный список JDK.
Обратите внимание, что для фильтрации эффект параллельной обработки более заметен. Суммирование-это дешевая операция для процессора, и мы не увидим одинаковых различий между последовательным и параллельным.
Кроме того, повышение производительности, которое ранее получили примитивные списки коллекций Eclipse, начинает испаряться, поскольку работа, проделанная над каждым элементом, начинает перевешивать затраты на упаковку и распаковку.
Чтобы завершить, мы могли видеть, что операции с примитивами выполняются быстрее, чем с объектами:
5. Заключение
В этой статье мы создали несколько тестов для сравнения коллекций Java с коллекциями Eclipse. Мы использовали JMH, чтобы попытаться свести к минимуму влияние окружающей среды.
Как всегда, исходный код доступен на GitHub .