8
\$\begingroup\$

During a Unity talk about performance, the guy told us to avoid for each loop to increase performance and use the normal for loop instead. What's the difference between for and for each in implementation point of view? What makes for each inefficient?

\$\endgroup\$
  • 1
    \$\begingroup\$ Apart from counting how many elements there are in the array or whatever it is you use, there shouldn't be much difference. With a quick google search I've found this: stackoverflow.com/questions/3430194/… (This will apply to Unity too, even though the language is different, it's still the same base logic that applies) \$\endgroup\$ – John Hamilton Oct 25 '17 at 11:36
  • 2
    \$\begingroup\$ I'd add: In your first iteration do not fine tune performance but a priority should be a clean architecture and code style. Only optimize (performance) if there's need for it later... \$\endgroup\$ – Marco Oct 25 '17 at 11:41
  • 2
    \$\begingroup\$ Do you have a link to that talk? \$\endgroup\$ – Vaillancourt Oct 25 '17 at 12:01
  • 2
    \$\begingroup\$ According to this Unity page, for each loops iterating over anything other than an array generates garbage (Unity versions prior to 5.5) \$\endgroup\$ – Hellium Oct 25 '17 at 13:04
  • 2
    \$\begingroup\$ I see a vote to close this as off-topic because it's general programming, but since it's asked in the context of Unity in particular and this is a behaviour that has changed between versions of Unity, I think it's worth keeping open here for reference of Unity game developers. \$\endgroup\$ – DMGregory Oct 25 '17 at 16:22
13
\$\begingroup\$

A foreach loop ostensibly creates an IEnumerator object out of the collection you pass it, and then walks over that. So a loop like this:

foreach(var entry in collection)
{
    entry.doThing();
}

translates to something a bit like this:
(eliding some complexity around how the enumerator is disposed of later)

var enumerator = collection.GetEnumerator();
while(enumerator.MoveNext()) {
   var entry = enumerator.Current;
   entry.doThing();
}

If this gets compiled naively/directly, then that enumerator object gets allocated on the heap (which takes a little time), even if its underlying type is a struct - something called boxing. After the loop is done this allocation becomes garbage to clean up later, and your game can stutter for a moment if enough garbage piles up for the collector to run a full sweep. Lastly, the MoveNext method and Current property could involve more function call steps than just grabbing an item directly with collection[i], if the compiler doesn't inline that action.

The Unity docs Hellium links above indicates that this naive form is exactly what happens in Unity prior to version 5.5, if iterating over anything other than a native Array.

In version 5.6+ (including 2017 versions), this unnecessary boxing is gone, and you shouldn't experience unnecessary allocation if you're using any concrete type (eg. int[] or List<GameObject> or Queue<T>).

You will still get an allocation if the method in question only knows that it's working with some kind of IList or other interface - in those cases it doesn't know what specific enumerator it's working with so it has to box it into an IEnumerator object first.

So, there's a good chance this is old advice that's not so important in modern Unity development. Unity devs like us can be a bit superstitious about things that used to be slow/hairy in old versions, so take any advice of that form with a grain of salt. ;) The engine evolves faster than our beliefs about it.

In practice, I've never had a game exhibit noticeable slowness or garbage collection hiccups from using foreach loops, but your mileage may vary. Profile your game on your chosen hardware to see if it's even worth worrying about. If you need to, it's pretty trivial to replace foreach loops with explicit for loops later in development should a problem manifest, so I wouldn't sacrifice legibility and ease of coding early in development.

\$\endgroup\$
  • \$\begingroup\$ Just to check, a List<MyCustomType> and IEnumerator<MyCustomType> getListEnumerator() { } are fine, whereas a method IEnumerator getListEnumerator() { } would not be. \$\endgroup\$ – Draco18s no longer trusts SE Oct 26 '17 at 4:18
0
\$\begingroup\$

If a for each loop used for collection or array of object (i.e. array of all elements other than primitive datatype), GC (Garbage Collector) is called to free space of reference variable at the end of a for each loop.

foreach (Gameobject temp in Collection)
{
    //Code Do Task
}

Whereas for loop is used to iterate over the elements using an index and so primitive datatype will not affect performance compared to a non-primitive datatype.

for (int i = 0; i < Collection.length; i++){
    //Get reference using index i;
    //Code Do Task
}
\$\endgroup\$
  • \$\begingroup\$ Do you have a citation for this? The temp variable here can be allocated on the stack, even if the collection is full of reference types (since it's just a reference to the existing entries already on the heap, not allocating new heap memory to hold an instance of that reference type), so we wouldn't expect garbage to occur here. The enumerator itself can be boxed in some circumstances and end up on the heap, but those cases apply to primitive data types too. \$\endgroup\$ – DMGregory Oct 30 '17 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.