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 ListarrayList = 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. Контрольные тесты
Первый микро-бенчмарк предназначен для метода ArrayList ‘ |/s add() :
@Benchmark public boolean addArrayList() { return arrayList.add(getValue); }
Аналогично, для клада TIntArrayList.add() :
@Benchmark public boolean addTroveIntList() { return tList.add(getValue); }
Аналогично, Colt IntArrayList.add() выглядит следующим образом:
@Benchmark public void addColtIntList() { coltList.add(getValue); }
А для библиотеки Fastutil эталоном метода IntArrayList.add() будет:
@Benchmark public boolean addFastUtilIntList() { return fastUtilList.add(getValue); }
А для библиотеки Fastutil эталоном метода || IntArrayList.add() || будет:
Теперь мы запускаем и сравниваем результаты:
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
Из результатов мы ясно видим, что Arraylist add() является самым медленным вариантом.
Это логично, как мы объяснили в статье библиотеки примитивных списков |, ArrayList будет использовать бокс/автобокс для хранения значений int внутри коллекции. Поэтому у нас здесь наблюдается значительное замедление темпов роста.
С другой стороны, методы add() для Colt и Fastutil были самыми быстрыми.
Под капотом все три библиотеки хранят значения внутри int[] . Так почему же у нас разное время выполнения для их методов add () ?
Ответ заключается в том, как они увеличивают int [] , когда емкость по умолчанию заполнена:
- Кольт вырастит свой внутренний int[] только когда он станет полным
- В отличие от этого, Trove и Fastutil будут использовать некоторые дополнительные вычисления при расширении контейнера int[]
Вот почему Кольт выигрывает в результатах наших тестов.
4. получить()
Теперь давайте добавим микро-бенчмарк get() operation.
4.1. Контрольные тесты
Во – первых, для операции ArrayList’ s get() :
@Benchmark public int getArrayList() { return arrayList.get(getValue); }
Аналогично, для клада ТИнтАррайЛист у нас будет:
@Benchmark public int getTroveIntList() { return tList.get(getValue); }
И, для Кольта cern.colt.list.IntArrayList, метод get() будет:
@Benchmark public int getColtIntList() { return coltList.get(getValue); }
Наконец, для Fastutil’s IntArrayList мы протестируем операцию 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. Контрольные тесты
Давайте добавим первый микро-бенчмарк для метода ArrayList’ s contains() :
@Benchmark public boolean containsArrayList() { return arrayList.contains(getValue); }
Аналогично, для клада TIntArrayList |/содержит() бенчмарк будет:
@Benchmark public boolean containsTroveIntList() { return tList.contains(getValue); }
Аналогично, тест для Кольта cern.colt.list.IntArrayList.contains() is:
@Benchmark public boolean containsColtIntList() { return coltList.contains(getValue); }
И, для Fastutil’s 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
Как обычно, метод contains ArrayList имеет худшую производительность . Напротив, Trove, Colt и Fastutil имеют лучшую производительность по сравнению с основным решением Java.
На этот раз выбор библиотеки зависит от разработчика. Результаты для всех трех библиотек достаточно близки, чтобы считать их идентичными.
6. Заключение
В этой статье мы исследовали фактическую производительность примитивных списков во время выполнения с помощью тестов JVM benchmark. Кроме того, мы сравнили результаты тестирования с ArrayList JDK .
Кроме того, имейте в виду, что цифры, которые мы представляем здесь, являются просто результатами теста JMH – всегда тестируйте в рамках данной системы и среды выполнения.
Как обычно, полный код этой статьи доступен на GitHub .