如何解决JMH 基准测试避免 jvm 优化
我正在尝试编写 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;
}
即删除未使用的对象初始化。
但是这个列表并没有涵盖所有类型的优化 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。但是
-
从 JVM 的角度来看,methodC 总是使用相同的引用调用,它会不会优化 methodC 以始终返回相同的 returnValueC?
-
为什么不这样做?
-
我们如何确保不会进行此优化?通过每次使用 @State(Scope.Thread) 传递不同的引用?
-
是否有详尽的清单来解释所有可能的优化?
解决方法
您是说要测试 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)
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
之间几乎没有区别。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。