拥有许多小方法是否有助于JIT编译器进行优化?
在最近关于如何优化某些代码的讨论中,我被告知将代码分解为许多小方法可以显着提高性能,因为JIT编译器不喜欢优化大型方法。
我不确定这一点,因为看起来JIT编译器本身应该能够识别自包含的代码段,而不管它们是否在自己的方法中。
任何人都可以确认或反驳这种说法吗?
Hotspot JIT仅内联小于特定(可配置)大小的方法。 因此,使用较小的方法可以实现更多的内联,这是很好的。
请参阅此页面上的各种内联选项。
编辑
详细说明:
- 如果一个方法很小,它将被内联,因此很少有机会因为在小方法中拆分代码而受到惩罚。
- 在某些情况下,拆分方法可能会导致更多内联。
示例 (如果您尝试,则完整代码具有相同的行号)
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 += tm(); } 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
(是的,我使用的值certificate了我的情况: m
是太大但重构的m
和m2
都低于阈值 - 其他值可能会得到不同的输出)。
您将看到m()
和main()
被编译,但m()
没有内联:
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:PrintAssemblyOptions=intel
) - 它将如下所示:
0x0000000002780624: int3 ;*invokevirtual m ; - javaapplication27.TestInline::main@20 (line 10)
如果你像这样重构代码(我已经在一个单独的方法中提取了if / else):
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; }
您将看到以下编译操作:
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
指令。 你会发现这样的行:
0x00000000026d0121: add ecx,edi ;*iinc ; - javaapplication27.TestInline::m2@7 (line 33) ; - javaapplication27.TestInline::m@7 (line 24) ; - javaapplication27.TestInline::main@20 (line 10)
基本上常见的指令是“共同的”。
结论
我并不是说这个例子具有代表性,但似乎certificate了几点:
- 使用较小的方法可提高代码的可读性
- 通常会内联较小的方法,因此您很可能不会支付额外方法调用的成本(它将是性能中立的)
- 使用较小的方法可能会在某些情况下改善全局内联,如上例所示
最后:如果您的代码的一部分对于这些考虑因素至关重要的性能至关重要,那么您应该检查JIT输出以微调您的代码并重要地分析前后的配置文件。
如果您使用完全相同的代码并将它们分解为许多小方法,那么根本不会帮助JIT。
更好的方法是,现代的HotSpot JVM不会因为编写很多小方法而惩罚你。 它们确实得到了积极的内联,所以在运行时你并没有真正支付函数调用的成本。 甚至对于调用虚拟调用(例如调用接口方法的调用)也是如此。
几年前我做了一篇博客文章 ,描述了如何看待JVM是内联方法。 该技术仍适用于现代JVM。 我还发现查看与invokedynamic相关的讨论很有用,其中广泛讨论了现代HotSpot JVM如何编译Java字节代码。
我真的不明白它是如何工作的,但基于AurA提供的链接 ,我猜想如果重用相同的位,JIT编译器必须编译更少的字节码,而不是必须编译相似的不同字节码不同的方法。
除此之外,您越能够将代码分解为多个意义,您将从代码中获得更多的重用,这将允许对运行它的VM进行优化(您提供了更多的模式跟…共事)。
但是,我怀疑如果你破坏你的代码没有任何不提供代码重用的意义,它将会产生任何好的影响。
- 无法使用方法和变量从静态上下文引用非静态方法
- XPath normalize-space()返回一系列规范化字符串
- 如何在JPA中的两列上运行SUM等聚合函数并显示其结果?
- 桌面上的JVM是否使用JIT编译?
- Apache Hadoop setXIncludeAware UnsupportedOperationException
- 如果实例没有分配generics类型,则为每个循环问题提供generics
- 叫哪种方法? (整数… a)vs.(int a,int b)
- 配置ant以运行unit testing。 图书馆应该在哪里? 应该如何配置classpath? 避免ZipException
- Trie节省空间,但如何?