0Pricing
Java Academy · 课时

避免死代码消除

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么使用 JMH
  2. 编写基准测试
  3. 预热与迭代
  4. 避免死代码消除
← 返回 Java Academy