Java – JIT – micro optimization – if statement elimination
We assume that we have the following code:
public static void check() { if (Config.initialized) { ... } }
Config. Initialized is false at the beginning and becomes true only at some point after the method has been compiled JIT Value never returns to false
I "know" that many very complex optimizations are in progress (loop unrolling, branch prediction, inlining, escape analysis, etc.). Although I am far from understanding them in detail, I am mainly interested in the following contents:
>Does the JIT compiler have a way to detect that if will always be true after a certain point in time so that the check can be skipped completely? Completely I mean no variable access, no conditional check / jne, etc.. > If JIT can't get rid of (from a certain point) unnecessary sample check (I don't know how it is possible), what can I do to support it? My only idea is to recreate the class and remove unnecessary code from the bytecode after the initialization event
I know this is a complete micro optimization. Even using tools like jmh may be difficult to guarantee, but I still want to know and understand
Last but not least:
>My understanding is correct. If the above methods are inline, all these methods will be recompiled (assuming they are hot) in case something changes, so that the inspection methods need to be recompiled?
If I correctly understand my jitwatch test results, the answer to the above question should be:
>No, no way There will always be conditional checks. > Really only through transformation > Yes
Solution
Yes, if the field is static final and its holder class is initialized at JIT compiler startup Obviously, this does not apply to your situation because config Initialized cannot be static final
java. lang.invoke. Mutablecallsite to rescue
This course is designed for your requirements Its SETTARGET method supports rebinding the calling site at run time It leads to the optimization of the current compilation method, and it is possible to recompile it with a new target in the future
You can use the dynamicinvoker method to obtain the methodhandle used to call the mutablecallsite target Note that the methodhandle should be static final to allow inlining
Yes
This is a benchmark to prove that the mutablecallsite method is as fast as alwaysfalse at the beginning and as fast as alwaystrue after switching I also included a static field switch in @ Holger's suggestion for comparison
package bench; import org.openjdk.jmh.annotations.*; import java.lang.invoke.*; import java.util.concurrent.*; @State(Scope.Benchmark) public class Toggle { static boolean toggleField = false; static final MutableCallSite toggleCallSite = new MutableCallSite(MethodHandles.constant(boolean.class,false)); static final MethodHandle toggleMH = toggleCallSite.dynamicInvoker(); public void switchToggle() { toggleField = true; toggleCallSite.setTarget(MethodHandles.constant(boolean.class,true)); MutableCallSite.syncAll(new MutableCallSite[]{toggleCallSite}); System.out.print("*** Toggle switched *** "); } @Setup public void init() { ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.schedule(this::switchToggle,10100,TimeUnit.MILLISECONDS); executor.shutdown(); } @Benchmark public int alwaysFalse() { return 0; } @Benchmark public int alwaysTrue() { return ThreadLocalRandom.current().nextInt(); } @Benchmark public int field() { if (toggleField) { return ThreadLocalRandom.current().nextInt(); } else { return 0; } } @Benchmark public int mutableCallSite() throws Throwable { if ((boolean) toggleMH.invokeExact()) { return ThreadLocalRandom.current().nextInt(); } else { return 0; } } }
By running the benchmark with 5 warm-up iterations and 10 measurement iterations, I got the following results:
# JMH version: 1.20 # VM version: JDK 1.8.0_192,VM 25.192-b12 # Benchmark: bench.Toggle.alwaysFalse # Run progress: 0,00% complete,ETA 00:01:00 # Fork: 1 of 1 # Warmup Iteration 1: 3,875 ns/op # Warmup Iteration 2: 3,369 ns/op # Warmup Iteration 3: 2,699 ns/op # Warmup Iteration 4: 2,696 ns/op # Warmup Iteration 5: 2,703 ns/op Iteration 1: 2,697 ns/op Iteration 2: 2,696 ns/op Iteration 3: 2,696 ns/op Iteration 4: 2,706 ns/op Iteration 5: *** Toggle switched *** 2,698 ns/op Iteration 6: 2,698 ns/op Iteration 7: 2,692 ns/op Iteration 8: 2,707 ns/op Iteration 9: 2,712 ns/op Iteration 10: 2,702 ns/op # Benchmark: bench.Toggle.alwaysTrue # Run progress: 25,ETA 00:00:48 # Fork: 1 of 1 # Warmup Iteration 1: 5,159 ns/op # Warmup Iteration 2: 5,198 ns/op # Warmup Iteration 3: 4,314 ns/op # Warmup Iteration 4: 4,321 ns/op # Warmup Iteration 5: 4,306 ns/op Iteration 1: 4,306 ns/op Iteration 2: 4,310 ns/op Iteration 3: 4,297 ns/op Iteration 4: 4,324 ns/op Iteration 5: *** Toggle switched *** 4,356 ns/op Iteration 6: 4,300 ns/op Iteration 7: 4,310 ns/op Iteration 8: 4,290 ns/op Iteration 9: 4,297 ns/op Iteration 10: 4,294 ns/op # Benchmark: bench.Toggle.field # Run progress: 50,ETA 00:00:32 # Fork: 1 of 1 # Warmup Iteration 1: 3,596 ns/op # Warmup Iteration 2: 3,429 ns/op # Warmup Iteration 3: 2,973 ns/op # Warmup Iteration 4: 2,937 ns/op # Warmup Iteration 5: 2,934 ns/op Iteration 1: 2,927 ns/op Iteration 2: 2,928 ns/op Iteration 3: 2,932 ns/op Iteration 4: 2,929 ns/op Iteration 5: *** Toggle switched *** 3,002 ns/op Iteration 6: 4,887 ns/op Iteration 7: 4,866 ns/op Iteration 8: 4,877 ns/op Iteration 9: 4,867 ns/op Iteration 10: 4,877 ns/op # Benchmark: bench.Toggle.mutableCallSite # Run progress: 75,ETA 00:00:16 # Fork: 1 of 1 # Warmup Iteration 1: 3,474 ns/op # Warmup Iteration 2: 3,332 ns/op # Warmup Iteration 3: 2,750 ns/op # Warmup Iteration 4: 2,701 ns/op # Warmup Iteration 5: 2,701 ns/op Iteration 1: 2,699 ns/op Iteration 4: 2,771 ns/op Iteration 6: 4,310 ns/op Iteration 7: 4,306 ns/op Iteration 8: 4,312 ns/op Iteration 9: 4,317 ns/op Iteration 10: 4,301 ns/op