JMH - 我为什么需要 Blackhole.consumeCPU()?

8

我正在尝试理解为什么使用Blackhole.consumeCPU()是明智的?

我在Google上找到了一些关于Blackhole.consumeCPU()的信息

有时候,当我们在多个线程上运行基准测试时,我们还想烧掉一些CPU周期来模拟运行我们的代码时的CPU业务。这不能是Thread.sleep,因为我们真正想要烧掉CPU。Blackhole.consumeCPU(long)使我们能够做到这一点。

我的示例代码:

    import java.util.concurrent.TimeUnit;

    import org.openjdk.jmh.annotations.Benchmark;
    import org.openjdk.jmh.annotations.BenchmarkMode;
    import org.openjdk.jmh.annotations.Level;
    import org.openjdk.jmh.annotations.Measurement;
    import org.openjdk.jmh.annotations.Mode;
    import org.openjdk.jmh.annotations.OutputTimeUnit;
    import org.openjdk.jmh.annotations.Scope;
    import org.openjdk.jmh.annotations.Setup;
    import org.openjdk.jmh.annotations.State;
    import org.openjdk.jmh.annotations.Warmup;
    import org.openjdk.jmh.infra.Blackhole;
    import org.openjdk.jmh.runner.Runner;
    import org.openjdk.jmh.runner.RunnerException;
    import org.openjdk.jmh.runner.options.Options;
    import org.openjdk.jmh.runner.options.OptionsBuilder;

    @State(Scope.Thread)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public class StringConcatAvgBenchmark {

    StringBuilder stringBuilder1;
    StringBuilder stringBuilder2;

    StringBuffer stringBuffer1;
    StringBuffer stringBuffer2;

    String string1;
    String string2;

    /*
     * re-initializing the value after every iteration
     */
    @Setup(Level.Iteration)
    public void init() {
        stringBuilder1 = new StringBuilder("foo");
        stringBuilder2 = new StringBuilder("bar");

        stringBuffer1 = new StringBuffer("foo");
        stringBuffer2 = new StringBuffer("bar");

        string1 = new String("foo");
        string2 = new String("bar");

    }

    @Benchmark
    @Warmup(iterations = 10)
    @Measurement(iterations = 100)
    @BenchmarkMode(Mode.AverageTime)
    public StringBuilder stringBuilder() {
        // operation is very thin and so consuming some CPU
        Blackhole.consumeCPU(100);
        return stringBuilder1.append(stringBuilder2);
        // to avoid dead code optimization returning the value
    }

    @Benchmark
    @Warmup(iterations = 10)
    @Measurement(iterations = 100)
    @BenchmarkMode(Mode.AverageTime)
    public StringBuffer stringBuffer() {
        Blackhole.consumeCPU(100);      
        // to avoid dead code optimization returning the value
        return stringBuffer1.append(stringBuffer2);
    }

    @Benchmark
    @Warmup(iterations = 10)
    @Measurement(iterations = 100)
    @BenchmarkMode(Mode.AverageTime)
    public String stringPlus() {
        Blackhole.consumeCPU(100);      
        return string1 + string2;
    }

    @Benchmark
    @Warmup(iterations = 10)
    @Measurement(iterations = 100)
    @BenchmarkMode(Mode.AverageTime)
    public String stringConcat() {
        Blackhole.consumeCPU(100);      
        // to avoid dead code optimization returning the value
        return string1.concat(string2);
    }

    public static void main(String[] args) throws RunnerException {

        Options options = new OptionsBuilder()
                .include(StringConcatAvgBenchmark.class.getSimpleName())
                .threads(1).forks(1).shouldFailOnError(true).shouldDoGC(true)
                .jvmArgs("-server").build();
        new Runner(options).run();
    }
    }

为什么使用 blackhole.consumeCPU(100) 后,这个基准测试的结果会更好?

更新:

使用blackhole.consumeCPU(100)的输出结果:

    Benchmark                      Mode  Cnt    Score    Error  Units
    StringBenchmark.stringBuffer   avgt   10  398,843 ± 38,666  ns/op
    StringBenchmark.stringBuilder  avgt   10  387,543 ± 40,087  ns/op
    StringBenchmark.stringConcat   avgt   10  410,256 ± 33,194  ns/op
    StringBenchmark.stringPlus     avgt   10  386,472 ± 21,704  ns/op

没有 blackhole.consumeCPU(100) 的输出:

    Benchmark                      Mode  Cnt   Score    Error  Units
    StringBenchmark.stringBuffer   avgt   10  51,225 ± 19,254  ns/op
    StringBenchmark.stringBuilder  avgt   10  49,548 ±  4,126  ns/op
    StringBenchmark.stringConcat   avgt   10  50,373 ±  1,408  ns/op
    StringBenchmark.stringPlus     avgt   10  87,942 ±  1,701  ns/op

我现在想我知道为什么他们使用这个了,因为没有一些延迟的话基准测试会太快了。
通过 blackhole.consumeCPU(100) 你可以更好地测量每个基准测试并获得更显著的结果。 是这样吗?


定义“更好”?我在你的问题中没有看到任何比较。 - Aleksey Shipilev
1个回答

9
添加人工延迟通常不会改善基准测试结果。但是,在某些情况下,您正在测量的操作可能与某些资源争用,因此您需要一个仅消耗CPU并希望不产生其他影响的后退策略。例如,请参见以下案例:http://shipilev.net/blog/2014/nanotrusting-nanotime/
原始问题中的基准测试并非如此,因此我猜测在那里使用Blackhole.consumeCPU没有充分的理由,或者至少该原因在注释中没有明确说明。请勿这样做。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接