以下问题再JDK8的server模式之下 JDK11, JDK14好像没这个问题
1. Fork是什么
Fork就是告诉JMH该在哪个进程中执行Benchmark的一个参数
说得在精炼也没卵用, 看看示例就明白
2. @Fork(0)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Sample_12_Forking_0 {
@Benchmark
@Fork(0)
public int measure_1_c1() {
return 1;
}
@Setup(Level.Trial)
public void setup() {
printProcessID("setup"); //打印pid
}
public static void main(String[] args) throws RunnerException {
printProcessID("main"); //打印pid
Options opt = new OptionsBuilder()
.include(Sample_12_Forking_1.class.getSimpleName())
.build();
new Runner(opt).run();
}
public static void printProcessID(String name) {
System.out.println();
System.out.println("------------------------------------------");
System.out.println(name + " pid is :" + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("------------------------------------------");
System.out.println();
}
}
我们在main方法 和 带测试的Benchmark setup方法上都打印了PID
输出是这样的:
main pid is :38424@zyj
setup pid is :38424@zyj
很明显, Benchmark, 与main方法是在同一个JVM中被执行
因为他们的pid是一致的
3. @Fork(1)
在上面的例子中, 我们做点小修改, 使用 @Fork(1)
@Benchmark
@Fork(1) //注解中传入 1
public int measure_1_c1() {
return 1;
}
我们再运行看看输出
main pid is :11144@zyj
setup pid is :16668@zyj
这个输出非常清晰, benchmark与main方法不是在一个进程中被执行的
4. @Fork()
再修改一下, 如果不传数字, 那JMH会用几个进程执行呢?
@Benchmark
@Fork()//不传, 默认就是-1, 效果等同于传5
public int measure_1_c1() {
return 1;
}
看看输出结果
main pid is :35456@zyj
setup pid is :10732@zyj
setup pid is :24268@zyj
setup pid is :44916@zyj
setup pid is :40700@zyj
setup pid is :19744@zyj
Benchmark Mode Cnt Score Error Units
Sample_12_Forking_default.measure_1_c1 avgt 5 1.791 ± 0.247 ns/op
观察执行输出
main方法用了一个JVM进程
另外benchmark又执行了5次, 每一次都是放入独立的JVM进程中执行
5. @Fork(n)
现在我们再随便传入一个数字试试
这次我传入30试试
@Benchmark
@Fork(30)
public int measure_1_c1() {
return 1;
}
看看输出结果
main pid is :38780@zyj
setup pid is :42280@zyj
setup pid is :32500@zyj
....
Benchmark Mode Cnt Score Error Units
Sample_12_Forking_n.measure_1_c1 avgt 30 1.662 ± 0.025 ns/op
benchmark被执行了30次, 每一次都放入了全新的JVM进程中
看了上面几个例子, 基本上就理解了 Fork是个什么东西了
在我们写JMH用例的时候, 最好是将benchmark单独放入一个进程中去执行, 这样可以提供最纯净的环境
6. 示例代码
JDK8之下此例才有意义
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Sample_12_Fork_My {
public interface Counter {
int inc();
}
public static class Counter1 implements Counter {
private int x;
@Override
public int inc() {
return x++;
}
}
public static class Counter2 implements Counter {
private int x;
@Override
public int inc() {
return x++;
}
}
public int measure(Counter c) {
int s = 0;
for (int i = 0; i < 1000; i++) {
s += c.inc();
}
return s;
}
//c1与c2代码完全一致, 但是类不一样
Counter c1 = new Counter1();
Counter c2 = new Counter2();
@Benchmark
@Fork(0) //与main方法同一个进程执行
public int measure_1_c1() {
return measure(c1);
}
@Benchmark
@Fork(0) //与main方法同一个进程执行
public int measure_2_c2() {
return measure(c2);
}
@Benchmark
@Fork(0) //与main方法同一个进程执行
public int measure_3_c1_again() {
return measure(c1);
}
@Benchmark
@Fork(1) //放入新的jvm中执行
public int measure_4_forked_c1() {
return measure(c1);
}
@Benchmark
@Fork(1) //放入新的jvm中执行
public int measure_5_forked_c2() {
return measure(c2);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(Sample_12_Fork_My.class.getSimpleName())
.jvmArgs("-server")
.build();
new Runner(opt).run();
}
}
运行结果
Benchmark Mode Cnt Score Error Units
Sample_12_Fork_My.measure_1_c1 avgt 1564.675 ns/op
Sample_12_Fork_My.measure_2_c2 avgt 1615.291 ns/op
Sample_12_Fork_My.measure_3_c1_again avgt 1556.856 ns/op
Sample_12_Fork_My.measure_4_forked_c1 avgt 268.587 ns/op
Sample_12_Fork_My.measure_5_forked_c2 avgt 256.540 ns/op
这个运行结果与google上查的预期不一致, 这可能是我用的是 dragonwell-JDK11, 没有得到网上的结果