JavaRush /Java блог /Архив info.javarush /Boxing/Unboxing в цикле
LuisBoroda
28 уровень

Boxing/Unboxing в цикле

Статья из группы Архив info.javarush
При изучении основ Java я частенько сталкивался с различными рекомендациями по использованию типов данных. В один прекрасный день мне захотелось чуть глубже разобраться и узнать, что же на самом деле происходит под капотом, а не просто запомнить, как правильно. Я не стал разбирать всем приевшийся пример с конкатенацией строк и решил разобрать не менее приевшийся пример с авто-упаковкой/распаковкой типов). Для начала стоит сказать пару слов о JVM. JVM является стековой машиной. Грубо говоря, каждый раз, когда мы что-то делаем с переменными – они сначала помещаются в стек операндов, который формируется для каждого метода, а затем производятся необходимые вычисления. Не вдаваясь в детали выглядит это так: Boxing/Unboxing в цикле - 1 Итак, предположим, что у нас есть задача по вычислению арифметической прогрессии от 0 до 1 000 000 000 с шагом 1.

Случай 1

Предположим, что мы написали такой код: public class Test { public static void main(String[] args) { Long result = 0L; for (int i = 0; i < 1_000_000_000; i++) { result += i; } System.out.println("Result:" + result); } } Будет ли он выполнять поставленную задачу? Да, но будет ли он оптимальным? Разберёмся. Для начала давайте посмотрим на байт-код, который получился (его можно получить так: javap –c Test.class). Вот в такую ужасающую конструкцию преобразился наш цикл for: 10: if_icmpge 30 13: aload_1 14: invokevirtual #4 // Method java/lang/Long.longValue:()J 17: iload_2 18: i2l 19: ladd 20: invokestatic #2 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 23: astore_1 24: iinc 2, 1 27: goto 7 Давайте по порядку:
  • 13 – Помещаем переменную Long result в стек операндов
  • 14 – Распаковываем ссылочный Long в примитивный long
  • 17 – Помещаем в стек операндов переменную int i
  • 18 – преобразуем её к типу long
  • 19 – теперь складываем long result и long i
  • 20 – запаковываем результат в ссылочный тип Long
  • 23 – и сохраняем обратно в переменную.
Таким образом, в нашем цикле упаковка и распаковка выполнятся, всего-то, по миллиарду раз. Помимо этого, тип Long является immutable, а это значит, что после каждого вызова метода упаковки Long.valueOf() (строка №20 байт-кода) мы получаем новый объект, при этом старый объект остаётся в памяти и ждёт, когда за ним придёт GC. В итоге, к концу работы программы, сборщик мусора на моей машине срабатывал в среднем 46 раз.

Случай 2

Попробуем заменить тип переменной result на примитивный long: public class Test { public static void main(String[] args) { long result = 0; for (int i = 0; i < 1_000_000_000; i++) { result += i; } System.out.println("Result:" + result); } } Посмотрим, что получилось. Байт-кода стало меньше. 7: if_icmpge 21 10: lload_1 11: iload_3 12: i2l 13: ladd 14: lstore_1 15: iinc 3, 1 18: goto 4
  • 10 – Помещаем в стек операндов нашу переменную result
  • 11 – Помещаем в стек операндов переменную int i
  • 12 – преобразуем int i в long i
  • 13 – складываем long result и long i
  • 14 – сохраняем результат обратно в локальную переменную
Сразу можно заметить, что мы избавились от ненужных операций распаковки/упаковки. А ещё, используя примитивные типы, мы не заставляем память хранить кучу ненужных объектов. Теперь давайте посмотрим на время выполнения обоих примеров:
  • Случай 1 – в среднем 5100 мс
  • Случай 2 – в среднем 535 мс
Почти 10-кратный прирост по скорости. Таким образом, если в коде используются классы-обёртки и с ними иногда необходимо проводить некоторые арифметические операции, то лучше производить распаковку заранее, не полагаясь на то, что JVM всё за вас оптимизирует, как я раньше и считал. P.S. Заметку писал для себя, но вдруг кому-нибудь окажется полезной. И, конечно, замечания, критика и дополнения приветствуются.
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
maks1m Уровень 25
24 сентября 2015
Вроде как все логично.
Еще нужно таким образом проверить правда ли что компилятор оптимизирует конкатенацию строк через StringBuilder. Интересно или описанный в статье пример можно принудительно оптимизировать через флаг jvm.
EvIv Уровень 30
23 сентября 2015
Отличная иллюстрация "дырявых абстракций" и того, что заглянуть под капот бывает очень полезно и познавательно!
Только пугает, что этих «капотов», одного под другим, сейчас существует десятки и сотни (если еще и энтерпрайз, с его спрингами и хайбернейтами рассматривать) и не под силу человеческому мозгу охватить их все. Эх, где времена бейсика и асма =))))))