Метод inlining JVM
Что такое Inlining?
Inlining - оптимизации, при которой вызов функции заменяется непосредственно её телом. При вызове метода JVM должна поместить новый кадр стека в стек, после завершения выполнения методе вернуться к вызывающему методу. Inlining повышает производительность т.к. помогает избежать этих накладных расходов.
Какие флаги влияют на 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
Сообщения компиляции
- "callee is too large" сообщение выводится компилятором C1, когда размер байт кода візіваемго метода больше заданого в параметре VM -XX:MaxInlineSize=35.
- "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 может сократить время разогрева, необходимое для теста, но вряд ли оно окажет значительное влияние на приложение, которое выполняется достаточно долго.
Материалы
- "Java: оптимизация программ" Бенджамин Эванс, Джеймс Гоф и Kpuc Ньюланд.
- "Эффективный Java. Тюнинг кода на Java 8, 11 и дальше" Оукс Скотт.
- "Java HotSpot VM Options"