以下问题再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"); //打印pidOptions 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@zyjsetup pid is :38424@zyj
很明显, Benchmark, 与main方法是在同一个JVM中被执行
因为他们的pid是一致的
3. @Fork(1)
在上面的例子中, 我们做点小修改, 使用 @Fork(1)
@Benchmark@Fork(1) //注解中传入 1public int measure_1_c1() {return 1;}
我们再运行看看输出
main pid is :11144@zyjsetup pid is :16668@zyj
这个输出非常清晰, benchmark与main方法不是在一个进程中被执行的
4. @Fork()
再修改一下, 如果不传数字, 那JMH会用几个进程执行呢?
@Benchmark@Fork()//不传, 默认就是-1, 效果等同于传5public int measure_1_c1() {return 1;}
看看输出结果
main pid is :35456@zyjsetup pid is :10732@zyjsetup pid is :24268@zyjsetup pid is :44916@zyjsetup pid is :40700@zyjsetup pid is :19744@zyj
Benchmark Mode Cnt Score Error UnitsSample_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@zyjsetup pid is :42280@zyjsetup pid is :32500@zyj....
Benchmark Mode Cnt Score Error UnitsSample_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;@Overridepublic int inc() {return x++;}}public static class Counter2 implements Counter {private int x;@Overridepublic 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 UnitsSample_12_Fork_My.measure_1_c1 avgt 1564.675 ns/opSample_12_Fork_My.measure_2_c2 avgt 1615.291 ns/opSample_12_Fork_My.measure_3_c1_again avgt 1556.856 ns/opSample_12_Fork_My.measure_4_forked_c1 avgt 268.587 ns/opSample_12_Fork_My.measure_5_forked_c2 avgt 256.540 ns/op
这个运行结果与google上查的预期不一致, 这可能是我用的是 dragonwell-JDK11, 没有得到网上的结果
