Рубрики
Без рубрики

Эталонные коллекции JDK и коллекции Eclipse

Изучите производительность традиционных коллекций JDK с помощью коллекций Eclipse.

Автор оригинала: 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 List jdkIntList;
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 List jdkIntList;
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 .