Math.log() 的死代码消除如何在 JMH 示例中工作

How does dead code elimination of Math.log() work in JMH sample

每个尝试利用 JMH 框架创建一些有意义的测试的人都会遇到 JMH 示例测试 (http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/)。 当我们通过它们时,我们陷入了死代码淘汰 (JMHSample_08_DeadCode.java)。

摘录:

private double x = Math.PI;

@Benchmark
public void baseline() {
 // do nothing, this is a baseline
}

@Benchmark
public void measureWrong() {
 // This is wrong: result is not used, and the entire computation is optimized out.
 Math.log(x);
}

measureWrong() 的测量值大约是 与基线测试相同。因为 return 的值 Math.log() 从未使用过。因此 HotSpot 编译器 将消除死代码。好的,明白了,但是编译器如何决定 Math.log() 可以被删除。

当我们仔细观察测试时,我们注意到 Math.log() 是一个本地方法。 本机调用向下到 OS 并执行相应的库。正确的? 这使我们假设如果不使用 return 值并且不执行 io 操作,编译器可以消除本机调用。

我们想知道如果驻留在 OS 某处并处理来自 java 世界的本机调用的库会怎么样 不提供 return 值但执行 io 操作(例如日志记录)。 那些指令会被彻底抹去吗?

为了证明我们的假设,我们通过简单的 JMH 测试和本机调用重建了场景。 我们编译了三个c-native libs来执行:

  1. return42
  2. 参数添加
  3. 创建空文件

正如我们在 JMH 测试中对它们的称呼(类似于 measureWrong() 测试),其中 none 已被淘汰,即使是那些不执行 io 操作的。 由于测试结果,我们的假设无法得到证实。本机调用无法优化,这意味着 Math.log() 和自定义本机调用不具有相同的基础。他们不是本地人。也许我们在本地库代码中犯了一个错误,至少应该删除测试 1 的本地调用。如果这是真的,我们将与您分享我们的代码。

所以我们进一步搜索并找到了一个术语 Intrinsics,其中 java code will be replaced with a 对应架构非常优化的代码。 java.lang.Math.log() 具有这样的内在实现。本地人和内在人之间有什么关系吗? 如果上述 Natives 和 Intrinsics 之间关系的假设成立,编译器会执行以下步骤来消除 native 调用吗?

As we looked closely to the test we note that Math.log() is a native method. And native calls go down to the OS and execute a corresponding lib. Right?

本机调用不会转到 OS,它们会通过 JNI 转到本机库。这可能最终进入 OS,或者它可能进入某些用户提供的库。对于 JDK 中的本机方法,我们还可以期望将一些本机调用编译为内部函数。

This lead us to the assumption that native calls could be eliminated by the compiler if their return value is not used and they don't perform io operations.

JVM 不会查看任意本机调用以确定它们可能具有或不具有何种副作用。这意味着本机调用确实是作为方法调用进行的(在汇编级别,您跳转到某个地方的外部代码,堆栈上的另一个框架等)。这也意味着 JVM 无法消除它们或它们的依赖输入。

Native calls cannot be optimized out, meaning that Math.log() and custom native calls do not have the same basis.

是的。

Are there any relations between the Natives and Intrinsics?

一些本机 JDK 方法是内部函数。但是普通的 JDK 方法也可以是内在函数。内部方法集也因一个 JVM 而异。

If the above assumption of the relationship between Natives and Intrinsics is valid will the compiler perform the following steps to eliminate the native call?

Math.log函数转化为C2编译器IR(中间表示)中的一个特殊节点。这个节点可以被优化掉,因为它没有副作用,而且它的值从未被使用过。如果使用该值,则 JVM 知道为此节点发出专用机器代码。

总结: Intrinsics 是内置于 JVM 编译器中的优化方法替换。它们可用于替换任何方法(本机或其他):

  1. 专用汇编代码
  2. 专业IR代码
  3. 内部 JVM 方法调用
  4. 以上的组合