One thing that the JVM's JIT can do is optimise away the cost of a null check in certain cases. This is a nice optimisation; a common pattern is if(a != null) { doSomethingTo(a); }. In this case, the JIT might decide to not compile in the nullcheck, and go straight to execution the method. If a is indeed null, a signal is trapped from the hardware and the handler can then determine the signal can be safely ignored and proceeds.
The intent here is to find out a relative cost of null check and what the likely cost of this extra signal work is. The original discussion was regarding flags for code path enablement. Using a static final likely avoids the null checks here. So - the check was written to avoid the 10K compile threshold and simulates more the use of null/present for control flow and compares it with true/false.
The checks:
package com.bluedevel.nulls; import org.openjdk.jmh.annotations.GenerateMicroBenchmark; import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Setup; @State(Scope.Thread) /** * {@code java -ea -jar target/microbenchmarks.jar ".*NeverNullAlwaysTrue.*"} */ public class NeverNullAlwaysTrue { private Operations ops = new Operations(); public int output = 0; // hopefully this means output is not eliminated public int getOutput() { return output; } @GenerateMicroBenchmark public int testPresentPresent() { output = doTestPP(output, ops); output = doTestPP(output, ops); return output; } private int doTestPP(int input, Operations o) { output = input; if (o != null) { output = o.op1(output); } else { output = ops.op2(output); } return output; } @GenerateMicroBenchmark public int testTrueTrue() { output = doTestTT(output, true); output = doTestTT(output, true); return output; } private int doTestTT(int input, boolean b) { output = input; if (b) { output = ops.op1(output); } else { output = ops.op2(output); } return output; } @GenerateMicroBenchmark public int testZPresentPresent() { output = doTestPP(output, ops); output = doTestPP(output, ops); return output; } @GenerateMicroBenchmark public int testZTrueTrue() { output = doTestTT(output, true); output = doTestTT(output, true); return output; } }And with Operations as:
package com.bluedevel.nulls; public class Operations { public int op1(int i) { return i + 2; } public int op2(int i) { return i + 3; } }The results below are interesting. If you are using the client VM, then you need to pay attention to your test approach. If you are using the server VM, then you can safely ignore it, as the code seems to run at approximately the same performance.
server: Benchmark Mode Thr Cnt Sec Mean Mean error Units c.b.n.NeverNullAlwaysTrue.testPresentPresent thrpt 1 20 5 492452.297 1015.518 ops/msec c.b.n.NeverNullAlwaysTrue.testTrueTrue thrpt 1 20 5 482684.551 2934.298 ops/msec c.b.n.NeverNullAlwaysTrue.testZPresentPresent thrpt 1 20 5 485969.596 8488.587 ops/msec c.b.n.NeverNullAlwaysTrue.testZTrueTrue thrpt 1 20 5 484919.891 2415.732 ops/msec client: Benchmark Mode Thr Cnt Sec Mean Mean error Units c.b.n.NeverNullAlwaysTrue.testPresentPresent thrpt 1 20 5 67216.614 2456.394 ops/msec c.b.n.NeverNullAlwaysTrue.testTrueTrue thrpt 1 20 5 78801.555 2455.900 ops/msec c.b.n.NeverNullAlwaysTrue.testZPresentPresent thrpt 1 20 5 71080.747 790.894 ops/msec c.b.n.NeverNullAlwaysTrue.testZTrueTrue thrpt 1 20 5 83989.886 458.524 ops/msec
Exactly what I was looking for, although the page layout is wrapping the results column so hard to figure out which is which.
ReplyDelete