user1906966
user1906966

Reputation: 49

puzzled with java if else performance

I am doing an investigation on a method's performance and finally identified the overhead was caused by the "else" portion of the if else statement. I have written a small program to illustrate the performance difference even when the else portion of the code never gets executed:

public class TestIfPerf
{
    public static void main( String[] args )
    {   
        boolean condition = true; 
        long time = 0L;
        int value = 0;
        // warm up test 
        for( int count=0; count<10000000; count++ )
        {       
            if ( condition ) 
            {           
                value = 1 + 2;  
            }           
            else        
            {           
                value = 1 + 3;  
            }           
        }       
        // benchmark if condition only
        time = System.nanoTime();
        for( int count=0; count<10000000; count++ )
        {
            if ( condition )
            {
                value = 1 + 2;
            }           
        }
        time = System.nanoTime() - time; 
        System.out.println( "1) performance " + time ); 
        time = System.nanoTime();
        // benchmark if else condition
        for( int count=0; count<10000000; count++ )
        {
            if ( condition )
            {
                value = 1 + 2;
            }           
            else
            {
                value = 1 + 3;
            }
        }
        time = System.nanoTime() - time; 
        System.out.println( "2) performance " + time ); 
    }   
}

and run the test program with java -classpath . -Dmx=800m -Dms=800m TestIfPerf.

I performed this on both Mac and Linux Java with 1.6 latest build. Consistently the first benchmark, without the else is much faster than the second benchmark with the else section even though the code is structured such that the else portion is never executed because of the condition. I understand that to some, the difference might not be significant but the relative performance difference is large. I wonder if anyone has any insight to this (or maybe there is something I did incorrectly).


Linux benchmark (in nano)

  1. performance 1215488
  2. performance 2629531

Mac benchmark (in nano)

  1. performance 1667000
  2. performance 4208000

Upvotes: 4

Views: 1053

Answers (6)

R.Moeller
R.Moeller

Reputation: 3446

must be related to VM init (the warm up is quite short) or jitter in time measurement (related to VM startup).

if you swap the loops, loop 2 gets faster :-)

In general the hotspot JIT is decent but unreliable and not that deterministic. To obtain best performance in java

  • avoid object creation
  • flag vars as final whenever possible. Sometimes this does not make a difference but sometimes it does. When accessing instance vars in a loop copy them to a final local.
  • mark methods final if possible.
  • if you want a method to get inlined, keep it short, maybe break it up. I verified this many times by testing, thaht by splitting "void foo() { if (..) return ..stuff... }" gets inlined/faster when split like "foo() { if (..) return; else foo1() } void foo1() { stuff }"

In general it is pretty hard to prove performance patterns using micro benchmarks, as you don't know what exactly is triggering inlining, jit compilation and further runtime optimization. There are thresholds in the JIT, so it may happen your performance slows down, just because you added a statement to a method or added a subclass of an existing class.

Upvotes: 0

Neo Wang
Neo Wang

Reputation: 47

Java code:

        for (int count = 0; count < 10000000; count++) {
            // start of the if
            if (cond) {
                value = 1 + 2;
            }
            // end of the if
        }

Java bytecode:

   7:   lstore_3      //store a long value in a local variable 3
   8:   iconst_0      //load the int value 0 onto the stack
   9:   istore  5     //store int value into variable #index
   11:  goto    23    //goes to another instruction at branchoffset
   14:  iload_1       //load an int value from local variable 1
   15:  ifeq    20    //if value is 0, branch to instruction at branchoffset
   18:  iconst_3      //load the int value 3 onto the stack
   19:  istore_2      //store int value into variable 2
   20:  iinc    5, 1  //increment local variable #index by signed byte const
   23:  iload   5     //load an int value from a local variable #index
   25:  ldc #22;  //push a constant #index from a constant pool (String, int or float) onto the stack - int 10000000
   27:  if_icmplt 14  //if value1 is less than value2, branch to instruction at branchoffset

Java code:

        for (int count = 0; count < 10000000; count++) {
            // start of the if
            if (cond) {
                value = 1 + 2;
            } else {
                value = 1 + 3;
            }
            // end of the if
        }

Java bytecode:

   63:  lstore_3      //store a long value in a local variable 3
   64:  iconst_0      //load the int value 0 onto the stack
   65:  istore  5     //store int value into variable #index
   67:  goto    84    //goes to another instruction at branchoffset
   70:  iload_1       //load an int value from local variable 1
   71:  ifeq    79    //if value is 0, branch to instruction at branchoffset
   74:  iconst_3      //load the int value 3 onto the stack
   75:  istore_2      //store int value into variable 2
   76:  goto    81    //goes to another instruction at branchoffset
   79:  iconst_4      //load the int value 4 onto the stack
   80:  istore_2      //store int value into variable 2
   81:  iinc    5, 1  //increment local variable #index by signed byte const
   84:  iload   5     //load an int value from a local variable #index
   86:  ldc #22;  //push a constant #index from a constant pool (String, int or float) onto the stack - int 10000000
   88:  if_icmplt 70  //if value1 is less than value2, branch to instruction at branchoffset

Upvotes: 0

Stephen C
Stephen C

Reputation: 718798

There are two possible explanations:

  • The times you are getting are being distorted by benchmark flaws. You are doing a number of things wrong - see How do I write a correct micro-benchmark in Java?

  • The version with the else genuinely is taking slightly longer per loop iteration. If that is the case there are a number of possible explanations. The best way to get a handle on it is to look at the native code generated by the JIT compiler and analyse its performance.

But the bottom line is that this is neither surprising (see above), or of any real consequence for the vast majority of Java applications. It is the application that determines whether an "if then" or "if then else" is required.

And it is doubtful that anything you might learn from artificial micro-benchmarks like this will be instructive for real code. The JIT compiler is likely to optimize the code at a more sophisticated level than your test is exercising. What you might see here (if your benchmark wasn't flawed) is unlikely to be reflected in a real application.

Upvotes: 2

Neo Wang
Neo Wang

Reputation: 47

public class Main {

public static void main(String[] args) {
    boolean cond = true;
    int nothing = 0;

    for (int i = 0; i < 20; i++) {
        int value = 0;
        long time = System.nanoTime();
        for (int count = 0; count < 10000000; count++) {
            if (cond) {
                value = 1 + 2;
            }
        }

        time = System.nanoTime() - time;
        System.out.println("1) performance: " + time);
        nothing = value; // prevent java ignoring value

        value = 0;
        time = System.nanoTime();
        for (int count = 0; count < 10000000; count++) {
            if (cond) {
                value = 1 + 2;
            } else {
                value = 1 + 3;
            }
        }
        time = System.nanoTime() - time;
        System.out.println("2) performance: " + time);
        nothing = value; // prevent java ignoring value
    }

    nothing = nothing + 1;
}
}

This is the result:

1) performance: 1797000
2) performance: 3742000
1) performance: 7290000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 1000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 1000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0

Upvotes: -1

Diego Basch
Diego Basch

Reputation: 13069

Your test is bunk. If I swap the test conditions I get the exact opposite results:

1) performance 5891113
2) performance 15216601

2) performance 5428062
1) performance 15087676

It probably has to do with the JVM optimizing the code as the execution progresses. If I copy/paste the conditions a few times, I get this:

2) performance 6258694
1) performance 34484277
2) performance 978
1) performance 978
2) performance 908

Upvotes: 5

lbalazscs
lbalazscs

Reputation: 17784

It gets even more bizarre if you comment out the else branch in the second measurement: it is still slower, although it is the same code now. The good news is that if you extract this code in a separate method, it runs much faster the second time.

The only thing I can think of is that the JVM optimizes only the first part of long methods. Yeah: if I put the if-else measurement measurement first, it is faster.

Upvotes: 0

Related Questions