Search code examples
javascala

How to make Scala or Java JVM remove branching if condition is same in runtime?


For example I have expression like this

if (DEBUG) log.debug("hello")

Is there a way in scala or java to initiate DEBUG variable in such way, so that if it is false whole expression is removed in runtime?

My guess that branching will be removed by JIT in runtime, but is it? Is there a way to "give a hint" to JIT to do it?


Solution

  • What you can do

    If a value is a constant, mark it static final. This also protects you from accidential modifications. If you need the variable to be per-instance (e.g. set in the constructor), just make it final. The JIT will also be able to make use of that information.

    But the JIT is not the only compiler capable of using that information.
    Let's say you have the following Java code:

    class A{
        static final boolean DEBUG = false;
        void a(){
            if(DEBUG){
                System.out.println("Hello World");
            }
        }
    }
    

    and you look at the bytecode generated by javac (using javap -c A.class), it will look something like that:

    Compiled from "A.java"
    class A {
      static final boolean DEBUG;
    
      A();
        Code:
           0: aload_0
           1: invokespecial #3                  // Method java/lang/Object."<init>":()V
           4: return
    
      void a();
        Code:
           0: return
    }
    

    As you see, javac (I don't know about the Scala compiler though) is already capable of eliminating some branches originating from static final fields if they are initialized with a literal. But this also means you need to recompile all classes using that field if you change your DEBUG variable.

    If you want to avoid that and still be sure the JIT optimizes it away at runtime, you can do something like:

    static final boolean DEBUG = initDebug();
    private static boolean initDebug(){
        return DEBUG;
    }
    

    What the JIT does

    Now, even if javac can't inline it, there is a lot more the JIT can do. It can see that a variable is final, assume it is never changed and optimize for that.

    But even if the result comes from a method that is called over and over and just happens to always return the same value, even if the JIT cannot inline the value, it can still detect that some branch is never executed. In that case, it typically replaces this branch with a deoptimization trap (if it would need to be called, the JIT deoptimizes the code and starts again). This would only happen for code that gets executed sufficiently often (and for other code, you don't need to care as it's normally irrelevant for performance).

    But you shouldn't care unless you have to

    In many cases, the things you think are important for performance are irrelevant. If you ever think you should optimize something, please first check whether it is actually a bottleneck. You can use flamegraphs or similar tools for this.

    Once you get to the point of optimizing a piece of code, first use JMH to actually measure the performance without the optimization. After doing your optimizations, measure it again (with JMH) and check whether it improved. If you don't see a significant improvement (or it even gets worse), the optimization is probably not worth it. You don't know which code will perform well and your optimizations may make performance worse because they could make some JIT optimizations harder or have similar issues.

    When writing Java applications, don't optimize for performance unless you know it is a problem. The JIT makes sure that what is relevant for performance is optimized well. Don't try to interfere with it unless you know that part of the application causes problems for your application and check whether your "optimizations" actually improve anything.