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:

  1. GetEnumerator function
  2. MoveNext function
  3. Current property
  4. Dispose 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:

  1. Open the Profiler editor pane
  2. Select the Deep Profile button
  3. Click the Clear button
  4. Click the Play button in the main editor window
  5. Click the Play button again shortly after the app starts
  6. In the Profiler editor pane, select the CPU Usage section
  7. Click in the timeline on the first frame or anywhere to the left of it
  8. Find TestScript.Start() in the bottom left section and click its triangle
  9. Click the triangles for all the test functions under TestScript.Start()
  10. 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!