2011-02-11 116 views
12

我有兴趣是否应该手动内联一些在性能敏感算法中称为100k-100万次的小方法。首先,我认为,通过不内联,我招致了一些开销,因为JVM将不得不查找是否要内联这个方法(甚至不这样做)。Java - 调用静态方法vs手动内联 - 性能开销

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

(发生性能提升的例子是用静态方法调用swap(int[] a, int i, int j)替换了数组交换(int t = a[i]; a[i] = a[j]; a[j] = t;)。另一个没有性能差异的例子是当我将一个被称为1000000次的10-liner方法)

回答

11

我看过类似的东西。 “手动内联”不一定更快,结果程序对于优化器来说可能太复杂以至于无法分析。

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

后来,swap()的主体被内联到调用方法中。那是在先前的优化之后,所以保存仍然存在。调用者方法体甚至有可能证明i和j总是在范围内,所以剩余的两个范围检查也被丢弃。

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

当然,我们没有任何理由相信最好是手动“勾画”,即提取小方法,并希望能够帮助优化器。

-

我学到的是,忘记手动微优化。这并不是说我不关心微型性能的改进,并不是我总是相信JVM的优化。这是我绝对不知道该做什么,比做得更好。所以我放弃了。

0

热点JIT编译器能够内联很多事情,特别是在-server模式下,尽管我不知道如何得到实际的性能提升。 (我的猜测是内联是通过方法调用计数完成的,并且交换这两个值的方法不会太频繁地调用)。

顺便说一下,如果它的性能真的很重要,那么可以尝试更换两个int值。 (我不是说这会更快,但它可能是值得一平底船)

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

几乎没有任何现代建筑的速度。 – Puppy 2011-02-11 14:34:03

1

然而,有一天,我换成这个手动内联代码的静态方法调用,并看到了性能促进。这怎么可能?

也许JVM分析器在一个地方(静态方法)更容易看到瓶颈,而不是分开实施多次。

8

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

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

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

2

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

在Java中开发时,尝试编写“简单和愚蠢的”代码。理由:

  1. 优化是在运行时进行的(因为编译本身是在运行时进行的)。因为编译器不会编译你编写的源代码,而是编译它的内部表示(几个AST - > VM代码 - > VM代码 - - >本地二进制代码转换是在运行时由JVM编译器和JVM解释器执行)
  2. 当优化编译器时,使用一些常用编程模式来决定优化哪些内容;所以帮他帮助你!写一个私有的静态(也许也最终)方法,它会立即它可以计算出:
    • 内嵌的方法
    • 其编译为本地代码

如果该方法是手动内联,它只是编译器首先试图理解的另一种方法的一部分,并且看看是否应该将其转换为二进制代码,或者它是否必须稍微了解一下程序流程。另外,取决于该方法的作用,在运行时期间可能会有几次重新调整JIT'> JVM仅在“预热”之后才生成最佳二进制代码......并且可能在JVM自动升温之前程序结束了(因为我预计最终的表现应该相当类似)。

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