Как JVM решила JIT-компиляцию метода (классифицировать метод как горячий)?

Я уже работал с -XX:+PrintCompilation, и я знаю основные приемы JIT-компилятора и почему используется JIT-компиляция.

Тем не менее, я до сих пор не выяснил, как JVM решает JIT-компиляцию метода, т.е. «когда настало подходящее время для JIT-компиляции метода».

Прав ли я в предположении, что каждый метод начинает интерпретироваться, и пока он не классифицируется как «горячий метод», он не будет компилироваться? У меня есть что-то в голове, что я прочитал, что метод считается "горячим", когда он был выполнен не менее 10.000 раз (после интерпретации метода 10.000 раз он будет скомпилирован), но я должен признать, что я не уверен в этом или где я это читал.

Итак, чтобы резюмировать мой вопрос:

(1) Интерпретируется ли каждый метод, если он не был отнесен к категории "горячих" (и, следовательно, был скомпилирован), или существуют ли причины для компиляции методов, даже если они не являются "горячими"? "?

(2) Как JVM разделяет методы на «горячие» и «горячие»? Количество казней? Что-нибудь еще?

(3) Если существуют определенные пороговые значения (например, количество выполнений) для «горячих» методов, существуют ли флаги Java (-XX:...) для установки этих пороговых значений?


person Markus Weninger    schedule 24.02.2016    source источник
comment
Посмотрите на вывод -XX:+PrintFlagsFinal, там много флагов, относящихся к JIT-компиляторам и их уровням, встраиванию, размерам методов, потокам компилятора и т. Д.   -  person the8472    schedule 24.02.2016


Ответы (2)


Политика компиляции HotSpot довольно сложна, особенно для многоуровневой компиляции, которая включена по умолчанию в Java 8. Это не количество выполнений, и не вопрос параметра CompileThreshold.

Лучшее объяснение (по-видимому, единственное разумное объяснение) можно найти в источниках HotSpot, см. advancedThresholdPolicy.hpp.

Я резюмирую основные моменты этой расширенной политики компиляции:

  • Выполнение начинается с уровня 0 (интерпретатор).
  • The main triggers for compilation are
    1. method invocation counter i;
    2. задний счетчик b. Обратные ветки обычно обозначают цикл в коде.
  • Каждый раз, когда счетчики достигают определенного значения частоты (TierXInvokeNotifyFreqLog, TierXBackedgeNotifyFreqLog), вызывается политика компиляции, чтобы решить, что делать дальше с текущим запущенным методом. В зависимости от значений i, b и текущей загрузки потоков компилятора C1 и C2 может быть принято решение

    • continue execution in interpreter;
    • запустить профилирование в интерпретаторе;
    • метод компиляции с C1 на уровне 3 с полными данными профиля, необходимыми для дальнейшей перекомпиляции;
    • метод компиляции с C1 на уровне 2 без профиля, но с возможностью перекомпиляции (маловероятно);
    • наконец, скомпилируйте метод с C1 на уровне 1 без профиля или счетчиков (также маловероятно).

    Ключевыми параметрами здесь являются TierXInvocationThreshold и TierXBackEdgeThreshold. Пороговые значения можно динамически настраивать для данного метода в зависимости от длины очереди компиляции.

  • Очередь компиляции - это не FIFO, а очередь с приоритетом.

  • Скомпилированный C1 код с данными профиля (уровень 3) ведет себя аналогично, за исключением того, что пороговые значения для перехода на следующий уровень (C2, уровень 4) намного больше. Например. Интерпретируемый метод может быть скомпилирован на уровне 3 примерно после 200 вызовов, в то время как метод, скомпилированный с помощью C1, подлежит перекомпиляции на уровне 4 после 5000+ вызовов.

  • Для встраивания методов используется специальная политика. Крошечные методы могут быть встроены в вызывающую программу, даже если они не «горячие». Немного более крупные методы могут быть встроены только в том случае, если они часто вызываются (InlineFrequencyRatio, InlineFrequencyCount).
person apangin    schedule 24.02.2016
comment
Эта ссылка действительно полезна и содержит все, что я хотел знать. Вы правы, трудно найти там разумную информацию, я еще не читал ничего подробного о многоуровневой компиляции и, скорее всего, никогда не прочитает. Спасибо! - person Markus Weninger; 25.02.2016
comment
Ключевым моментом для меня является то, что нормальный путь - это 0 (интерпретируемый) - ›3 (C1, полное профилирование) -› 4 (C2). На этом пути C1 действительно существует только для сбора данных профиля, с которыми C2 будет работать. - person Tom Anderson; 13.08.2016
comment
Тогда есть три второстепенных альтернативных пути. (1) Если компиляция C1 обнаруживает, что метод тривиален, он компилируется с 1 (C1, без профилирования), потому что 4 (C2) не будет быстрее. (2) Если компилятор C2 занят, метод компилируется на 2 (C1, легкое профилирование) до тех пор, пока C2 не станет менее загруженным, после чего он перекомпилируется на 3 C1, полное профилирование), поэтому он может продолжить до 4 ( C2). (3) Если C1 занят, а C2 нет, профилирование выполняется в интерпретаторе, поэтому метод может перейти прямо к C2, не переходя через C1. - person Tom Anderson; 13.08.2016
comment
@apangin оказывается, что общие сведения (включая мои) о 10_000 вызовах неверны. Спасибо за отличный ответ. - person Eugene; 28.12.2016
comment
Если кто-то еще ищет текущую реализацию: AdvancedThresholdPolicy имеет поскольку был объединен с SimpleThresholdPolicy. - person meriton; 02.03.2019
comment
›Наконец скомпилировать метод с C1 на уровне 1 без профиля или счетчиков‹ Означает ли это, что методы уровня 1 не имеют возможности перекомпилировать? Будут ли они перекомпилировать при достижении пороговых значений уровня 4, чтобы обеспечить оптимизацию C2? Спасибо за разъяснение всего этого - практически невозможно найти информацию! - person jocull; 14.12.2019
comment
@jocull Метод компиляции на уровне 1 подразумевает, что HotSpot предполагает, что метод не получит преимуществ от компиляции на более высоком уровне. Обычно это небольшие простые методы, не требующие сложной оптимизации C2. - person apangin; 14.12.2019

Главный параметр для контроля - -XX:CompileThreshold=10000.

Hotspot для Java 8 теперь по умолчанию использует многоуровневую компиляцию с несколькими этапами компиляции от уровня 1 до 4. Я считаю, что 1 - это не оптимизация. Уровень 3 - это C1 (на основе клиентского клиента), а уровень 4 - это C2 (на основе серверного компилятора)

Это означает, что небольшая оптимизация может произойти раньше, чем вы могли ожидать, и она может продолжаться еще долго после достижения порога в 10 КБ. Самое лучшее, что я видел, - это анализ побега, устраняющий StringBuilder после миллиона вызовов.

Примечание: цикл, повторяющийся много раз, может запускать компилятор. например цикла в 10К раз может быть достаточно.

1) Пока метод не считается достаточно популярным, он интерпретируется. Однако некоторые JVM (например, Azul Zing) могут компилировать методы при запуске, и вы можете заставить JVM Hotspot скомпилировать метод через внутренний API. Java 9 также может иметь компилятор AOT (Ahead Of Time), но он все еще исследуется AFAIK

2) Количество вызовов или количество итераций.

3) Да -XX:CompileThreshold=, являясь основным.

person Peter Lawrey    schedule 24.02.2016
comment
У вас есть дополнительные материалы, где я мог бы прочитать об этом? Я знаю про C1 и C2, но не слышал об уровнях компиляции 1-4 (Interpeter, C1, C2 будет 3 уровня). И вы в основном говорите, что метод может считаться популярным ниже CompilerThreshold. Для чего тогда этот порог? Могу ли я предположить, что метод считается горячим не позднее, когда он был вызван CompilerThreshold раз? - person Markus Weninger; 24.02.2016
comment
Извините за уточняющий вопрос, но просто для правильного понимания терминологии: могу ли я назвать метод горячим, если он был оптимизирован хотя бы один раз, или это именно определение горячего метода? - person Markus Weninger; 24.02.2016
comment
@MarkusWeninger Это то, что меняется от обновления к обновлению. Если вы вызовете один и тот же набор кода много раз (но каждый раз), вы увидите, что не все они компилируются сразу. Фактический алгоритм намного сложнее. Я подозреваю, что лучшее место для получения дополнительной информации - это источник. Все остальное может быть устаревшим для текущей JVM. - person Peter Lawrey; 24.02.2016
comment
Компиляция @MarkusWeninger выполняется в фоновом режиме, и если есть много методов для компиляции, это может занять нетривиальное количество времени. Я бы сказал, что метод очень популярен, когда он становится кандидатом на компиляцию. Точно, когда он компилируется и заменяет существующий код, он немного случайен и его трудно контролировать. - person Peter Lawrey; 24.02.2016
comment
Хорошо, тогда спасибо за информацию! Я оставлю этот вопрос открытым еще на некоторое время, чтобы посмотреть, появятся ли новые ответы, в противном случае я приму ваш. :) - person Markus Weninger; 24.02.2016