以下问题再JDK8的server模式之下 JDK11, JDK14好像没这个问题

1. Fork是什么

Fork就是告诉JMH该在哪个进程中执行Benchmark的一个参数
说得在精炼也没卵用, 看看示例就明白

2. @Fork(0)

  1. @Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
  2. @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
  3. @State(Scope.Thread)
  4. @BenchmarkMode(Mode.AverageTime)
  5. @OutputTimeUnit(TimeUnit.NANOSECONDS)
  6. public class Sample_12_Forking_0 {
  7. @Benchmark
  8. @Fork(0)
  9. public int measure_1_c1() {
  10. return 1;
  11. }
  12. @Setup(Level.Trial)
  13. public void setup() {
  14. printProcessID("setup"); //打印pid
  15. }
  16. public static void main(String[] args) throws RunnerException {
  17. printProcessID("main"); //打印pid
  18. Options opt = new OptionsBuilder()
  19. .include(Sample_12_Forking_1.class.getSimpleName())
  20. .build();
  21. new Runner(opt).run();
  22. }
  23. public static void printProcessID(String name) {
  24. System.out.println();
  25. System.out.println("------------------------------------------");
  26. System.out.println(name + " pid is :" + ManagementFactory.getRuntimeMXBean().getName());
  27. System.out.println("------------------------------------------");
  28. System.out.println();
  29. }
  30. }

我们在main方法 和 带测试的Benchmark setup方法上都打印了PID
输出是这样的:
image.png

  1. main pid is :38424@zyj
  2. setup pid is :38424@zyj

很明显, Benchmark, 与main方法是在同一个JVM中被执行
因为他们的pid是一致的

3. @Fork(1)

在上面的例子中, 我们做点小修改, 使用 @Fork(1)

  1. @Benchmark
  2. @Fork(1) //注解中传入 1
  3. public int measure_1_c1() {
  4. return 1;
  5. }

我们再运行看看输出
image.png

  1. main pid is :11144@zyj
  2. setup pid is :16668@zyj

这个输出非常清晰, benchmark与main方法不是在一个进程中被执行的

4. @Fork()

再修改一下, 如果不传数字, 那JMH会用几个进程执行呢?

  1. @Benchmark
  2. @Fork()//不传, 默认就是-1, 效果等同于传5
  3. public int measure_1_c1() {
  4. return 1;
  5. }

看看输出结果
image.png

  1. main pid is :35456@zyj
  2. setup pid is :10732@zyj
  3. setup pid is :24268@zyj
  4. setup pid is :44916@zyj
  5. setup pid is :40700@zyj
  6. setup pid is :19744@zyj
  1. Benchmark Mode Cnt Score Error Units
  2. Sample_12_Forking_default.measure_1_c1 avgt 5 1.791 ± 0.247 ns/op

观察执行输出
main方法用了一个JVM进程
另外benchmark又执行了5次, 每一次都是放入独立的JVM进程中执行

5. @Fork(n)

现在我们再随便传入一个数字试试
这次我传入30试试

  1. @Benchmark
  2. @Fork(30)
  3. public int measure_1_c1() {
  4. return 1;
  5. }

看看输出结果

  1. main pid is :38780@zyj
  2. setup pid is :42280@zyj
  3. setup pid is :32500@zyj
  4. ....
  1. Benchmark Mode Cnt Score Error Units
  2. Sample_12_Forking_n.measure_1_c1 avgt 30 1.662 ± 0.025 ns/op

benchmark被执行了30次, 每一次都放入了全新的JVM进程中


看了上面几个例子, 基本上就理解了 Fork是个什么东西了
在我们写JMH用例的时候, 最好是将benchmark单独放入一个进程中去执行, 这样可以提供最纯净的环境


6. 示例代码

JDK8之下此例才有意义

  1. @Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
  2. @Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
  3. @State(Scope.Thread)
  4. @BenchmarkMode(Mode.AverageTime)
  5. @OutputTimeUnit(TimeUnit.NANOSECONDS)
  6. public class Sample_12_Fork_My {
  7. public interface Counter {
  8. int inc();
  9. }
  10. public static class Counter1 implements Counter {
  11. private int x;
  12. @Override
  13. public int inc() {
  14. return x++;
  15. }
  16. }
  17. public static class Counter2 implements Counter {
  18. private int x;
  19. @Override
  20. public int inc() {
  21. return x++;
  22. }
  23. }
  24. public int measure(Counter c) {
  25. int s = 0;
  26. for (int i = 0; i < 1000; i++) {
  27. s += c.inc();
  28. }
  29. return s;
  30. }
  31. //c1与c2代码完全一致, 但是类不一样
  32. Counter c1 = new Counter1();
  33. Counter c2 = new Counter2();
  34. @Benchmark
  35. @Fork(0) //与main方法同一个进程执行
  36. public int measure_1_c1() {
  37. return measure(c1);
  38. }
  39. @Benchmark
  40. @Fork(0) //与main方法同一个进程执行
  41. public int measure_2_c2() {
  42. return measure(c2);
  43. }
  44. @Benchmark
  45. @Fork(0) //与main方法同一个进程执行
  46. public int measure_3_c1_again() {
  47. return measure(c1);
  48. }
  49. @Benchmark
  50. @Fork(1) //放入新的jvm中执行
  51. public int measure_4_forked_c1() {
  52. return measure(c1);
  53. }
  54. @Benchmark
  55. @Fork(1) //放入新的jvm中执行
  56. public int measure_5_forked_c2() {
  57. return measure(c2);
  58. }
  59. public static void main(String[] args) throws RunnerException {
  60. Options opt = new OptionsBuilder()
  61. .include(Sample_12_Fork_My.class.getSimpleName())
  62. .jvmArgs("-server")
  63. .build();
  64. new Runner(opt).run();
  65. }
  66. }

运行结果

  1. Benchmark Mode Cnt Score Error Units
  2. Sample_12_Fork_My.measure_1_c1 avgt 1564.675 ns/op
  3. Sample_12_Fork_My.measure_2_c2 avgt 1615.291 ns/op
  4. Sample_12_Fork_My.measure_3_c1_again avgt 1556.856 ns/op
  5. Sample_12_Fork_My.measure_4_forked_c1 avgt 268.587 ns/op
  6. Sample_12_Fork_My.measure_5_forked_c2 avgt 256.540 ns/op

这个运行结果与google上查的预期不一致, 这可能是我用的是 dragonwell-JDK11, 没有得到网上的结果