我正在阅读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
...
该结果解释如下:
注意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
答案 0 :(得分:5)
在第一次测试期间,有Counter
的一个实现。 JIT编译器能够假设调用measure(Counter)
的任何内容都使用相同的实现,因此它可以内联inc()
的代码。
在第二个测试中,我们引入了第二个实现 - 现在调用需要内联两个实现,或者在每次迭代时执行动态调度。由于这种不确定性(任一选择),这比第一次测试要慢得多。
在第三个测试中,我们使用与第一个测试相同的实现 - 但世界状态与第一个测试不同,因为JIT仍然知道第二个实现存在...它我不能再相信那里只有Counter
的一个实现...所以它仍然必须以比第一次测试更慢的方式执行inc()
。
故事的寓意在于,它不仅仅是影响表现的代码 - 它还是世界的状态。第一次测试中的世界状态(从优化角度来看)比第二次和第三次测试中的世界状态要好得多。