3

As an addendum to this question Java loop efficiency ("for" vs. "foreach")

I have a simple question. Does the enhanced for-loop have a larger memory footprint, or will they both compile to the same construction, making their memory footprint identical and thus making for(Object o : collection) { ... } always better for read-only operations?

My reason for asking is that in a simulator I'm working on, every frame (60/sec) I am running operations against possibly thousands of items in arrays such as faces to draw, to raytrace against, etc. The loop

for(Object o : collection) {
   o.doSomething(); 
}

Looks like it may make a memory-copy of the collection (and I seem to recall that it does from my previous reading) which is okay in most circumstances, but not if you do 30000 raytraces times 60 a second.

On the other hand, it is clear that the loop

count = collection.size();
for(i = 0; i < count; i++) {
   collection[i].doSomething();
}

does everything by reference and has a relatively small footprint even though it is harder to read (though honestly, not much)

Any ideas, folks?

(Note: the impact of the difficulty to read for loops is only evident when you're several layers in - for single layer fors the gain is extremely minor. I say this from experience... collection[i].property.subcollection[j].row[k].col[l].prop.subtable[m] gets to be a mindbreaker, especially if some of those need to be cast: ((Type3)((Type2)((Type1)collection[i]).property.subcollection[j].row[k]).col[l].prop).subtable[m] for example.)

Community
  • 1
  • 1
  • 2
    Well I can at least tell you that the second one will actually allow you to change the list without breaking the iterator aka ConcurrentModificationException hell. That does come in useful. – thatidiotguy Sep 12 '12 at 15:33
  • 1
    Your second loop won't compile. Is `collection` an array or a `Collection`? – Lukas Eder Sep 12 '12 at 15:33
  • Yep. concurrentmodification exception is annoying, and sometimes leads to the creation of *another* collection just to store the objects you're about to remove. –  Sep 12 '12 at 15:33
  • 1
    the enhance for loop will be turned into an Iterator based loop. so, it may have a slightly higher overhead. however, the JIT may get rid of that overhead. – jtahlborn Sep 12 '12 at 15:33
  • 1
    @RiverC - unless you just use `Iterator.remove()`... – jtahlborn Sep 12 '12 at 15:34
  • @thatidiotguy seems not to be "that" idiot... – UmNyobe Sep 12 '12 at 15:34
  • @Lukas it's pseudocode and is only presented as an example to make sure you know I mean for(obj o : c) and not for(;;) –  Sep 12 '12 at 15:34
  • I thought it was compiled to down to the same code. Meaning, foreach only provide more readability. – Pran Sep 12 '12 at 15:36
  • 1
    @RiverC: I'm just saying that a foreach loop for arrays compiles to different byte-code than one for `Iterable`. I guess that's relevant for your question... – Lukas Eder Sep 12 '12 at 15:44
  • ok, sure. That's a good point. What I'm aiming for is to reduce stress on the GC engine in any way possible - visual performance blips can happen and so forth. –  Sep 12 '12 at 15:56

2 Answers2

2

If you have an ArrayList using hte following pattern can be a micro-optimisation

for(int i=0, len=list.size(); i < len; i++)

The memory you save is about 16-24 bytes as the for-each loop will always create an Iterator.

(60/sec) I am running operations against possibly thousands of items in arrays such as faces to draw, to raytrace against,

For 60,000 per second you are unlikely to notice the difference esp as you are doing relatively significant work with each item.

Looks like it may make a memory-copy of the collection

It doesn't which is why you can get a ConcurrentModicationException if you change the list while looping over it. One workaround for this is to take a copy.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • the 60000+ operations are not on the same list (a simple list of 60000) but on numerous lists that are multi-dimensional in some cases. This is just to clarify. –  Sep 12 '12 at 15:43
  • Ok, so if you are iterating over 60000 lists per second then using random access might be better than using for-each as it saves an Iterator each time. – Peter Lawrey Sep 12 '12 at 15:46
  • Do the iterators get destroyed each time, or does the collection, if not an array, reuse its iterator? –  Sep 12 '12 at 15:57
  • Also in some cases I use arrays since their memory overhead is smaller - i.e. data points in a map might have collection data attached to each point, so I use arrays there to avoid having thousands of collections. –  Sep 12 '12 at 15:59
  • for-each over an array doesn't create an Iterator and is just as efficient as a result. – Peter Lawrey Sep 12 '12 at 16:00
  • 1
    @RiverC Every time you ask for an iterator you get a new one. This is essential because it retains the state of your own personal current iteration, i.e. where you are presently up to. – user207421 Sep 13 '12 at 03:41
  • So if you're doing a lot of small collections you can end up creating and disposing a lot of iterators. Good to know. –  Sep 18 '12 at 16:55
1

It's hard to say how the JIT will optimize your code (which implementation of Java, which OS, etc.). Main point here is that the for-each syntax compiles to the same byte code as using iterator syntax see here and here. The primary reason for using iterator syntax is for removing items during iteration. Note that the remove method on the Iterator interface is optional.

I cooked some simple examples to see the byte code myself:

For each iteration of array

// compiled .class file is 585 bytes
public class ForEachLoop {
  public static void main(String[] args) {
    for(String s : args){
      System.out.println(s);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=5, Args_size=1
   0:   aload_0
   1:   dup
   2:   astore  4
   4:   arraylength
   5:   istore_3
   6:   iconst_0
   7:   istore_2
   8:   goto    26
   11:  aload   4
   13:  iload_2
   14:  aaload
   15:  astore_1
   16:  getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   19:  aload_1
   20:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   23:  iinc    2, 1
   26:  iload_2
   27:  iload_3
   28:  if_icmplt   11
   31:  return
  LineNumberTable: 
   line 4: 0
   line 5: 16
   line 4: 23
   line 7: 31

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      32      0    args       [Ljava/lang/String;
   16      7      1    s       Ljava/lang/String;

Array indexing loop

// compiled .class file is 554 bytes
public class ArrayLoop {
  public static void main(String[] args) {
    for (int i = 0; i < args.length; i++) {
      System.out.println(args[i]);
    }
  }
}

// byte code for main method
public static void main(java.lang.String[]);
  Code:
   Stack=3, Locals=2, Args_size=1
   0:   iconst_0
   1:   istore_1
   2:   goto    17
   5:   getstatic   #16; //Field java/lang/System.out:Ljava/io/PrintStream;
   8:   aload_0
   9:   iload_1
   10:  aaload
   11:  invokevirtual   #22; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  iinc    1, 1
   17:  iload_1
   18:  aload_0
   19:  arraylength
   20:  if_icmplt   5
   23:  return
  LineNumberTable: 
   line 4: 0
   line 5: 5
   line 4: 14
   line 7: 23

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      24      0    args       [Ljava/lang/String;
   2      21      1    i       I

We can see that the array iteration byte code is more compact (by 31 bytes to be exact).

Community
  • 1
  • 1
cyber-monk
  • 5,470
  • 6
  • 33
  • 42
  • Isnt for each looping over arrays a special case? There is no iterator involved afaik, or is it? – jontro Sep 12 '12 at 15:47
  • What about the case of arrays (which I sometimes use because they have a small memory footprint)? –  Sep 12 '12 at 15:58
  • On your update. Very cool to know! So you take less program memory for the normal for loop. –  Sep 18 '12 at 16:56