I'm currently learning to program in Java, and I have a question about the unary incrementers listed in the title I haven't been able to find elsewhere. I just started playing around with them, and couldn't quite decide which one to use in a for loop because it seems the difference in behaviors between prefix(increment then evaluate) and postfix(evaluate then increment) wouldn't apply in a for statement. So I tried both, and both worked. This worries me, because I want to use them the way they're meant to be used.
So my question is, are they truly interchangeable in incrementing a for loop, or is there some obscure problem I'll run into down the road using one vice the other?
I decided to time them (below), and ++x
definitely runs faster than x++
, but I have no idea why. Can anyone expand on that?
Thanks!
public class PlusPlus
{
public static void main(String[] args)
{
long startTime1, startTime2, endTime1, endTime2;
final double COUNT = 100000000;
//times x++ incrementing
startTime1 = System.currentTimeMillis();
for(int x = 0; x < COUNT; x++);
endTime1 = System.currentTimeMillis();
System.out.println("x++ loop: " + (endTime1 - startTime1) + " milliseconds");
//times ++x incrementing
startTime2 = System.currentTimeMillis();
for(int x = 0; x < COUNT; ++x);
endTime2 = System.currentTimeMillis();
System.out.println("++x loop: " + (endTime2 - startTime2) + " milliseconds");
}
}
Your test isn't going to yield much in terms of knowing how the performance works. Because of the nature of the JVM, writing performance tests is difficult enough that entire frameworks are written for benchmarking.
To really understand what the difference is, you should decompile the code using javap
.
I compiled this class:
public class MyClass {
public static void main(String[] args) {
for (int i = 0; i < 20; i++)
System.out.println(i);
}
}
And ran javap -c MyClass
to get this for main
:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: bipush 20
5: if_icmpge 21
8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
15: iinc 1, 1
18: goto 2
21: return
}
I then did it again, but this time, used the pre-increment:
for (int i = 0; i < 20; ++i)
And I got this for javap
:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: bipush 20
5: if_icmpge 21
8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
15: iinc 1, 1
18: goto 2
21: return
}
Notice anything? Particularly that they're exactly the same?
This is because Java optimized away the extra instructions around pre-loading versus post-loading the value. It recognizes that the instruction it generates to load the variable into a register (iload_#
in javap
output) is redundant and unnecessary and optimizes it out.
In programming, there's a term called premature optimization. This is where you optimize something that you don't really need to optimize because it could cause performance problems. All you're really doing is making your code more complex. Most "good" programmers (if such a being exists) avoid this and instead focus on writing code that's maintainable. If the maintainable code doesn't perform well, then it's optimized. I remember reading a response here on SO about the 3 different levels of programmers (beginner, intermediate, advanced) and how to tell (loosely) which category you fall into. I'll see if I can find it.
Edit: Quick explanation of my answer: javac
compiles your .java files into bytecode. Bytecode is stored in .class files. The bytecode tells the JVM step-by-step how to accomplish something. For example, iinc 1, 1
tells it to take variable 1
(in this case, this is i
) and increment it by 1. iload_1
tells it to take variable 1
and load it so it can be used, usually in an operation or a method call. For example, this:
8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_1
12: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
means "get System.out
and load it, then load variable #1 (i
), now call method #3." When we call the method, the first thing loaded (System.out
) is our "target", meaning call the method on that guy. Every other thing loaded in is passed as an argument. In this case, that means i
. So those three lines represent the line System.out.println(i)
. The bytecode just tells Java what to do to actually do that.
The fact that there is no iload_1
in the decompiled code for both pre- and post-increment means that Java optimized it away since it realized it wasn't going to actually be used. In both cases, it just did iinc
without an iload
, which is exactly contrary to most of the other answers in this thread.
As far as efficiency goes, we are splitting hairs. Even when you really try to optimize your down to the metal code, x++
vs ++x
isnt super important. However, yes, ++x
is technically faster because x++
must store the old value of x, increment it, and then return the old value. ++x
does not need to store it.
The big reason to worry about ++x
vs x++
is really all about the return value. Sometimes you want to have one over the other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With