Статьи

Низкий GC в Java: используйте примитивы вместо оболочек

обзор
Есть две веские причины использовать примитивы вместо оболочек, где это возможно.

  • Ясность. Используя примитив, вы даете понять, что нулевое значение не подходит.
  • Производительность. Использование примитивов часто намного быстрее.

Четкость часто важнее, чем производительность, и является лучшей причиной для их использования. Тем не менее, в этой статье обсуждались последствия использования оберток для производительности.

У меня был большой интерес к этой статье, как избежать сбора мусора , однако в ней не хватало многих практических деталей. Это первая статья в серии о способах снижения требований к GC.

Производительность использования обёрток
Следующий микро-тест ведет себя так, как это делают многие приложения.

Цикл с использованием Wrappers и Wrapper Collection

01
02
03
04
05
06
07
08
09
10
11
12
Map<Integer, Integer> counters = new HashMap<Integer, Integer>();
int runs = 20 * 1000;
for (Integer i = 0; i < runs; i++) {
    Integer x = i % 12;
    Integer y = i / 12 % 12;
    Integer times = x * y;
    Integer count = counters.get(times);
    if (count == null)
        counters.put(times, 1);
    else
        counters.put(times, count + 1);
}

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

Примерно за 6 минут было 20 второстепенных сборов.
Среднее время каждого цикла составляет субмикросекунду, что довольно быстро.

Взял 4099 нс за цикл
Взял 559 нс за цикл
Взял 115 нс за цикл
Взял 240 нс за цикл
Взял 255 нс за цикл
В первом тесте JVM не прогрелась.

Может ли использование примитивов действительно иметь большое значение?

Производительность использования примитивов
Следующий бенчмарк ведет себя несколько иначе, чем в большинстве приложений. Даже если он выполняет ту же работу, что и предыдущий тест, объекты не создаются.

Цикл с использованием примитивов и массивов

1
2
3
4
5
6
7
8
int[] counters = new int[144];
int runs = 20 * 1000;
for (int i = 0; i < runs; i++) {
    int x = i % 12;
    int y = i / 12 % 12;
    int times = x * y;
    counters[times]++;
}

и использование кучи отражает это

Там не было GCs в течение 5 минут. Тест мог длиться дольше и все еще не вызвал сборщик мусора.
И среднее время за цикл также намного ниже

Взял 198 нс за цикл
Взял 17 нс за цикл
Взял 16 нс за цикл
Взял 14 нс за цикл
Взял 15 нс за цикл

В первом тесте JVM не прогрелась.

Вывод
Использование примитивов будет работать лучше. (Если нет чрезмерного бокса и распаковки)

Даже в приложениях, где производительность не критична, это улучшит четкость кода, а когда вы попытаетесь профилировать свое приложение, это уменьшит уровень «шума», делая его более понятным в отношении проблемы.

Заметки
Даже в тесте, где было создано несколько объектов, вы можете увидеть распределение объектов. В основном это связано с опросом VisualVM. Чтобы уменьшить это, я изменил интервал опроса с 3 до 20 секунд.

Размер Eden был увеличен, чтобы сделать графики более четкими, с -XX: NewSize = 100m. Это значение не рекомендуется (за исключением, возможно, микропроцессоров), но это параметр, который вам может потребоваться настроить для вашего приложения.

Полный код