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 .