Перейти к основному содержимому

Метод inlining JVM

Что такое Inlining?

Inlining - оптимизации, при которой вызов функции заменяется непосредственно её телом. При вызове метода JVM должна поместить новый кадр стека в стек, после завершения выполнения методе вернуться к вызывающему методу. Inlining повышает производительность т.к. помогает избежать этих накладных расходов. Docs Version Dropdown

Какие флаги влияют на inlining при компиляции?

-XX:+PrintFlagsFinal - показывает с какими флагами запушено приложение и default значения.

Нас интересуют те которые влияют на inlining:

  • CompileThreshold - количество вызовов метода, перед тем как метод будет считаться горячим.
  • MaxInlineLevel - лимит глубины вызовов методов. Значение по умолчанию 9.
  • MaxInlineSize - максимальный размер метода в байткодах, для inlining. Значение по умолчанию 35. Если метод не горячий и его значение более 35 байт он не будет встроен.
  • FreqInlineSize - определяет максимальный размер горячих методов. Зависит от платформы у меня 325 байт.

Как JIT определяет какой метод встраивать?

JIT compiler пытается выполнить inlining основываясь на 2-х вещах:

  • Как часто метод вызывался.
  • Какой размер метода.

Главное решение относительно того, стоит ли встраивать метод, зависит от его размера и от того, насколько он горяч. JVM определяет, является ли метод горячим, на основании своих внутренних вычислений; никакие настраиваемые параметры не управляют напрямую этим процессом. Если метод признается пригодным для встраивания, потому что он часто вызывается, он будет встроен только в том случае, если размер его байткода менее 325 байт (или размера, заданного флагом -XX:MaxFreqInlineSize=N). В противном случае он будет признан пригодным для встраивания только в том случае, если его размер менее 35 байт (или другого значения, заданного флагом -XX:MaxInlineSize=N)

Так же, JIT компилятор должен убедиться, что имеется одна реализация метода. Поэтому inlining применим к staic, private, final методам, а public методы встраиваются если не изменяется их тело.

Пример Inlining

Рассмотри пример из листинга ниже

public class App {
public static void main(String[] args) {
long upto = Long.parseLong(args[0]);

for(int i = 0; i < upto; i++) {
int x = inline1();
}
}

public static int inline1() {
return inline2();
}

public static int inline2() {
int x = 3;
int y = inline3() + x;
return y;
}

public static int inline3() {
return inline4();
}

public static int inline4() {
return 3;
}
}
mvn verify
java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -cp target/inlining-1.0-SNAPSHOT.jar com.rkdeep.App 10000

Сообщения компиляции

  1. "callee is too large" сообщение выводится компилятором C1, когда размер байт кода візіваемго метода больше заданого в параметре VM -XX:MaxInlineSize=35.
  2. "too big" и "hot method too big" - сообщение выводится компилятром С2, когда размер inlining метода больше MaxInlineSize (35) или FreqInlineSize (325) соответственно. Поэтому оба сообщения означают приблизительно одинаковые вещи, но на различных стадиях компиляции.

Результат выполнения:

    130   90       3       com.rkdeep.App::inline2 (4 bytes)
@ 0 com.rkdeep.App::inline3 (4 bytes) inline
@ 0 com.rkdeep.App::inline4 (2 bytes) inline
130 88 1 com.rkdeep.App::inline4 (2 bytes)
130 89 3 com.rkdeep.App::inline1 (4 bytes)
@ 0 com.rkdeep.App::inline2 (4 bytes) inline
@ 0 com.rkdeep.App::inline3 130 92 4 com.rkdeep.App::inline2 (4 bytes)
(4 bytes) inline
@ 0 com.rkdeep.App::inline4 (2 bytes) inline
130 91 2 com.rkdeep.App::inline3 (4 bytes)
@ 0 com.rkdeep.App::inline4 (2 bytes) inline
131 90 3 com.rkdeep.App::inline2 (4 bytes) made not entrant
@ 0 com.rkdeep.App::inline3 (4 bytes) inline (hot)
@ 0 com.rkdeep.App::inline4 (2 bytes) inline (hot)
131 93 4 com.rkdeep.App::inline1 (4 bytes)
131 89 3 com.rkdeep.App::inline1 (4 bytes) made not entrant
@ 0 com.rkdeep.App::inline2 (4 bytes) inline (hot)
@ 0 com.rkdeep.App::inline3 (4 bytes) inline (hot)
@ 0 com.rkdeep.App::inline4 (2 bytes) inline (hot)

Добавим флаг компиляции -XX:FreqInlineSize=8

    118   95 %     4       com.rkdeep.App::main @ 9 (28 bytes)
118 93 % 3 com.rkdeep.App::main @ 9 (28 bytes) made not entrant
@ 16 com.rkdeep.App::inline1 (4 bytes) inline (hot)
@ 0 com.rkdeep.App::inline2 (10 bytes) too big

Видим, что метод inline2 с комментарием компиляции "too big" т.к. мы выставили размер 8 байт для метода.

Иногда встречаются рекомендации, которые предлагают увеличить значение флага MaxInlineSize, чтобы встраивание применялось к большему количеству методов. При этом часто упускается из виду один аспект: присваивание MaxInlineSize значения, превышающего 35, означает, что метод будет встроен при первом вызове. Но если метод вызывается часто — а в этом случае его производительность начинает играть намного более важную роль, — он все равно будет встроен со временем (если его размер менее 325 байт). В противном случае изменение флага MaxInlineSize может сократить время разогрева, необходимое для теста, но вряд ли оно окажет значительное влияние на приложение, которое выполняется достаточно долго.

Материалы

  1. "Java: оптимизация программ" Бенджамин Эванс, Джеймс Гоф и Kpuc Ньюланд.
  2. "Эффективный Java. Тюнинг кода на Java 8, 11 и дальше" Оукс Скотт.
  3. "Java HotSpot VM Options"