Is performance gained when using continue in a for-loop

Viewed 81

continue first? or Should the code just go ahead?

Example:

for (int a : array) {
    if (map.containsKey(a))
        continue;
    ~~~
}

//or

for (int a : array) {
    if (!map.containsKey(a)) {
        ~~~
    }
}

Which phrase is faster? Or is there no difference in performance? Then what's the reason?

2 Answers

They are entirely indistinguishable. Java, like many languages, compiles to bytecode, JVM bytecode in this case. And at the bytecode level, things like if statements don't exist. They get compiled down to conditional and unconditional jumps to labels. Your first code block will compile to a conditional jump back to the top of the loop. Something like

// Pseudocode
loop:
  _cond = map.containsKey(a)
  jump_if_true loop, _cond
  ...
end:
  jump loop

Your second will compile to something like

// Pseudocode
loop:
  _cond = map.containsKey(a)
  jump_if_false end, _cond
  ...
end:
  jump loop

And since end is an unconditional jump to loop, any good optimizer will turn this, immediately, into the former.

Just to be sure, I took the two functions you proposed above (replacing ~~~ with a System.out.println) and compiled them. They produce, verbatim, the same bytecode. After decompiling back to Java, they look entirely identical.

So don't micro-optimize. Write your code, and trust the compiler to handle things within its control. "Should I use continue or if here" is a micro-optimization. "Can I turn this triple nested loop into fewer iterations" is probably a good thing to ask up-front.

In Java, you can also add labels to the continue statement. In fact, continue is actually a syntactic sugar for a goto LABEL statement with LABEL pointing to the top of the most inner loop.

for (int a : array) {
    if (map.containsKey(a))
        continue;
    // ... 
}

Is equivalent to:

label1:
for (int a : array) {
    if (map.containsKey(a))
        continue label1;
    // ... 
}

When compiled, both produce the same bytecode. Actually, when decompiling their respective .class file of either one, you'll see the decompiler converting them into your second version:

for (int a : array) {
    if (!map.containsKey(a)) {
        // ...
    }

Besides the readability reason mentioned that you get your code one nested level less, there is no noticeable performance difference between the 2.

The presence of the continue statement in the language syntax is not even necessary. Most probably, it remained for backward compatibility reasons. By contrast, the Scala language doesn't offer a continue statement in the standard library, simply because there is nothing that continue offers that you can't do with a Boolean value that changes when some condition is not met anymore.

Here's how javap disassembles your examples. I added a println statement instead of ~~~:

Using continue:

74: iload         5
76: iload         4
78: if_icmpge     118
81: aload_3
82: iload         5
84: iaload
85: istore        6
87: aload_1
88: iload         6
90: invokestatic  #10         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
93: invokeinterface #22,  2   // InterfaceMethod java/util/Map.containsKey:(Ljava/lang/Object;)Z
98: ifeq          104
101: goto          112
104: getstatic     #26         // Field java/lang/System.out:Ljava/io/PrintStream;
107: iload         6
109: invokevirtual #32         // Method java/io/PrintStream.println:(I)V
112: iinc          5, 1
115: goto          74

Without using continue:

  74: iload         5
  76: iload         4
  78: if_icmpge     115
  81: aload_3
  82: iload         5
  84: iaload
  85: istore        6
  87: aload_1
  88: iload         6
  90: invokestatic  #10         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  93: invokeinterface #22,  2   // InterfaceMethod java/util/Map.containsKey:(Ljava/lang/Object;)Z
  98: ifne          109
 101: getstatic     #26         // Field java/lang/System.out:Ljava/io/PrintStream;
 104: iload         6
 106: invokevirtual #32         // Method java/io/PrintStream.println:(I)V
 109: iinc          5, 1
 112: goto          74

They look almost identical. Notice in the first version, using continue translates into a goto statement.

Related