Ответ 1
Тот факт, что то, что вы видите, является результатом некоторой оптимизации JIT, должно быть ясно, глядя на все полученные вами комментарии. Но что действительно происходит и почему этот код оптимизирован всегда почти после того же количества итераций внешнего for
?
Я постараюсь ответить на оба вопроса, но помните, что все объяснение здесь относительное только для Oracle Hotspot VM. Нет спецификации Java, которая определяет, как должна работать JVM JIT.
Прежде всего, посмотрим, что делает JIT, выполняющая программу тестирования с некоторым дополнительным флагом (достаточно простого JVM для ее запуска, нет необходимости загружать разделяемую библиотеку отладки, необходимую для некоторых параметров UnlockDiagnosticVMOptions
):
java -XX:+PrintCompilation Test
Выполнение завершается с помощью этого вывода (удаление нескольких строк в начале, которые показывают, что компилируются другие методы):
[...]
195017
184573
184342
184262
183491
189494
131 51% 3 Test::getTimes @ 2 (22 bytes)
245167
132 52 3 Test::getTimes (22 bytes)
165144
65090
132 53 1 java.nio.Buffer::limit (5 bytes)
59427
132 54% 4 Test::getTimes @ 2 (22 bytes)
75137
48110
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
150820
86951
90012
91421
printlns
из вашего кода чередуются с диагностической информацией, связанной с компиляцией, выполняемой JIT.
Просмотр одной строки:
131 51% 3 Test::getTimes @ 2 (22 bytes)
Каждый столбец имеет следующее значение:
- Отметка
- Идентификатор компиляции (с дополнительными атрибутами при необходимости)
- Уровень многоуровневой сборки
- Краткое краткое имя метода (при наличии < <26 > , если доступно)
- Скомпилированный размер метода
Сохранение только строк, связанных с getTimes
:
131 51% 3 Test::getTimes @ 2 (22 bytes)
132 52 3 Test::getTimes (22 bytes)
132 54% 4 Test::getTimes @ 2 (22 bytes)
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
Ясно, что getTimes
компилируется более одного раза, но каждый раз, когда он скомпилирован по-другому.
Этот символ %
означает, что была выполнена замена на стеке (OSR), что означает, что цикл 10k, содержащийся в getTimes
, был скомпилирован отдельно от остальной части метода и что JVM заменил этот раздел код метода с скомпилированной версией. osr_bci
- это индекс, который указывает на этот новый скомпилированный блок кода.
Следующая компиляция представляет собой классическую компиляцию JIT, которая компилирует весь метод getTimes
(размер все тот же, поскольку в этом методе не существует ничего другого, кроме цикла).
В третий раз выполняется другая OSR, но на другом уровне. Многоуровневая компиляция была добавлена в Java7 и в основном позволяет JVM выбирать режим JIT клиента или сервера во время выполнения, свободно переключаясь между ними, когда это необходимо. Режим "Клиент" выполняет более простой набор стратегий оптимизации, в то время как режим сервера способен применять более сложные оптимизации, которые, с другой стороны, имеют большую стоимость с точки зрения времени, затрачиваемого на компиляцию.
Я не буду вдаваться в подробности о разных режимах или о многоуровневой компиляции, если вам нужна дополнительная информация, я рекомендую Производительность Java: окончательное руководство Scott Oaks, а также проверить этот вопрос, который объясняет, какие изменения между уровнями.
Возвращаясь к выводу PrintCompilation, суть в том, что с определенного момента времени последовательность компиляций с возрастающей сложностью выполняется до тех пор, пока метод не станет, по-видимому, стабильным (т.е. JIT не скомпилирует его снова).
Итак, почему все это начинается в тот определенный момент времени, после 5-10 повторений основного цикла?
Поскольку внутренний цикл getTimes
стал "горячим".
Hotspot VM обычно определяет "горячие" те методы, которые были вызваны не менее чем 10k раз (что порог исторического значения по умолчанию можно изменить с помощью -XX:CompileThreshold=<num>
, при многоуровневой компиляции теперь имеется несколько пороговых значений), но в случае из OSR я предполагаю, что он выполнялся, когда блок кода считается "горячим" достаточно, в течение абсолютного или относительного времени выполнения внутри метода содержит его.
Дополнительные ссылки
Руководство по PrintCompilation от Krystal Mok