江铃控股。将Microbenchmark的结果公开

时间:2015-10-01 14:12:22

标签: java microbenchmark jmh

我已经读过,为了避免在微基准测试中消除死代码,最常见的解决方案是:

  1. 返回计算结果
  2. 使用黑洞消耗结果。
  3. 我的问题是:

    可以通过将计算结果放在公共变量中来避免死代码消除吗?

    修改

    感谢Shipilev的回答,我意识到返回结果或使用黑洞消耗它们必须正确完成,以避免死代码消除(DCE),如JMH示例中所述。

    因此,我会重写我的问题以使其更清晰:

    如果返回计算结果或使用blackwholes消耗它足以以避免DCE,还可以将结果放在公共变量中吗?

    我运行了这样的示例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
    

1 个答案:

答案 0 :(得分:2)

这很容易回答:不,这是不安全的,除非你正在控制环境,验证没有发生任何不良影响等等。最简单的情况如何破坏是优化者认为有几个连续存储到该领域,除了最新的商店,消除一切。例如。采取一个众所周知的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,您可以“意外”在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

糟糕。 Blackholes正在工作,并且下沉不是:这是您更新的问题的反例。除非您使用该技巧验证每个基准测试,并仔细检查您正在使用该技巧的代码,否则您无法确定该技巧是否有效。我的观点是,您最好花时间找出特定于您的基准测试的问题(99%的基准测试错误),而不是试图在线束上作弊几纳秒。优先级!

维护者现在的观点。 JMH开发跟踪更新的JVM中正在进行的操作。 Blackholes正在修复。 JMH基准存根的代码形状正在得到纠正。但是,它们在使用广告保证的有效基准上得到验证。我们没有理由关心那些自己做某事的基准。如果,例如编译器能够内联@Benchmark,展开JMH正在进行的外部循环,然后它会为上面发现的问题设置sink。换句话说,如果您希望代码具有面向未来的能力,使用已知和记录的API ,而不是一些技巧。