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

标签 java intrinsics microbenchmark jmh

每个尝试利用 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() 的测量值大约是 与基线测试相同。因为返回值 Math.log() 从未使用过。因此 HotSpot 编译器 将消除死代码。好的,明白了,但是编译器如何决定可以消除 Math.log()。

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

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

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

  1. 返回 42
  2. 参数添加
  3. 创建空文件

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

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

  • 在编译时,HotSpot 检查 Math.log() 的内部实现(在 jdk 中?)是否存在,并用该代码替换 Math.log()。
  • 然后进行第二次检查,HotSpot 检查方法的返回值。根据这一结果,HotSpot 决定完全消除 Math.log() 调用。

最佳答案

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?

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

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 不会查看任意 native 调用以确定它们可能具有或不具有何种副作用。这意味着 native 调用确实是作为方法调用进行的(在汇编级别,您跳转到某个地方的外部代码,堆栈上的另一个框架等)。这也意味着 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?

一些 native 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 编译器中的优化方法替换。它们可用于替换任何方法( native 或其他):

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

关于java - Math.log() 的死代码消除如何在 JMH 示例中工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31751252/

相关文章:

benchmarking - STREAM内存带宽基准测试真正衡量了什么?

java - 读取 XML 并将 DOM 模型保留在内存中

Java 依赖关系图

java - Printf 问题还是程序问题?

c - 在 KNC (Xeon Phi) 中查找 vector 数组中数字的实例

c++ - 如何在内部函数中使用 if 条件

java - 需要澄清引导 Spring Boot 应用程序的推荐方法

gcc - 使用 SSE2 内在函数和 gcc 内联汇编器

c++ - 为什么使用较大数组的 SIMD 内在函数可以获得比标量更大的相对加速比?

java - Java 微基准测试系统