两种相同方法的性能差异
Performance difference in two equal methods
我正在阅读 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
...
此结果解释如下:
Note that C1 is faster, C2 is slower, but the C1 is slow again! This is because ...
但我的问题是:为什么 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
在第一次测试中,有一个 Counter
的实现。 JIT 编译器能够假定任何调用 measure(Counter)
的东西都使用相同的实现,因此它可以内联来自 inc()
.
的代码
在第二个测试中,我们引入了第二个实现 - 现在调用需要内联两个实现,或者在每次迭代时执行动态调度。由于不确定性(无论选择哪种),这比第一次测试慢得多。
在第三个测试中,我们使用与第一个测试相同的实现 - 但世界状态与第一个测试不同,因为 JIT 仍然知道第二个实现存在...它不能回过头来相信 Counter
只有一个实现...所以它仍然必须以比第一个测试更慢的方式执行 inc()
。
这个故事的寓意是,影响性能的不仅仅是代码,还有世界的状态。第一次测试中的世界状态(从优化的角度来看)比第二次和第三次测试中的世界状态要好得多。
我正在阅读 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
...
此结果解释如下:
Note that C1 is faster, C2 is slower, but the C1 is slow again! This is because ...
但我的问题是:为什么 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
在第一次测试中,有一个 Counter
的实现。 JIT 编译器能够假定任何调用 measure(Counter)
的东西都使用相同的实现,因此它可以内联来自 inc()
.
在第二个测试中,我们引入了第二个实现 - 现在调用需要内联两个实现,或者在每次迭代时执行动态调度。由于不确定性(无论选择哪种),这比第一次测试慢得多。
在第三个测试中,我们使用与第一个测试相同的实现 - 但世界状态与第一个测试不同,因为 JIT 仍然知道第二个实现存在...它不能回过头来相信 Counter
只有一个实现...所以它仍然必须以比第一个测试更慢的方式执行 inc()
。
这个故事的寓意是,影响性能的不仅仅是代码,还有世界的状态。第一次测试中的世界状态(从优化的角度来看)比第二次和第三次测试中的世界状态要好得多。