我感兴趣的是我是否应该手动内联在一些性能敏感算法中称为100k-100万次的小方法。
首先,我认为,由于没有内联,我会产生一些开销,因为JVM必须确定是否要内联此方法(甚至不能这样做)。
然而,前几天,我用静态方法的调用替换了这个手动内联代码,并看到了性能提升。怎么可能?这是否表明实际上没有开销,让JVM内联“意志”实际上提升了性能?或者这在很大程度上取决于平台/架构?
(发生性能提升的示例是使用静态方法调用int t = a[i]; a[i] = a[j]; a[j] = t;
替换数组交换(swap(int[] a, int i, int j)
)。另一个没有性能差异的示例是当我内联10-衬里方法被称为1000000次。)
答案 0 :(得分:12)
我见过类似的东西。 “手动内联”不一定更快,结果程序可能过于复杂而无法进行优化分析。
在你的例子中,让我们做一些疯狂的猜测。当您使用swap()方法时,JVM可能能够分析方法体,并得出结论,由于i和j不会更改,尽管有4个数组访问,但只需要2个范围检查而不是4个。局部变量t
不是必需的,JVM可以使用2个寄存器来完成工作,而不会在堆栈上涉及t
的r / w。
稍后,swap()的主体内联到调用方法中。这是在上一次优化之后,因此保存仍然存在。调用者方法体甚至可能证明i和j总是在范围内,因此剩下的2个范围检查也被丢弃。
现在在手动内联版本中,优化器必须立即分析整个程序,变量太多,动作太多,可能无法证明保存范围检查或消除局部变量是安全的{ {1}}。在最坏的情况下,这个版本可能需要花费6个以上的内存访问来进行交换,这是巨大的开销。即使只有1个额外的内存读取,它仍然非常明显。
当然,我们没有理由认为手动“概述”总是更好,即提取小方法,如愿以为它会帮助优化者。
-
我所学到的是,忘记了手动微观优化。并不是我不关心微观性能改进,而是我始终信任JVM的优化。这是我完全不知道该做什么比做坏事更好。所以我放弃了。
答案 1 :(得分:9)
JVM可以非常有效地内联小方法。唯一能够自我介绍的好处是,如果你可以删除代码,也可以通过内联来简化代码。
JVM在识别这些结构时会查找某些结构并进行一些“手动编码”优化。通过使用交换方法,JVM可以识别结构并通过特定优化以不同方式对其进行优化。
您可能有兴趣尝试OpenJDK 7调试版本,该版本可以打印出它生成的本机代码。
答案 2 :(得分:2)
很抱歉我迟到的回复,但我刚刚发现了这个话题,引起了我的注意。
在Java中开发时,尝试编写“简单而愚蠢”的代码。原因:
如果方法是手动内联的,那么它只是编译器首先尝试理解的另一种方法的一部分,并且看是否有时间将其转换为二进制代码,或者是否必须稍等一下才能理解程序流程。此外,根据方法的作用,在运行时期间可以进行多次重新JIT:> JVM只在“预热”之后生成最佳二进制代码......并且可能在JVM自行升温之前程序结束(因为我希望最终性能应该非常相似)。
结论:优化C / C ++中的代码是有意义的(因为静态转换为二进制文件),但相同的优化通常不会对Java产生影响,其中编译器JIT是字节代码,而不是源代码码。顺便说一句,从我看到的javac甚至都没有做出优化:)
答案 3 :(得分:1)
然而,前几天,我用静态方法的调用替换了这个手动内联代码,并看到了性能提升。怎么可能?
如果它位于一个地方(静态方法),那么JVM分析器可能更容易看到瓶颈,而不是单独实施多次。
答案 4 :(得分:0)
Hotspot JIT编译器能够内联很多东西,特别是在-server
模式下,虽然我不知道你是如何获得实际的性能提升的。 (我的猜测是内联是通过方法调用计数完成的,交换这两个值的方法不会经常调用。)
顺便说一句,如果它的性能非常重要,您可以尝试将其交换为两个int
值。 (我不是说它会更快,但它可能值得一试。)
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];