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

Сравнение производительности примитивных списков в Java

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

1. Обзор

В этом уроке мы собираемся сравнить производительность некоторых популярных библиотек примитивных списков в Java .

Для этого мы протестируем методы add(), get() и contains() для каждой библиотеки.

2. Сравнение производительности

Теперь давайте выясним, какая библиотека предлагает быстро работающий API коллекций примитивов .

Для этого сравним аналоги List от Trove, Fastutil и Colt . Мы будем использовать инструмент JMH (Java Microbenchmark Harness) для написания тестов производительности.

2.1. Параметры JMH

Мы запустим наши тесты производительности со следующими параметрами:

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Measurement(batchSize = 100000, iterations = 10)
@Warmup(batchSize = 100000, iterations = 10)
@State(Scope.Thread)
public class PrimitivesListPerformance {
}

Здесь мы хотим измерить время выполнения для каждого метода тестирования. Кроме того, мы хотим отображать наши результаты в миллисекундах.

Аннотация @State указывает , что переменные, объявленные в классе, не будут участвовать в выполнении эталонных тестов. Однако затем мы можем использовать их в наших методах тестирования.

Кроме того, давайте определим и инициализируем наши списки примитивов:

public static class PrimitivesListPerformance {
private List<Integer> arrayList = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
private TIntArrayList tList = new TIntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
private cern.colt.list.IntArrayList coltList = new cern.colt.list.IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
private IntArrayList fastUtilList = new IntArrayList(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});

private int getValue = 4;
}

Теперь мы готовы написать наши тесты.

3. добавить()

Во-первых, давайте проверим добавление элементов в наши примитивные списки. Мы также добавим один для ArrayList в качестве нашего элемента управления.

3.1. Сравнительные тесты

Первый микротест для метода add () ArrayList : ``

@Benchmark
public boolean addArrayList() {
return arrayList.add(getValue);
}

Точно так же для Trove TIntArrayList.add() :

@Benchmark
public boolean addTroveIntList() {
return tList.add(getValue);
}

Аналогично IntArrayList.add() в Colt выглядит так:

@Benchmark
public void addColtIntList() {
coltList.add(getValue);
}

И для библиотеки Fastutil тест метода IntArrayList.add() будет:

@Benchmark
public boolean addFastUtilIntList() {
return fastUtilList.add(getValue);
}

3.2. Результаты теста

Теперь запускаем и сравниваем результаты:

Benchmark           Mode  Cnt  Score   Error  Units
addArrayList ss 10 4.527 ± 4.866 ms/op
addColtIntList ss 10 1.823 ± 4.360 ms/op
addFastUtilIntList ss 10 2.097 ± 2.329 ms/op
addTroveIntList ss 10 3.069 ± 4.026 ms/op

Из результатов ясно видно, что метод add() в ArrayList является самым медленным вариантом.

Это логично, как мы объяснили в статье о библиотеках примитивных списков , ArrayList будет использовать упаковку/автоупаковку для хранения значений int внутри коллекции. Поэтому у нас здесь значительное замедление. ``

С другой стороны, методы add() для Colt и Fastutil были самыми быстрыми.

Под капотом все три библиотеки хранят значения внутри int[] . Так почему же у нас разное время выполнения их методов add() ?

Ответ заключается в том, как они увеличивают int[] , когда емкость по умолчанию заполнена:

  • Colt будет увеличивать свой внутренний int[] только тогда, когда он заполнится
  • Напротив, Trove и Fastutil будут использовать некоторые дополнительные вычисления при расширении контейнера int[]

Вот почему Colt побеждает в наших результатах испытаний.

4. получить ()

Теперь давайте добавим микротест операции get() .

4.1. Сравнительные тесты

Во-первых, для операции get () ArrayList : ``

@Benchmark
public int getArrayList() {
return arrayList.get(getValue);
}

Точно так же для TIntArrayList Trove у нас будет:

@Benchmark
public int getTroveIntList() {
return tList.get(getValue);
}

А для Colt cern.colt.list.IntArrayList метод get() будет таким:

@Benchmark
public int getColtIntList() {
return coltList.get(getValue);
}

Наконец, для IntArrayList Fastutil мы протестируем операцию getInt() :

@Benchmark
public int getFastUtilIntList() {
return fastUtilList.getInt(getValue);
}

4.2. Результаты теста

После запускаем бенчмарки и смотрим результаты:

Benchmark           Mode  Cnt  Score   Error  Units
getArrayList ss 20 5.539 ± 0.552 ms/op
getColtIntList ss 20 4.598 ± 0.825 ms/op
getFastUtilIntList ss 20 4.585 ± 0.489 ms/op
getTroveIntList ss 20 4.715 ± 0.751 ms/op

Хотя разница в баллах невелика, мы можем заметить, что getArrayList() работает медленнее.

Для остальных библиотек у нас почти идентичные реализации метода get() . Они будут немедленно извлекать значение из int[] без какой-либо дополнительной работы. Вот почему Colt, Fastutil и Trove имеют одинаковую производительность для операции get() .

5. содержит()

Наконец, давайте протестируем метод contains() для каждого типа списка.

5.1. Сравнительные тесты

Давайте добавим первый микротест для метода contains() в ArrayList : ``

@Benchmark
public boolean containsArrayList() {
return arrayList.contains(getValue);
}

Точно так же для TIntArrayList Trove эталонный тест contains() будет:

@Benchmark
public boolean containsTroveIntList() {
return tList.contains(getValue);
}

Точно так же тест для cern.colt.list.IntArrayList.contains() от Colt :

@Benchmark
public boolean containsColtIntList() {
return coltList.contains(getValue);
}

А для Fastutil IntArrayList тест метода contains() выглядит так:

@Benchmark
public boolean containsFastUtilIntList() {
return fastUtilList.contains(getValue);
}

5.2. Результаты теста

Наконец, мы запускаем наши тесты и сравниваем результаты:

Benchmark                  Mode  Cnt   Score    Error  Units
containsArrayList ss 20 2.083 ± 1.585 ms/op
containsColtIntList ss 20 1.623 ± 0.960 ms/op
containsFastUtilIntList ss 20 1.406 ± 0.400 ms/op
containsTroveIntList ss 20 1.512 ± 0.307 ms/op

Как обычно, худшая производительность у метода containsArrayList . Напротив, Trove, Colt и Fastutil имеют более высокую производительность по сравнению с основным решением Java.

На этот раз выбор библиотеки зависит от разработчика. Результаты для всех трех библиотек достаточно близки, чтобы считать их идентичными.

6. Заключение

В этой статье мы исследовали фактическую производительность примитивных списков во время выполнения с помощью эталонных тестов JVM. Более того, мы сравнили результаты теста с ArrayList от JDK .

Кроме того, имейте в виду, что цифры, которые мы представляем здесь, являются просто результатами тестов JMH — всегда тестируйте в рамках данной системы и среды выполнения.

Как обычно, полный код для этой статьи доступен на GitHub .