我正在尝试编写 jmh 基准。
我在各种博客上都提到了 jmh 基准测试中的陷阱。常见的例子是
int sum() {
int a =7;
int b = 8;
return a+b;
}
将优化为
int sum() {
return 15;
}
int sum(int y) {
int x = new Object();
return y;
}
将优化为
int sum(int y) {
return y;
}
即删除未使用的对象初始化。
下面是我面临的问题。
假设有几个方法,这是调用图的样子
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。但是
从 JVM 的角度来看,methodC 总是使用相同的引用调用,它会不会优化 methodC 以始终返回相同的 returnValueC?
为什么不这样做?
我们如何确保不会进行此优化?通过每次使用 @State(Scope.Thread) 传递不同的引用?
是否有详尽的清单来解释所有可能的优化?
答案 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
中,我没有。结果 simulateWork
从 methodOne
中消除,如结果所示:
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
请注意,methodOne
和 methodTwo
之间几乎没有区别。