Do Foreach Loops Still Create Garbage?
Over a year ago I wrote an article title Do Foreach Loops Create Garbage using Unity 5.2 and tested foreach
with a variety of collections: List
, Dictionary
, arrays, etc. Since then Unity has a new C# compiler and version 5.6 has been released. Is it safe to use foreach
now? Read on to find out!
Today’s article is simply a re-test of the same exact test script from the previous article. Instead of Unity 5.2.2f1, I’ve tested today with 5.6.0f3. All of the code and steps to run it still apply.
As a reminder, the test script uses a foreach
loop on a variety of collection types (e.g. List
) to see if any garbage is created. It then runs another foreach
loop to see if any garbage is created the second time, which may not be the case if there’s some internal caching going on.
Now on to the huge table of results. I’ve added a “5.2 Total” column on the right side to show the previous results.
Type | GetEnumerator() | MoveNext() | Current | Dispose() | 5.6 Total | 5.2 Total | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
1st | 2nd | 1st | 2nd | 1st | 2nd | 1st | 2nd | 1st | 2nd | 1st | 2nd | |
Array |
n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | 0 | 0 | 0 | 0 |
ArrayList |
40 | 40 | 0 | 0 | 0 | 0 | n/a | n/a | 40 | 40 | 40 | 40 |
BitArray |
40 | 40 | 0 | 0 | 17 | 17 | n/a | n/a | 57 | 57 | 57 | 57 |
Dictionary<T> |
32 | 0 | 32 | 0 | 0 | 0 | 0 | 0 | 64 | 0 | 72 | 40 |
HashSet<T> |
32 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 0 | 40 | 40 |
Hashtable |
56 | 56 | 0 | 0 | 32 | 32 | n/a | n/a | 88 | 88 | 88 | 88 |
IEnumerable |
40 | 40 | 0 | 0 | 20 | 20 | 0 | 0 | 92 | 60 | 92 | 60 |
IEnumerable<T> |
40 | 40 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 40 | 40 | 40 |
LinkedList<T> |
32 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 0 | 48 | 48 |
List<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 40 |
Queue |
32 | 32 | 0 | 0 | 0 | 0 | n/a | n/a | 32 | 32 | 32 | 32 |
Queue<T> |
32 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 0 | 32 | 32 |
SortedDictionary<TKey, TValue> |
32 | 0 | 256 | 192 | 0 | 0 | 0 | 0 | 288 | 192 | 304 | 240 |
SortedList |
178 | 64 | 0 | 0 | 32 | 32 | n/a | n/a | 210 | 96 | 210 | 96 |
Stack |
32 | 32 | 0 | 0 | 0 | 0 | n/a | n/a | 32 | 32 | 32 | 32 |
Stack<T> |
32 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 0 | 32 | 32 |
Dictionary<T>
, HashSet<T>
, LinkedList<T>
, Queue<T>
, and Stack<T>
now create the same, 8, or 16 bytes less garbage on the first loop and zero garbage on any loops thereafter. That’s great news because there’s no way to use a plain for
loop instead of foreach
with any of these collections.
SortedDictionary<TKey, TValue>
now creates 16 bytes less garbage on the first loop and 64 bytes on subsequent loops. It’s still the biggest garbage creator of any collection, so try to avoid it if you’re keeping clear of the GC.
Finally the big news: List<T>
no longer creates any garbage! This is by far the most common collection, so this is a huge win for us Unity programmers! Of course there is a caveat to that. In order for the foreach
loop to not create any garbage, you need to use a List<T>
variable. You can’t use an interface that it implements like IEnumerable<T>
or 40 bytes of garbage will be created every loop. Normally you can avoid that though, and you should if you want to take advantage of a garbage-free foreach
.
While all the other collection types haven’t changed the amount of garbage they create, at least none of them use more garbage than they did back in Unity 5.2. So we have nothing but good news today. If you’re using foreach
because you can’t use for
then you’re not going to be creating as much garbage as before with common collections like Dictionary<T>
. With List<T>
you won’t be creating any garbage, so there’s no need to use for
with them anymore.
#1 by Imi on April 3rd, 2017 ·
We experienced differences when using foreach over lists of value-based data types (e.g. int, Vector3..) and when iterating over reference-based data types (e.g. GameObject)
Did you measure this? What data type did you use?
And by any chance: Did you measure with IL2CPP as well? Also the AOT – mode instead of IL2CPP? And what about Consoles like PS4 and XBox one (which seem to have different compiler and AOT-quirks)?
A complete and comprehensive guide would be awesome, maybe Unity could shed a light here.. ;)
#2 by Imi on April 3rd, 2017 ·
Never mind. Discussion on twitter resulted in me checking every problem case we had again and indeed – we don’t get allocations on second call even on List & Co..
foreach forever!
#3 by Jack Mott on April 6th, 2017 ·
In regular .NET 4.6.1 foreach is still slower over Lists than for loops, regardless of garbage, is that not true in Unity?
#4 by jackson on April 6th, 2017 ·
I did a test of
for
versusforeach
last May using Unity 5.3 and found thatforeach
was quite a lot slower thanfor
. That may have changed with the new compiler and other advancements in Unity 5.4-5.6, so I’ll make a note to re-test.#5 by Benoit Alain on April 21st, 2017 ·
Add two of your TestScript component, and you’ll see that the ‘FirstTest’ has the same cost as the ‘SecondTest’ on the second component. The difference is most likely just static initialization, i.e. you don’t pay the cost per-object. For all practical consideration, most generic containers now have 0 memory leak on foreach.
#6 by jackson on April 21st, 2017 ·
You’re right that I didn’t explicitly state in the article that the subsequent
foreach
loops represented by the 2nd column apply to all instances of that collection type, not just the single instance. It is static initialization, not instance initialization.You’re also close to right about generic containers not creating any garbage due to
foreach
. It’s still true thatSortedDictionary
creates (a lot) of garbage and any container typed asIEnumerable
will create garbage.SortedDictionary
isn’t very common, so that’s probably not a concern, but it’s still important to use the exact type—not an implemented interface—of the container in order to avoid theIEnumerable
cost.#7 by Uvi Vagabond on July 27th, 2018 ·
Hi, could you repeat tests for Unity 2018?
#8 by jackson on July 27th, 2018 ·
I haven’t repeated the tests for 2018 yet, but that might be useful now that .NET 4.x and .NET Core 2.0 are available. In the meantime, check out this article for an in-depth look at
foreach
in IL2CPP using 2017.3.#9 by TimV on March 12th, 2020 ·
I tried repeating this test for 2019.3.2. I’m not sure about one thing though. Unity reports allocations from Mono.JIT in the profiler. Did you include these numbers? It seems to me that these allocations should be subtracted if you’re building for a IL2CPP target.
#10 by jackson on March 12th, 2020 ·
No, I only included the functions named in the column headers.
#11 by marimo on November 3rd, 2022 ·
I am interested in how performance is with
versus
. In many contexts I define data with the latter but return that with the former so as to disallow accidental modification of data.
Perhaps it is overkill (though it communicates intent well for my purposes) but I am curious if there is an unintended consequence to this approach with regards to Unity.