JMH。制作 Microbenchmark 的结果 public
JMH. Making the Microbenchmark's results public
我读到,为了避免微基准测试中的死代码消除,最常见的解决方案是:
- Return计算结果
- 使用黑洞消耗结果。
我的问题是:
是否可以通过将计算结果放在 public 变量中来避免删除死代码?
编辑:
感谢 Shipilev 的回答,我意识到返回结果或使用黑洞消耗它们必须正确完成,以避免死代码清除 (DCE),如 JMH 示例中所述。
因此,我将重写我的问题以使其更清楚:
在返回计算结果或用 blackwholes 消耗它的情况下 足以 避免 DCE,也足以将结果放在 public 变量中?
我有 运行 这个例子的变体 JMHSample_08_DeadCode 像这样:
public double sink;
@Benchmark
public void measureRightPerhaps_2() {
// What could possibly go wrong?
sink = Math.log(x);
}
从结果来看似乎是这样:
Benchmark Mode Cnt Score Error Units
baseline avgt 15 0,458 � 0,001 ns/op
measureRight avgt 15 33,233 � 0,268 ns/op
measureRightPerhaps_2 avgt 15 30,177 � 0,603 ns/op
measureWrong avgt 15 0,459 � 0,001 ns/op
measureWrong_2 avgt 15 0,917 � 0,001 ns/op
这很容易回答:不,那不安全,除非你控制环境,验证没有不良影响发生等。最简单的情况是优化器认为有几个连续的商店到现场,并消除除最新商店以外的所有东西。例如。取一个众所周知的JMHSample_08_DeadCode,并添加这个测试:
public double sink;
@Benchmark
public void measureWrong_2() {
// What could possibly go wrong?
sink = Math.log(x);
// Imagine this happens somewhere downstream.
// Or, you are sinking in the loop.
// Or, measureWrong_2 had inlined and the very next Math.log will sink.
sink = Math.PI;
}
...然后运行它,哭泣:
Benchmark Mode Cnt Score Error Units
JMHSample_08_DeadCode.baseline avgt 5 0.251 ± 0.001 ns/op
JMHSample_08_DeadCode.measureRight avgt 5 19.034 ± 0.033 ns/op
JMHSample_08_DeadCode.measureWrong avgt 5 0.251 ± 0.001 ns/op
JMHSample_08_DeadCode.measureWrong_2 avgt 5 0.326 ± 0.001 ns/op
士气:除非你知道自己在做什么,否则不要脱离 JMH 文档提到的避免 DCE 的受支持方法。
更新:当然,当其他技术起作用时,您可以找到一个极端情况。但是,即使某些东西目前正在工作,您也不能确定它是否会与其他一些无害的更改一起工作。这就是使用 Blackholes 的全部意义所在——它们一直在工作。例如。更复杂的情况 JMHSample_09_Blackholes,您可以 "accidentally" 在 sink
中创建两个背靠背存储:
@Benchmark
public void measureRight_2(Blackhole bh) {
bh.consume(Math.log(x1));
bh.consume(Math.log(x2));
}
public double sink;
@Benchmark
public void measureWrong_2() {
sink = Math.log(x1);
sink = Math.log(x2);
}
...和:
JMHSample_09_Blackholes.measureRight_1 avgt 5 35.837 ± 0.043 ns/op
JMHSample_09_Blackholes.measureRight_2 avgt 5 38.378 ± 0.071 ns/op
JMHSample_09_Blackholes.measureWrong avgt 5 19.012 ± 0.009 ns/op
JMHSample_09_Blackholes.measureWrong_2 avgt 5 16.659 ± 0.018 ns/op
糟糕。黑洞在起作用,而汇则没有:这是您更新后的问题的反例。除非您使用该技巧验证 每个 基准测试,并仔细检查您正在按预期使用该技巧的代码,否则您无法确定该技巧是否有效。我的观点是,您最好花时间找出特定于您的基准测试的问题(占所有基准测试错误的 99%),而不是试图在 harness 上作弊几纳秒。优先事项!
Maintainer 现在的视角。 JMH 开发跟踪在更新的 JVM 中所做的事情,随着它们的发展。黑洞在这个过程中得到修复。 JMH 基准存根的代码形状正在得到纠正。但是,它们在使用广告保证的有效基准上得到验证。我们没有理由关心自己做某事的基准。如果,例如编译器将能够内联 @Benchmark
,展开 JMH 正在执行的外部循环,然后它将为上面发现的问题设置 sink
。换句话说,如果您希望您的代码面向未来,使用已知和文档化的 API,而不是一些技巧。
我读到,为了避免微基准测试中的死代码消除,最常见的解决方案是:
- Return计算结果
- 使用黑洞消耗结果。
我的问题是:
是否可以通过将计算结果放在 public 变量中来避免删除死代码?
编辑:
感谢 Shipilev 的回答,我意识到返回结果或使用黑洞消耗它们必须正确完成,以避免死代码清除 (DCE),如 JMH 示例中所述。
因此,我将重写我的问题以使其更清楚:
在返回计算结果或用 blackwholes 消耗它的情况下 足以 避免 DCE,也足以将结果放在 public 变量中?
我有 运行 这个例子的变体 JMHSample_08_DeadCode 像这样:
public double sink;
@Benchmark
public void measureRightPerhaps_2() {
// What could possibly go wrong?
sink = Math.log(x);
}
从结果来看似乎是这样:
Benchmark Mode Cnt Score Error Units
baseline avgt 15 0,458 � 0,001 ns/op
measureRight avgt 15 33,233 � 0,268 ns/op
measureRightPerhaps_2 avgt 15 30,177 � 0,603 ns/op
measureWrong avgt 15 0,459 � 0,001 ns/op
measureWrong_2 avgt 15 0,917 � 0,001 ns/op
这很容易回答:不,那不安全,除非你控制环境,验证没有不良影响发生等。最简单的情况是优化器认为有几个连续的商店到现场,并消除除最新商店以外的所有东西。例如。取一个众所周知的JMHSample_08_DeadCode,并添加这个测试:
public double sink;
@Benchmark
public void measureWrong_2() {
// What could possibly go wrong?
sink = Math.log(x);
// Imagine this happens somewhere downstream.
// Or, you are sinking in the loop.
// Or, measureWrong_2 had inlined and the very next Math.log will sink.
sink = Math.PI;
}
...然后运行它,哭泣:
Benchmark Mode Cnt Score Error Units
JMHSample_08_DeadCode.baseline avgt 5 0.251 ± 0.001 ns/op
JMHSample_08_DeadCode.measureRight avgt 5 19.034 ± 0.033 ns/op
JMHSample_08_DeadCode.measureWrong avgt 5 0.251 ± 0.001 ns/op
JMHSample_08_DeadCode.measureWrong_2 avgt 5 0.326 ± 0.001 ns/op
士气:除非你知道自己在做什么,否则不要脱离 JMH 文档提到的避免 DCE 的受支持方法。
更新:当然,当其他技术起作用时,您可以找到一个极端情况。但是,即使某些东西目前正在工作,您也不能确定它是否会与其他一些无害的更改一起工作。这就是使用 Blackholes 的全部意义所在——它们一直在工作。例如。更复杂的情况 JMHSample_09_Blackholes,您可以 "accidentally" 在 sink
中创建两个背靠背存储:
@Benchmark
public void measureRight_2(Blackhole bh) {
bh.consume(Math.log(x1));
bh.consume(Math.log(x2));
}
public double sink;
@Benchmark
public void measureWrong_2() {
sink = Math.log(x1);
sink = Math.log(x2);
}
...和:
JMHSample_09_Blackholes.measureRight_1 avgt 5 35.837 ± 0.043 ns/op
JMHSample_09_Blackholes.measureRight_2 avgt 5 38.378 ± 0.071 ns/op
JMHSample_09_Blackholes.measureWrong avgt 5 19.012 ± 0.009 ns/op
JMHSample_09_Blackholes.measureWrong_2 avgt 5 16.659 ± 0.018 ns/op
糟糕。黑洞在起作用,而汇则没有:这是您更新后的问题的反例。除非您使用该技巧验证 每个 基准测试,并仔细检查您正在按预期使用该技巧的代码,否则您无法确定该技巧是否有效。我的观点是,您最好花时间找出特定于您的基准测试的问题(占所有基准测试错误的 99%),而不是试图在 harness 上作弊几纳秒。优先事项!
Maintainer 现在的视角。 JMH 开发跟踪在更新的 JVM 中所做的事情,随着它们的发展。黑洞在这个过程中得到修复。 JMH 基准存根的代码形状正在得到纠正。但是,它们在使用广告保证的有效基准上得到验证。我们没有理由关心自己做某事的基准。如果,例如编译器将能够内联 @Benchmark
,展开 JMH 正在执行的外部循环,然后它将为上面发现的问题设置 sink
。换句话说,如果您希望您的代码面向未来,使用已知和文档化的 API,而不是一些技巧。