JMH 基准测试避免 jvm 优化

时间:2021-04-07 15:37:49

标签: java performance performance-testing microbenchmark jmh

我正在尝试编写 jmh 基准。

我在各种博客上都提到了 jmh 基准测试中的陷阱。常见的例子是

  1. 此代码
int sum() {
   int a =7;
   int b = 8;
   return a+b;
}

将优化为

int sum() {
return 15;
}
  1. 此代码
int sum(int y) {
   int x = new Object();
   return y;
}

将优化为

int sum(int y) {
   return y;
}

即删除未使用的对象初始化。

但是这个列表并没有涵盖所有类型的优化 jvm 会做什么。

下面是我面临的问题。

假设有几个方法,这是调用图的样子

int methodA(CustomObjectA a) {
   //do something 
   methodB(a);
   //do something
   return returnValueA;
}

int methodB(CustomObjectA a) {
   //do something 
   methodC(a);
   //do something
   return returnValueB;
}

int methodC(CustomObjectA a) {
   //do something
   return returnValueC;
}

我们将尝试对 methodA 进行基准测试。通过传递在状态对象中创建的 CustomObjectA。但是

  1. 从 JVM 的角度来看,methodC 总是使用相同的引用调用,它会不会优化 methodC 以始终返回相同的 returnValueC?

  2. 为什么不这样做?

  3. 我们如何确保不会进行此优化?通过每次使用 @State(Scope.Thread) 传递不同的引用?

  4. 是否有详尽的清单来解释所有可能的优化?

1 个答案:

答案 0 :(得分:2)

您是说要测试 methodA 而所有其他方法都是 private,这就是调用链的样子?如果是这样,这里的 JMH 无关紧要 - 将应用哪些优化,仍将应用于该代码。也很难说最终会发生什么优化,因为它们在 JVM很多,并且还取决于许多其他因素,例如操作系统、CPU、等等;所以根本不可能存在“广泛的列表”。

例如,根据您在每个方法中的 //do something 中执行的操作,可以省略或不省略该代码。看看这个简化的例子:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 5)
@Measurement(iterations = 5, time = 5)
public class Sample {

    private static final int ME = 1;

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                .include(Sample.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    public int methodOne(CustomObjectA a) {
        simulateWork();
        return 42;
    }

    @Benchmark
    public int methodTwo(CustomObjectA a, Blackhole bh) {
        bh.consume(simulateWork());
        return 42;
    }

    @State(Scope.Thread)
    public static class CustomObjectA {

    }

    private static double simulateWork() {
        return ME << 1;
    }

}

不同之处在于,在方法 methodTwo 中,我使用了所谓的 Blackhole(阅读 this 了解更多详细信息),而在 methodOne 中,我没有。结果 simulateWorkmethodOne 中消除,如结果所示:

Benchmark         Mode  Cnt  Score   Error  Units
Sample.methodOne  avgt   25  1.950 ± 0.078  ns/op
Sample.methodTwo  avgt   25  3.955 ± 0.120  ns/op

另一方面,如果我稍微改变代码以尽可能减少副作用:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 5)
@Measurement(iterations = 5, time = 5)
public class Sample {

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                .include(Sample.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }

    @Benchmark
    public int methodOne(CustomObjectA a) {
        simulateWorkWithA(a);
        return 42;
    }

    @Benchmark
    public int methodTwo(CustomObjectA a) {
        return simulateWorkWithA(a) + 42;
    }

    @Benchmark
    public int methodThree(CustomObjectA a, Blackhole bh) {
        bh.consume(simulateWorkWithA(a));
        return 42;
    }

    @State(Scope.Thread)
    public static class CustomObjectA {
        int x = 0;
    }

    private static int simulateWorkWithA(CustomObjectA a) {
        return a.x = a.x + 1;
    }

}

simulateWorkWithA(a) 中消除 methodOne 不会发生:

Benchmark           Mode  Cnt  Score   Error  Units
Sample.methodOne    avgt   25  2.267 ± 0.198  ns/op
Sample.methodThree  avgt   25  3.711 ± 0.131  ns/op
Sample.methodTwo    avgt   25  2.325 ± 0.008  ns/op

请注意,methodOnemethodTwo 之间几乎没有区别。

相关问题