避免死代码消除
Blackhole 与状态
避免死代码消除 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
避免死代码消除
微基准测试中最大的陷阱是死代码消除(DCE):如果即时编译器证明某个结果从未被使用,就会删除这项计算。这样,您的基准测试测量到的就什么也没有。JMH 提供了两种防止这种情况的工具——返回值和 Blackhole。
DCE 陷阱
这个循环计算一百万次平方根,却从未使用计算结果。聪明的编译器可以删除整个循环。
public class Main {
public static void main(String[] args) {
long t = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
double ignored = Math.sqrt(i); // result thrown away
}
System.out.println("ns: " + (System.nanoTime() - t));
}
}修复方法 1:返回结果
最简单的防范方法是从 @Benchmark 方法中返回计算出的值。JMH 会消费每一个返回值,因此 JVM 无法证明该值不会被使用。
import org.openjdk.jmh.annotations.Benchmark;
public class Bench {
@Benchmark
public double sqrt() {
return Math.sqrt(42.0); // returned -> consumed by JMH
}
}修复方法 2:黑洞
当基准测试产生多个值,或者您无法返回某个值时,请注入一个 Blackhole 并调用 consume(...)。黑洞会让即时编译器相信每个值都已被使用,同时不会产生真实工作的开销。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public void manyValues(Blackhole bh) {
for (int i = 0; i < 100; i++) {
bh.consume(Math.sqrt(i));
}
}
}@State 对象
@State 类会保存对优化器不透明的输入值,从而避免常量折叠。JMH 会实例化该类,并将其传递给您的基准测试。作用域可以是 Thread、Benchmark(共享)或 Group。
import org.openjdk.jmh.annotations.*;
public class Bench {
@State(Scope.Thread)
public static class Data {
public int x = 21;
}
@Benchmark
public int multiply(Data d) {
return d.x * 2; // d.x is not a compile-time constant
}
}@Setup 与 @TearDown
在 @State 类中,带有 @Setup 注解的方法会在测量前运行,带有 @TearDown 注解的方法会在测量后运行。Level 控制运行频率:Trial(一次)、Iteration 或 Invocation。
import org.openjdk.jmh.annotations.*;
@State(Scope.Thread)
public class Data {
int[] arr;
@Setup(Level.Trial)
public void init() {
arr = new int[1000];
for (int i = 0; i < arr.length; i++) arr[i] = i;
}
}避免使用常量输入
请勿将字面量常量传给待测方法。Math.sqrt(2.0) 可能会被折叠为常量;Math.sqrt(state.value) 则不会。请始终从 @State 字段获取输入。
返回多个值
如果您的基准测试自然会产生两个结果,您可以返回一个结果,并通过黑洞对象消费另一个结果,也可以将二者合并。任一结果未被消费,都会再次留下 DCE 漏洞。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public int twoResults(Blackhole bh) {
int a = 3 * 7;
int b = 5 + 9;
bh.consume(b);
return a;
}
}黑洞开销
黑洞对象经过设计,开销极低,而且自身也能抵抗优化,但它并非完全没有开销。对于超快速操作,请优先返回单个值;循环和多值场景再使用黑洞对象。
检查您的数值是否合理
如果基准测试报告了低得不可能的耗时(实际工作却低于一纳秒),请怀疑 DCE 或常量折叠。请重新检查结果是否已返回或消费,以及输入是否来自 @State。
综合运用
一个可靠的基准测试应从 @State 对象读取输入,执行操作,然后返回结果或通过 Blackhole.consume 消费结果。养成这三个习惯后,DCE 和常量折叠就不会破坏您的测量结果。
快速检查
检查您对如何阻止死代码消除的理解。
回顾
您学会了如何让基准测试保持可靠:
- 死代码消除会删除未使用的计算。
- 返回单个结果,让 JMH 消费它。
- 对循环和多个值使用 Blackhole.consume。
- @State 对象会让输入保持不可见,从而阻止常量折叠。
- @Setup / @TearDown 会在选定的级别准备和清理状态。
常见问题解答
「避免死代码消除」课时是免费的吗?
是的 — 「避免死代码消除」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「避免死代码消除」这节课中我会学到什么?
Blackhole 与状态 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「避免死代码消除」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。