JMH如何在粒度值以下测量执行时间?

6

我想尝试一下微基准测试,选择了JMH,并阅读了一些文章。JMH如何测量低于系统定时器粒度的方法执行时间?

更详细的解释:

这些是我要运行的基准测试(方法名已经说明了):

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 10, time = 200, timeUnit = TimeUnit.NANOSECONDS)
@Measurement(iterations = 20, time = 200, timeUnit = TimeUnit.NANOSECONDS)
public class RandomBenchmark {

    public long lastValue;

    @Benchmark
    @Fork(1)
    public void blankMethod() {
    }

    @Benchmark
    @Fork(1)
    public void simpleMethod(Blackhole blackhole) {
        int i = 0;
        blackhole.consume(i++);
    }

    @Benchmark
    @Fork(1)
    public void granularityMethod(Blackhole blackhole) {
        long initialTime = System.nanoTime();
        long measuredTime;
        do {
            measuredTime = System.nanoTime();
        } while (measuredTime == initialTime);
        blackhole.consume(measuredTime);
    }
}

这里是结果:
# Run complete. Total time: 00:00:02

Benchmark                          Mode  Cnt    Score    Error  Units
RandomBenchmark.blankMethod        avgt   20    0,887 ?  0,274  ns/op
RandomBenchmark.granularityMethod  avgt   20  407,002 ? 26,297  ns/op
RandomBenchmark.simpleMethod       avgt   20    6,979 ?  0,743  ns/op

当前运行在Windows 7上,根据多篇文章的描述,它具有很大的粒度(407ns)。使用下面的基本代码进行检查,可以发现新的计时器值确实每隔约400ns出现一次:

    final int sampleSize = 100;
    long[] timeMarks = new long[sampleSize];
    for (int i=0; i < sampleSize; i++) {
        timeMarks[i] = System.nanoTime();
    }
    for (long timeMark : timeMarks) {
        System.out.println(timeMark);
    }

很难完全理解生成的方法是如何工作的,但通过反编译JMH生成的代码,似乎在执行前后使用相同的System.nanoTime()来测量时间差。它是如何能够测量几纳秒级别的方法执行时间,而粒度却为400纳秒?


如果它没有足够多次地迭代测量的方法,那么你就让我无从下手了。 - Marko Topolnik
1
可能是通过对多个样本进行平均来实现的? - fge
是的,这是一个显而易见的想法,但也许还有其他的技巧,考虑到nanoTime()是内在的。 - Oleksii Vynnychenko
1个回答

3

你说得非常正确。如果某个东西比您系统的定时器粒度更快,那么您将无法测量它。

JMH不会测量每次benchmark方法的调用。它在迭代开始之前调用System.nanotime()方法,执行benchmark方法X次并在迭代后再次调用System.nanotime()方法。然后计算时间差异/操作数(可能您可以在方法上使用@OperationsPerInvocation指定一次调用多个操作)。

Aleksey Shipilev在他的文章《Nanotrusting the Nanotime》中讨论了使用纳秒时间测量的问题。"延迟"一节包含一个代码示例,显示JMH如何测量一个基准迭代。


Shipilev的博客对您也可能很有用:http://shipilev.net/。观看一些演示文稿,了解更多关于JMH以及在创建良好基准时会遇到的问题。 - Matthias Huber

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