Do Foreach Loops Create Garbage?
We know that we should reduce the garbage our code produces to lighten the load on Unity’s garbage collector. The trouble is that many of the ways we’re creating garbage are hidden from us. One such way to inadvertently create a lot of garbage is to use a foreach
loop… at least that’s what we’ve been told. Do foreach
loops really create garbage for all types of arrays, lists, dictionaries, and the rest of the collections? Do they create garbage for every loop or just the first one? Today’s article investigates to put these questions to rest. Are you safe using foreach
loops or should you re-write everything to use for
. Read on to find out!
Let’s start by breaking down what a foreach
loop does behind the scenes. Here’s a simple foreach
loop:
foreach (var cur in list) { }
And here’s what it looks like when you remove the syntax sugar:
var e = list.GetEnumerator(); try { while (e.MoveNext()) { var cur = (ElementType)e.Current; } } finally { var disposable = e as IDisposable; if (disposable != null) { disposable.Dispose(); } }
This means there are three or four calls to functions outside our code in every foreach
loop. Each of these has the opportunity to creates some garbage:
GetEnumerator
functionMoveNext
functionCurrent
propertyDispose
function
With that in mind, let’s design a test to see if foreach
creates any garbage for any of the following types:
System.Array
(all array types)System.Collections.ArrayList
System.Collections.BitArray
System.Collections.Dictionary<TKey, TValue>
System.Collections.Generic.HashSet<T>
System.Collections.Hashtable
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.LinkedList<T>
System.Collections.Generic.List<T>
System.Collections.Queue
System.Collections.Generic.Queue<T>
System.Collections.Generic.SortedDictionary<TKey, TValue>
System.Collections.SortedList
System.Collections.Stack
System.Collections.Generic.Stack<T>
The following test creates an instance of each of these with one element and then iterates over each twice with an empty foreach
loop during Start
. The second iteration should tell us if anything is cached from the first iteration.
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; class TestScript : MonoBehaviour { int[] array = { 123 }; ArrayList arrayList = new ArrayList { 123 }; BitArray bitArray = new BitArray(1); Dictionary<int, int> dictionary = new Dictionary<int, int> { {123,456} }; HashSet<int> hashSet = new HashSet<int> { 123 }; Hashtable hashtable = new Hashtable { {123,456} }; IEnumerable enumerable = new List<int> { 123 }; IEnumerable<int> enumerableT = new List<int> { 123 }; LinkedList<int> linkedList = new LinkedList<int>(); List<int> list = new List<int> { 123 }; Queue queue = new Queue(); Queue<int> queueT = new Queue<int>(); SortedDictionary<int,int> sortedDictionary = new SortedDictionary<int,int> { {123,456} }; SortedList sortedList = new SortedList { {123,456} }; Stack stack = new Stack(); Stack<int> stackT = new Stack<int>(); void Start() { linkedList.AddFirst(123); queue.Enqueue(123); queueT.Enqueue(123); stack.Push(123); stackT.Push(123); FirstTest(); SecondTest(); } void FirstTest() { Test(); } void SecondTest() { Test(); } void Test() { TestArray(); TestArrayList(); TestBitArray(); TestDictionary(); TestEnumerable(); TestEnumerableT(); TestHashSet(); TestHashtable(); TestLinkedList(); TestList(); TestQueue(); TestQueueT(); TestSortedDictionary(); TestSortedList(); TestStack(); TestStackT(); } void TestArray() { foreach (var cur in array) {} } void TestArrayList() { foreach (var cur in arrayList) {} } void TestBitArray() { foreach (var cur in bitArray) {} } void TestDictionary() { foreach (var cur in dictionary) {} } void TestEnumerable() { foreach (var cur in enumerable) {} } void TestEnumerableT() { foreach (var cur in enumerableT) {} } void TestHashSet() { foreach (var cur in hashSet) {} } void TestHashtable() { foreach (var cur in hashtable) {} } void TestLinkedList() { foreach (var cur in linkedList) {} } void TestList() { foreach (var cur in list) {} } void TestQueue() { foreach (var cur in queue) {} } void TestQueueT() { foreach (var cur in queueT) {} } void TestSortedDictionary() { foreach (var cur in sortedDictionary) {} } void TestSortedList() { foreach (var cur in sortedList) {} } void TestStack() { foreach (var cur in stack) {} } void TestStackT() { foreach (var cur in stackT) {} } }
Simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then check for garbage collector allocations like this:
- Open the
Profiler
editor pane - Select the
Deep Profile
button - Click the
Clear
button - Click the
Play
button in the main editor window - Click the
Play
button again shortly after the app starts - In the
Profiler
editor pane, select theCPU Usage
section - Click in the timeline on the first frame or anywhere to the left of it
- Find
TestScript.Start()
in the bottom left section and click its triangle - Click the triangles for all the test functions under
TestScript.Start()
- Look at the values in the
GC Alloc
column for each function call
I followed these steps on Mac OS X 10.11 with Unity 5.2.2f1 and found the following results:
Type | GetEnumerator() | MoveNext() | Current | Dispose() | Total | |||||
---|---|---|---|---|---|---|---|---|---|---|
1st | Second | First | Second | First | Second | First | Second | First | Second | |
Array |
n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | 0 | 0 |
ArrayList |
40 | 40 | 0 | 0 | 0 | 0 | n/a | n/a | 40 | 40 |
BitArray |
40 | 40 | 0 | 0 | 17 | 17 | n/a | n/a | 57 | 57 |
Dictionary<T> |
0 | 0 | 32 | 0 | 0 | 0 | 0 | 0 | 72 | 40 |
HashSet<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 40 |
Hashtable |
56 | 56 | 0 | 0 | 32 | 32 | n/a | n/a | 88 | 88 |
IEnumerable |
40 | 40 | 0 | 0 | 20 | 20 | 0 | 0 | 92 | 60 |
IEnumerable<T> |
40 | 40 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 40 |
LinkedList<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 48 | 48 |
List<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 40 |
Queue |
32 | 32 | 0 | 0 | 0 | 0 | n/a | n/a | 32 | 32 |
Queue<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 32 |
SortedDictionary<TKey, TValue> |
0 | 0 | 256 | 192 | 0 | 0 | 0 | 0 | 304 | 240 |
SortedList |
178 | 64 | 0 | 0 | 32 | 32 | n/a | n/a | 210 | 96 |
Stack |
32 | 32 | 0 | 0 | 0 | 0 | n/a | n/a | 32 | 32 |
Stack<T> |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 32 | 32 |
The compiler generates a normal loop when using a foreach
loop on an array, so none of the functions were called. In that case, no garbage is ever created.
The rest of the collections all create garbage in both the first run and subsequent runs. In the lightest cases—Queue
, Queue<T>
, Stack
, Stack<T>
—this is only 32 bytes, but it ranges up to 304 bytes in the worst case: SortedDictionary<TKey, TValue>
on the first loop.
The culprit for all the garbage creation varies from collection to collection. GetEnumerator()
, MoveNext()
, and Current
all create garbage, but never all three. Dispose()
, thankfully, never creates any garbage.
In some cases—HashSet<T>
, LinkedList<T>
, List<T>
, Queue<T>, Stack<T>
—none of the functions create any garbage but the overall test function does. The cause is a mystery. It may be attributable to flaws in the Unity profiler or code being generated behind the scenes. If you’ve got any idea where the garbage is being created, let me know in the comments.
Some of the collections—Dictionary<TKey, TValue>
, IEnumerable
, SortedDictionary<TKey, TValue>
, SortedList
—create less garbage on the second iteration than on the first. This is likely due to some sort of internal caching, such as reuse of an enumerator or related values. Sometimes GetEnumerator()
creates less garbage, but other times it’s MoveNext()
that’s creating less garbage. In only one case—Dictionary<TKey, TValue>.MoveNext()—do subsequent calls to
MoveNext()
create no garbage at all.
Finally, let's answer the questions set forth in the opening paragraph.
"Do foreach
loops really create garbage for all types of arrays, lists, dictionaries, and the rest of the collections?"
Yes, except for arrays.
"Do they create garbage for every loop or just the first one?"
Yes, for every loop.
If you've had any experiences with foreach
and garbage creation, share your thoughts in the comments!
#1 by focus on November 18th, 2015 ·
Hi, this is a good post, thank you.
And thanks for this nice blog as well, reading it for many years already, I was an AS3 developer in the past.
Just wish to share with you one trick from my colleague to avoid extra garbage while iterating List with foreach:
https://gist.github.com/yuri-tikhomirov/6bb2cc1a04391451ba60
#2 by jackson on November 18th, 2015 ·
Thanks for sharing! That looks like a handy class and actually one I considered writing myself as a follow-up to this article. Perhaps I still will since I have some inspiration to work off now. :)
#3 by focus on November 19th, 2015 ·
Looking forward to your implementation! ^^
#4 by frank28 on July 21st, 2016 ·
All you need to know about this ‘Foreach gc problem’ is included in this article:
https://codingadventures.me/2016/02/15/unity-mono-runtime-the-truth-about-disposable-value-types/
Seems that you should use Reflector to show ‘IL code’, and you could really see where gc happens:
var disposable = e as IDisposable;
#5 by jackson on July 21st, 2016 ·
Great article. Thanks for linking!
#6 by sandeep on September 19th, 2016 ·
How about the foreach on hashset??
I could see there is no GC Alloc – foreach if used on hashset.
Any one tried foreach on hashset please share your review.
Thank you.
Below is the script i used for performance checking.Just add bullet prefab.
Note: We could see that foreach on hash set executes less number of times when compared to list
This is object pooling implementation
#7 by jackson on September 19th, 2016 ·
I pasted your code into a script but get compiler errors for
List
andHashSet
. Where are those defined? I don’t know of aHashSet
, onlyHashSet<T>
andHashtable
.