RKDEEP

Метод inlining в JVM

Kirill Rybkin2022-04-11

Что такое Inlining?

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

Stack memory

Какие флаги влияют на 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"