Reputation: 49
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)
Mac benchmark (in nano)
Upvotes: 4
Views: 1053
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
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
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
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
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
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
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