Search code examples
javaalgorithmperformancedesign-patternsperformancecounter

Redundant assignment vs check before assignment in Java


In front of a long loop, it costs more (processor + memory) to assign redundantly the same value, or to check before the assignment? 

int count = 0;
for(int i=0; i<100_000; i++){
    if (...) {
        count++
        doLogic(count); // logic is strictly related with count
    } else {
      count = 0;  //50.000 redundant assignment
    }
}

VS.

int count = 0;
for(int i=0; i<10_000; i++){
    if (...) {
        count++
        doLogic(count); // logic is strictly related with count
    } else {
        if(count > 0) { // 50.000 checks 
          count = 0;
        }
    }
}

And would it cost the same if count would be present in a different object (injected as a singleton in a Spring context) and the increments/check/reset would be like:

config.incrementCount();
config.getCount();
config.resetCount();

Solution

  • The short answer to your question is it doesn't matter. Either approach will have roughly same performance and most probably will be dominated by doLogic.

    Your first choice should always be to write simple and idiomatic code instead of doing premature optimization.


    The long answer is it depends (it always does, doesn't it?).

    First of all, you don't really know what kind of optimizations JIT will do to your code. What is true for one platform and Java version may not be true for another. You can't rely on anything that is not explicitly guaranteed.

    Second, you know what they say about premature optimization. It's always a good idea to benchmark and profile your code, but even benchmarks are not 100% reliable.

    Ok, let's benchmark:

    # First case, variable counter
    Benchmark                 Mode  Cnt  Score   Error  Units
    Benchmark1.testCounter    avgt    8  0.149 ± 0.026  ms/op
    Benchmark1.testCounterIf  avgt    8  0.190 ± 0.036  ms/op
    
    # Second case, counter in the wrapper class
    Benchmark                      Mode  Cnt  Score   Error  Units
    Benchmark1.testCounterClass    avgt    8  0.198 ± 0.025  ms/op
    Benchmark1.testCounterClassIf  avgt    8  0.181 ± 0.016  ms/op
    

    Benchmark code

    While for the case with simple counter variable the "if optimization" seems to lose, in the second case the difference is within the margin of error.

    In my benchmark I just used a simple static field holding Counter class. Looking at the ASM, generated by JIT, it appears that method calls were inlined. Your situation may be different, as enterprise java and Spring are infamous for doing obscure magic behind the scenes (e.g. via proxies and bytecode manipulation). Not only methods may not be inlined, but also some hidden overheads may emerge unexpectedly.


    P.S. If you are interested in performance and microoptimization for JVM, I suggest to read Alexey Shipilev's JVM Anatomy Quarks Series.