两种相同方法的性能差异

Nik*_*kov 1 java performance jmh

我正在阅读JMH框架的示例,我对来自JMHSample_12_Forking的示例代码有疑问.运行此代码后,我有以下结果(正如作者预测的那样):

testJavaUtilConcurrency.JMHSample_12_Forking.measure_1_c1         avgt    5   3.314 ±  0.200  ns/op
testJavaUtilConcurrency.JMHSample_12_Forking.measure_2_c2         avgt    5  22.403 ±  1.023  ns/op
...
Run Code Online (Sandbox Code Playgroud)

该结果解释如下:

注意C1更快,C2更慢,但C1再慢!这是因为 ...

但我的问题是:为什么C2比C1慢?两个类中的代码和两个方法看起来完全相同,那么,性能差异的来源是什么?

更新:

我试图为Counter添加第三个实现并获得以下结果:

testJavaUtilConcurrency.JMHSample_12_Forking.measure_1_c1         avgt    5   3.328 ± 0.073  ns/op
testJavaUtilConcurrency.JMHSample_12_Forking.measure_2_c2         avgt    5   22.437 ± 0.552  ns/op
testJavaUtilConcurrency.JMHSample_12_Forking.measure_2_c3         avgt    5  44.614 ± 5.080  ns/op
testJavaUtilConcurrency.JMHSample_12_Forking.measure_3_c1_again   avgt    5  43.535 ± 1.154  ns/op
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 5

在第一次测试期间,有一个实现Counter.JIT编译器能够假设任何调用measure(Counter)都使用相同的实现,因此它可以内联代码inc().

在第二个测试中,我们引入了第二个实现 - 现在调用需要内联两个实现,或者在每次迭代时执行动态调度.由于这种不确定性(任一选择),这比第一次测试要慢得多.

在第三个测试中,我们使用的是与第一个测试相同的实现 - 但是世界状态与第一个测试不同,因为JIT仍然知道第二个实现存在...它无法返回相信只有一个实现Counter...所以它仍然必须以inc()比第一次测试更慢的方式执行.

故事的寓意在于,不只是影响表现的代码 - 它是世界的状态.第一次测试中的世界状态(从优化角度来看)比第二次和第三次测试中的世界状态好得多.

  • 我是JMH Sample的作者,可以证明@ JonSkeet的答案是正确的.基本上,单态内联(仅将C1作为可能的目标)产生比双态内联(期望C1和C2)更好的代码.在此处查看更多信息:http://shipilev.net/blog/2015/black-magic-method-dispatch/ (3认同)