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

1

5

Everyone who tries to utilize JMH framework to create some meaningful tests will come across JMH sample tests (http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/). As we went through them we stucked by the dead code elemination (JMHSample_08_DeadCode.java).

Excerpt:

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);
}

The measurement of measureWrong() will be approximately the same as for the baseline test. Because the return value of the Math.log() is never used. Therefore the HotSpot compiler will eliminate the dead code. Ok, understood but how can the compiler decide that Math.log() can be eliminated.

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? 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.

We wonder what if the lib which resides somewhere in the OS and which handles the native calls from the java world provides no return value but does io operations (e.g. logging). Will those instructions completely be wiped out?

To proof our assumption we reconstructed the scenario with a simple JMH test and a native call. We compiled three c-native libs to perform:

  1. returning 42
  2. parameter addition
  3. empty file creation

As we called them in a JMH test (similarly to measureWrong() test) none of them has been eliminated, not even the one which doesn't perform an io operation. Due to the test result our assumption cannot be confirmed. Native calls cannot be optimized out, meaning that Math.log() and custom native calls do not have the same basis. They are not equaly native. Maybe we made a mistake in our native lib code and at least the native call of test 1 should have been eleminated. If this is true we will share our code with you.

So we searched further and found a term Intrinsics where java code will be replaced with a corresponding to the architecture very optimized code. java.lang.Math.log() is having such intrinsic implementation. Are there any relations between the Natives and Intrinsics? 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?

  • At the compile time the HotSpot checks the existence of the intrinsic implementation (in jdk?) for the Math.log() and replaces Math.log() with that code.
  • Afterwards the second check happens where HotSpot looks after the return value of a method. And on this outcome the HotSpot decides to eliminate the Math.log() call completely.
Presidentship answered 31/7, 2015 at 16:48 Comment(1)
Math.log is acctually an intrinsic. This means, it is known to the JIT and substituted with a predefined set of machine instructions. Also, native instructions are not eliminated by the jit as it cannot prove they do not have side effects. Android might still treat its native methods as intrinsics.Mouthwatering
H
9

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 calls don't go to the OS, they go to a native library via JNI. That may end up going to the OS, or it may go into some user provided lib. In the case of native methods in the JDK we can also expect some native calls to be compiled as intrinsics.

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.

The JVM does not look into arbitrary native calls to determine what sort of side effects they may or may not have. This means native calls are indeed made as method calls (on the assembly level, you jump into foriegn code somewhere, another frame on the stack etc.). This also means that the JVM cannot eliminate them or their dependant inputs.

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

Yes.

Are there any relations between the Natives and Intrinsics?

Some native JDK methods are intrinsics. But normal JDK methods can be intrinsics too. The set of intrinsic methods is also different from one JVM to the next.

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?

The Math.log function is transformed into a special node in the C2 compiler IR (intermediate representation). This node can be optimized away because it is known to have no side effects and it's value is never used. If the value is used the JVM knows to emit specialized machine code for this node.

In summary: Intrinsics are optimized method replacements built into the JVM compiler. They can be used to replace any method (native or otherwise) with:

  1. Specialized assembly code
  2. Specialized IR code
  3. An internal JVM method call
  4. A combination of the above
Horace answered 3/8, 2015 at 9:6 Comment(1)
Thank you for the detailed and helpful answer. The lack of knowledge about possible side effects within the native code explains why our native call was not eliminated.Presidentship

© 2022 - 2024 — McMap. All rights reserved.