在最近关于如何优化某些代码的讨论中,我被告知将代码分解为许多小方法可以显着提高性能,因为JIT编译器不喜欢优化大型方法。
In a recent discussion about how to optimize some code, I was told that breaking code up into lots of small methods can significantly increase performance, because the JIT compiler doesn't like to optimize large methods.
我不确定这一点,因为看起来JIT编译器本身应该能够识别自包含的代码段,而不管它们是否采用自己的方法。
I wasn't sure about this since it seems that the JIT compiler should itself be able to identify self-contained segments of code, irrespective of whether they are in their own method or not.
任何人都可以确认或反驳这项索赔吗?
Can anyone confirm or refute this claim?
推荐答案仅限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.
更多推荐
拥有许多小方法是否有助于JIT编译器进行优化?
发布评论