Java – 调用静态方法与手动内联 – 性能开销

我感兴趣的是我是否应该手动内联在一些性能敏感算法中称为100k-100万次的小方法。

首先,我认为,由于没有内联,我会产生一些开销,因为JVM必须确定是否内联此方法(甚至不能这样做)。

然而,前几天,我用静态方法的调用替换了这个手动内联代码,并看到了性能提升。 怎么可能? 这是否表明实际上没有开销,让JVM内联“意志”实际上提升了性能? 或者这很大程度上取决于平台/架构?

(发生性能提升的示例是使用静态方法调用swap(int[] a, int i, int j) )替换数组交换( int t = a[i]; a[i] = a[j]; a[j] = t; )) swap(int[] a, int i, int j) 。另一个没有性能差异的例子是当我内联一个名为1000000次的10线程方法时。)

我见过类似的东西。 “手动内联”不一定更快,结果程序可能太复杂而无法进行优化分析。

在你的例子中,让我们做一些疯狂的猜测。 当您使用swap()方法时,JVM可能能够分析方法体,并得出结论,由于i和j不会改变,尽管有4个数组访问,但只需要2个范围检查而不是4个。局部变量t不是必需的,JVM可以使用2个寄存器来完成工作,而不涉及堆栈上的r / w。

之后,swap()的主体被内联到调用方法中。 这是在上一次优化之后,因此保存仍然存在。 调用者方法体甚至可能已经certificatei和j总是在范围内,因此剩下的2个范围检查也被丢弃。

现在在手动内联版本中,优化器必须立即分析整个程序,变量太多,动作太多,可能无法certificate保存范围检查或消除局部变量t 。 在最坏的情况下,这个版本可能需要花费6个以上的内存访问来进行交换,这是一个巨大的开销。 即使只有1个额外的内存读取,它仍然非常明显。

当然,我们没有理由认为手动“概述”总是更好,即提取小方法,如愿以为它会帮助优化器。

我所学到的是,忘记手动微优化。 并不是我不关心微观性能改进,而是我始终信任JVM的优化。 这是我完全不知道该做什么比做坏事更好。 所以我放弃了。

JVM可以非常有效地内联小方法。 唯一能够自我介绍的好处是,如果您可以删除代码,即通过内联来简化它的function。

JVM在识别这些结构时会查找某些结构并进行一些“手动编码”优化。 通过使用交换方法,JVM可以识别结构并通过特定优化以不同方式对其进行优化。

您可能有兴趣尝试OpenJDK 7调试版本,该版本可以打印出它生成的本机代码。

对不起,我的回复很晚,但我刚刚发现这个话题,引起了我的注意。

在Java中开发时,尝试编写“简单而愚蠢”的代码。 原因:

  1. 优化是在运行时进行的(因为编译本身是在运行时进行的)。 编译器无论如何都要弄清楚要做什么优化,因为它不是编译你编写的源代码,而是编译它使用的内部表示(几个AST – > VM代码 – > VM代码…… – >本机二进制代码转换是在JVM编译器和JVM解释器的运行时)
  2. 优化编译器时使用一些常用的编程模式来决定优化内容; 所以帮助他帮助你! 编写一个私有静态(也许是最终的)方法,它会立即发现它可以:
    • 内联方法
    • 将其编译为本机代码

如果该方法是手动内联的,那么它只是编译器首先尝试理解的另一种方法的一部分,并且看是否需要将其转换为二进制代码,或者是否必须等待一段时间才能理解程序流程。 此外,根据方法的作用,在运行期间可以进行多次重新JIT:> JVM仅在“预热”后生成最佳二进制代码…并且可能在JVM自行升温之前程序结束(因为我期望最终表现应该非常相似)。

结论:在C / C ++中优化代码是有意义的(因为静态地转换成二进制文件),但是相同的优化通常不会对Java产生影响,其中编译器JIT是字节代码,而不是源代码。 顺便说一句,从我看到的javac甚至都不打算进行优化:)

然而,前几天,我用静态方法的调用替换了这个手动内联代码,并看到了性能提升。 怎么可能?

可能JVM分析器在一个地方(静态方法)比单独实施多次更容易看到瓶颈。

Hotspot JIT编译器能够内联很多东西,特别是在-server模式下,虽然我不知道你是如何获得实际的性能提升的。 (我的猜测是内联是通过方法调用计数完成的,并且交换这两个值的方法不会经常调用。)

顺便说一句,如果它的性能真的很重要,你可以尝试这个来交换两个int值。 (我不是说它会更快,但它可能值得一试。)

 a[i] = a[i] ^ a[j]; a[j] = a[i] ^ a[j]; a[i] = a[i] ^ a[j];