JMH 如何测量低于粒度值的执行时间?
How JMH measures execution time below granularity value?
所以我正在尝试使用微基准测试,选择了 JMH,阅读了一些文章。 JMH 如何测量低于系统计时器 g运行ularity 的方法的执行?
更详细的解释:
这些是我的基准 运行(方法名称不言自明):
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 上,正如各种文章中所描述的那样,它具有很大的 g运行 粒度 (407 ns)。检查下面的基本代码,它确实是新的定时器值每 ~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() 并测量差异。当 g运行ularity 为 400 ns 时,它如何能够测量几纳秒的方法执行?
你完全正确。您无法测量比系统计时器粒度更快的东西。
JMH 不测量基准方法的每次调用。它在迭代开始前调用 System.nanotime() ,执行基准方法 X 次并在迭代后再次调用 System.nanotime() 。结果是时间差/操作数(您可能在方法上指定每次调用 @OperationsPerInvocation 超过 1 个操作)。
Aleksey Shipilev 在他的文章 Nanotrusting the Nanotime 中讨论了 Nanotime 的测量问题。 'Latency' 部分包含一个代码示例,展示了 JMH 如何测量一个基准迭代。
所以我正在尝试使用微基准测试,选择了 JMH,阅读了一些文章。 JMH 如何测量低于系统计时器 g运行ularity 的方法的执行?
更详细的解释:
这些是我的基准 运行(方法名称不言自明):
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 上,正如各种文章中所描述的那样,它具有很大的 g运行 粒度 (407 ns)。检查下面的基本代码,它确实是新的定时器值每 ~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() 并测量差异。当 g运行ularity 为 400 ns 时,它如何能够测量几纳秒的方法执行?
你完全正确。您无法测量比系统计时器粒度更快的东西。
JMH 不测量基准方法的每次调用。它在迭代开始前调用 System.nanotime() ,执行基准方法 X 次并在迭代后再次调用 System.nanotime() 。结果是时间差/操作数(您可能在方法上指定每次调用 @OperationsPerInvocation 超过 1 个操作)。
Aleksey Shipilev 在他的文章 Nanotrusting the Nanotime 中讨论了 Nanotime 的测量问题。 'Latency' 部分包含一个代码示例,展示了 JMH 如何测量一个基准迭代。