且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

拥有许多小方法是否有助于JIT编译器进行优化?

更新时间:2023-11-10 18:54:16

仅限Hotspot JIT内联小于某个(可配置)大小的方法。所以使用较小的方法允许更多的内联,这是好的。

The Hotspot JIT only inlines methods that are less than a certain (configurable) size. So using smaller methods allows more inlining, which is good.

请参阅此页

编辑

详细说明:


  • 如果方法很小,它将被内联,因此很少有机会因为在小方法中拆分代码而受到惩罚。

  • 在某些情况下,拆分方法可能会导致更多内联。

示例(完整代码,如果您尝试使用相同的行号)

Example (full code to have the same line numbers if you try it)

package javaapplication27;

public class TestInline {
    private int count = 0;

    public static void main(String[] args) throws Exception {
        TestInline t = new TestInline();
        int sum = 0;
        for (int i  = 0; i < 1000000; i++) {
            sum += t.m();
        }
        System.out.println(sum);
    }

    public int m() {
        int i = count;
        if (i % 10 == 0) {
            i += 1;
        } else if (i % 10 == 1) {
            i += 2;
        } else if (i % 10 == 2) {
            i += 3;
        }
        i += count;
        i *= count;
        i++;
        return i;
    }
}

使用以下JVM标志运行此代码时: -XX:+ UnlockDiagnosticVMOptions -XX:+ PrintCompilation -XX:FreqInlineSize = 50 -XX:MaxInlineSize = 50 -XX:+ PrintInlining (是的,我使用的值证明了我的情况: m 太大但重构的 m m2 低于阈值 - 其他值可能会得到不同的输出。)

When running this code with the following JVM flags: -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:FreqInlineSize=50 -XX:MaxInlineSize=50 -XX:+PrintInlining (yes I have used values that prove my case: m is too big but both the refactored m and m2 are below the threshold - with other values you might get a different output).

你会看到 m() main()得到编译,但是 m()没有内联:

You will see that m() and main() get compiled, but m() does not get inlined:

 56    1             javaapplication27.TestInline::m (62 bytes)
 57    1 %           javaapplication27.TestInline::main @ 12 (53 bytes)
          @ 20   javaapplication27.TestInline::m (62 bytes)   too big

您还可以检查生成程序集以确认 m 未内联(我使用了这些JVM标志: -XX:+ PrintAssembly -XX:Prin tAssemblyOptions = intel ) - 它将如下所示:

You can also inspect the generated assembly to confirm that m is not inlined (I used these JVM flags: -XX:+PrintAssembly -XX:PrintAssemblyOptions=intel) - it will look like this:

0x0000000002780624: int3   ;*invokevirtual m
                           ; - javaapplication27.TestInline::main@20 (line 10)

如果你像这样重构代码(我在单独的方法中提取了if / else):

If you refactor the code like this (I have extracted the if/else in a separate method):

public int m() {
    int i = count;
    i = m2(i);
    i += count;
    i *= count;
    i++;
    return i;
}

public int m2(int i) {
    if (i % 10 == 0) {
        i += 1;
    } else if (i % 10 == 1) {
        i += 2;
    } else if (i % 10 == 2) {
        i += 3;
    }
    return i;
}

您将看到以下编译操作:

You will see the following compilation actions:

 60    1             javaapplication27.TestInline::m (30 bytes)
 60    2             javaapplication27.TestInline::m2 (40 bytes)
            @ 7   javaapplication27.TestInline::m2 (40 bytes)   inline (hot)
 63    1 %           javaapplication27.TestInline::main @ 12 (53 bytes)
            @ 20   javaapplication27.TestInline::m (30 bytes)   inline (hot)
            @ 7   javaapplication27.TestInline::m2 (40 bytes)   inline (hot)

所以 m2 被内联到 m ,你会期望我们回到原来的情景。但是当 main 被编译时,它实际上是整个内容的内联。在汇编级别,这意味着您将不再找到任何 invokevirtual 指令。你会发现这样的行:

So m2 gets inlined into m, which you would expect so we are back to the original scenario. But when main gets compiled, it actually inlines the whole thing. At the assembly level, it means you won't find any invokevirtual instructions any more. You will find lines like this:

 0x00000000026d0121: add    ecx,edi   ;*iinc
                                      ; - javaapplication27.TestInline::m2@7 (line 33)
                                      ; - javaapplication27.TestInline::m@7 (line 24)
                                      ; - javaapplication27.TestInline::main@20 (line 10)

其中基本上常见的指令是共同的 。

where basically common instructions are "mutualised".

结论

我不是说这个例子具有代表性但是它似乎证明了几点:

I am not saying that this example is representative but it seems to prove a few points:


  • 使用更小的方法提高了代码的可读性

  • 更小的方法通常会内联,因此您很可能不会支付额外方法调用的成本(它将是性能中立的)

  • 使用较小的方法可能改进内联在某些情况下全局,如上例所示

  • using smaller method improves readability in your code
  • smaller methods will generally be inlined, so you will most likely not pay the cost of the extra method call (it will be performance neutral)
  • using smaller methods might improve inlining globally in some circumstances, as shown by the example above

最后:如果你的代码的一部分对性能非常重要考虑因素很重要,您应该检查JIT输出以微调您的代码,并重要地在之前和之后进行配置。

And finally: if a portion of your code is really critical for performance that these considerations matter, you should examine the JIT output to fine tune your code and importantly profile before and after.