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

Java Math.log()的死代码消除在JMH示例中如何工作,java,intrinsics,microbenchmark,jmh,Java,Intrinsics,Microbenchmark,Jmh,任何试图利用JMH框架创建一些有意义的测试的人都会遇到JMH示例测试 (). 当我们浏览它们的时候,我们被死代码元素(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 w

任何试图利用JMH框架创建一些有意义的测试的人都会遇到JMH示例测试 (). 当我们浏览它们的时候,我们被死代码元素(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);
}
MeasureError()的测量值约为 与基线测试相同。因为 从未使用Math.log()。因此,热点编译器 将消除死代码。好的,明白了,但是编译器如何决定可以删除Math.log()

当我们仔细查看测试时,我们注意到Math.log()是一个本机方法。 而本机调用则进入操作系统并执行相应的lib。对吗? 这导致我们假设,如果不使用本机调用的返回值并且它们不执行io操作,则编译器可以消除本机调用

我们想知道,如果驻留在操作系统中某个地方并处理来自java世界的本机调用的lib 不提供返回值,但执行io操作(如日志记录)。 这些指令会被彻底删除吗

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

  • 返回42
  • 参数加法
  • 创建空文件
  • 正如我们在JMH测试中所调用的(类似于measureError()测试),它们都没有被消除,甚至没有执行io操作的一个也没有。 由于测试结果,我们的假设无法得到证实。无法优化本机调用,这意味着Math.log()和自定义本机调用的基础不同。他们不是同样的本地人。也许我们在本机lib代码中犯了一个错误,至少测试1的本机调用应该被删除。如果这是真的,我们将与您共享我们的代码

    因此,我们进一步搜索并找到了一个术语intrinsic,其中java代码将被替换为 对应的架构非常优化的代码。java.lang.Math.log()具有这样的内在实现。土著人和本质主义者之间有什么关系吗? 如果上述关于本机和内部函数之间关系的假设有效,编译器会执行以下步骤来消除本机调用吗

    • 在编译时,热点检查Math.log()的内在实现(在jdk中?)是否存在,并用该代码替换Math.log()
    • 之后,第二个检查发生在热点关注方法返回值的地方。根据这个结果,HotSpot决定完全取消Math.log()调用
    当我们仔细查看测试时,我们注意到Math.log()是一个本机方法。而本机调用则进入操作系统并执行相应的lib。对吧?

    本机调用不会进入操作系统,而是通过JNI进入本机库。这可能最终进入操作系统,也可能进入用户提供的库。对于JDK中的本机方法,我们还可以期望将一些本机调用编译为内部函数

    这导致我们假设,如果不使用本机调用的返回值并且它们不执行io操作,则编译器可以消除本机调用

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

    无法优化本机调用,这意味着Math.log()和自定义本机调用的基础不同

    土著人和本质主义者之间有什么关系吗

    一些本地JDK方法是内在的。但正常的JDK方法也可以是内在的。不同JVM之间的内在方法集也不同

    如果上述关于本机和内部函数之间关系的假设有效,编译器会执行以下步骤来消除本机调用吗

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

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

  • 专用汇编代码
  • 专用红外码
  • 内部JVM方法调用
  • 上述因素的组合

  • 实际上,Math.log是一个内在的。这意味着,JIT知道它,并用一组预定义的机器指令代替它。此外,本机指令不会被jit消除,因为它无法证明它们没有副作用。Android可能仍然将其原生方法视为内在方法。感谢您提供详细而有用的答案。缺乏关于本机代码中可能的副作用的知识解释了为什么本机调用没有被消除。