每个试图利用 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() 是一个本地方法。并且本机调用进入操作系统并执行相应的库。对?这导致我们假设,如果不使用它们的返回值并且它们不执行 io 操作,编译器可以消除本机调用。
我们想知道如果驻留在操作系统中某处并处理来自 java 世界的本机调用的库不提供返回值但执行 io 操作(例如日志记录)会怎样。这些指令会被彻底清除吗?
为了证明我们的假设,我们使用简单的 JMH 测试和本地调用重构了场景。我们编译了三个 c-native 库来执行:
- 返回 42
- 参数添加
- 空文件创建
正如我们在 JMH 测试中所称的那样(类似于 measureWrong() 测试),它们都没有被消除,即使是不执行 io 操作的那个也没有。由于测试结果无法证实我们的假设。本机调用无法优化,这意味着 Math.log() 和自定义本机调用没有相同的基础。他们不是平等的本地人。也许我们在原生 lib 代码中犯了一个错误,至少应该取消 test 1 的原生调用。如果这是真的,我们将与您分享我们的代码。
所以我们进一步搜索,发现了一个术语 Intrinsics,其中 java 代码将替换为对应于架构非常优化的代码。java.lang.Math.log() 具有这样的内在实现。Natives和Intrinsics之间有什么关系吗?如果上面关于 Natives 和 Intrinsics 之间关系的假设成立,编译器会执行以下步骤来消除 native 调用吗?
- 在编译时,HotSpot 检查 Math.log() 的内在实现(在 jdk 中?)是否存在,并用该代码替换 Math.log()。
- 之后第二次检查发生在 HotSpot 查看方法的返回值的地方。根据这个结果,HotSpot 决定完全消除 Math.log() 调用。