系列文章目录
jmh学习笔记-源代码编译与bench mode
jmh学习笔记-State共享对象
jmh学习笔记-State共享对象前后置方法
jmh学习笔记-代码清除
jmh学习笔记-常量折叠
jmh学习笔记-Forking分叉
jmh学习笔记-环境配置
jmh学习笔记-缓存行的处理方式
jmh学习笔记-自定义项目引入jmh
前言
消除死代码的另一面是常量折叠。 如果JVM意识到无论如何计算结果都是相同的,它可以巧妙地优化它。 这意味着我们可以将计算移到内部JMH循环之外,这样最后的测试结果是不精确的。 通过始终读取@State对象的非最终实例字段的输入,基于这些值计算结果并遵循规则以防止DCE,可以防止此情况。
提示:以下是本篇文章正文内容,下面案例可供参考
一、常量折叠
1、常量折叠
// IDEs will probably also say "Look, it could be final". Don't. Trust. Them. Either.
// (While this is normally fine advice, it does not work in the context of measuring correctly.)
private final double wrongX = Math.PI;
@Benchmark
public double baseline() {
// simply return the value, this is a baseline
return Math.PI;
}
@Benchmark
public double measureWrong_1() {
// This is wrong: the source is predictable, and computation is foldable.
return Math.log(Math.PI);
}
@Benchmark
public double measureWrong_2() {
// This is wrong: the source is predictable, and computation is foldable.
return Math.log(wrongX);
}
在上面的基准测试代码当中,由于Math.PI与wrongX(通过final修饰)都是不可变的,所以measureWrong_1和measureWrong_2结果值都是不可变的,这样在迭代测试过程中针对这两个方法的循环都返回的是相同的值,JVM会判断出然后进行优化,不再真实通过Math.log计算结果,而是直接返回结果值。这就是所谓的常量折叠问题,属于JVM优化的一个方案。对应一般代码来说,这是优化,但是对于基准测试来说,则会使测试的结果不精准了.
2、避免常量折叠
jmh为了避免常量折叠,首先实例不能是final类型的,其次需要通过@State注解。
This can be prevented by always reading the inputs from non-final instance fields of @State objects, computing the result based on those values, and follow the rules to prevent DCE.
比如
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JMHSample_10_ConstantFold {
private double x = Math.PI;
@Benchmark
public double baseline() {
// simply return the value, this is a baseline
return Math.PI;
}
@Benchmark
public double measureRight() {
// This is correct: the source is not predictable.
return Math.log(x);
}
}
3、测试结果
针对以上代码进行测试如下
# JMH version: 1.26
# VM version: JDK 1.8.0_121, Java HotSpot(TM) 64-Bit Server VM, 25.121-b13
# VM invoker: C:\Program Files\Java\jdk1.8.0_121\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar=63626:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.openjdk.jmh.samples.JMHSample_10_ConstantFold.baseline
# Run progress: 0.00% complete, ETA 00:06:40
# Fork: 1 of 1
# Warmup Iteration 1: 5.229 ns/op
# Warmup Iteration 2: 5.244 ns/op
# Warmup Iteration 3: 4.159 ns/op
# Warmup Iteration 4: 4.189 ns/op
# Warmup Iteration 5: 4.439 ns/op
Iteration 1: 4.321 ns/op
Iteration 2: 4.151 ns/op
Iteration 3: 4.157 ns/op
Iteration 4: 4.124 ns/op
Iteration 5: 4.135 ns/op
Result "org.openjdk.jmh.samples.JMHSample_10_ConstantFold.baseline":
4.178 ±(99.9%) 0.313 ns/op [Average]
(min, avg, max) = (4.124, 4.178, 4.321), stdev = 0.081
CI (99.9%): [3.865, 4.490] (assumes normal distribution)
# JMH version: 1.26
# VM version: JDK 1.8.0_121, Java HotSpot(TM) 64-Bit Server VM, 25.121-b13
# VM invoker: C:\Program Files\Java\jdk1.8.0_121\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar=63626:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureRight
# Run progress: 25.00% complete, ETA 00:05:06
# Fork: 1 of 1
# Warmup Iteration 1: 35.830 ns/op
# Warmup Iteration 2: 35.950 ns/op
# Warmup Iteration 3: 35.603 ns/op
# Warmup Iteration 4: 35.940 ns/op
# Warmup Iteration 5: 35.849 ns/op
Iteration 1: 35.635 ns/op
Iteration 2: 29.650 ns/op
Iteration 3: 28.012 ns/op
Iteration 4: 27.998 ns/op
Iteration 5: 29.232 ns/op
Result "org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureRight":
30.106 ±(99.9%) 12.234 ns/op [Average]
(min, avg, max) = (27.998, 30.106, 35.635), stdev = 3.177
CI (99.9%): [17.872, 42.339] (assumes normal distribution)
# JMH version: 1.26
# VM version: JDK 1.8.0_121, Java HotSpot(TM) 64-Bit Server VM, 25.121-b13
# VM invoker: C:\Program Files\Java\jdk1.8.0_121\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar=63626:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureWrong_1
# Run progress: 50.00% complete, ETA 00:03:23
# Fork: 1 of 1
# Warmup Iteration 1: 4.830 ns/op
# Warmup Iteration 2: 5.127 ns/op
# Warmup Iteration 3: 4.441 ns/op
# Warmup Iteration 4: 4.248 ns/op
# Warmup Iteration 5: 4.373 ns/op
Iteration 1: 4.125 ns/op
Iteration 2: 4.104 ns/op
Iteration 3: 4.115 ns/op
Iteration 4: 4.073 ns/op
Iteration 5: 4.192 ns/op
Result "org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureWrong_1":
4.122 ±(99.9%) 0.170 ns/op [Average]
(min, avg, max) = (4.073, 4.122, 4.192), stdev = 0.044
CI (99.9%): [3.952, 4.292] (assumes normal distribution)
# JMH version: 1.26
# VM version: JDK 1.8.0_121, Java HotSpot(TM) 64-Bit Server VM, 25.121-b13
# VM invoker: C:\Program Files\Java\jdk1.8.0_121\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar=63626:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureWrong_2
# Run progress: 75.00% complete, ETA 00:01:41
# Fork: 1 of 1
# Warmup Iteration 1: 5.614 ns/op
# Warmup Iteration 2: 6.327 ns/op
# Warmup Iteration 3: 4.147 ns/op
# Warmup Iteration 4: 4.109 ns/op
# Warmup Iteration 5: 4.183 ns/op
Iteration 1: 4.164 ns/op
Iteration 2: 4.156 ns/op
Iteration 3: 4.188 ns/op
Iteration 4: 4.298 ns/op
Iteration 5: 4.148 ns/op
Result "org.openjdk.jmh.samples.JMHSample_10_ConstantFold.measureWrong_2":
4.191 ±(99.9%) 0.238 ns/op [Average]
(min, avg, max) = (4.148, 4.191, 4.298), stdev = 0.062
CI (99.9%): [3.953, 4.429] (assumes normal distribution)
# Run complete. Total time: 00:06:45
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark Mode Cnt Score Error Units
JMHSample_10_ConstantFold.baseline avgt 5 4.178 ± 0.313 ns/op
JMHSample_10_ConstantFold.measureRight avgt 5 30.106 ± 12.234 ns/op
JMHSample_10_ConstantFold.measureWrong_1 avgt 5 4.122 ± 0.170 ns/op
JMHSample_10_ConstantFold.measureWrong_2 avgt 5 4.191 ± 0.238 ns/op
从测试结果不难看出,常量折叠导致测试的平均值与真实计算相差很大。
总结
通常编译器的优化对程序执行有很大的帮助,但是在基准测试时必须要尽量避免它。