9

My friend works with Unity3D on C#. And he told me specifically:

Every iteration of every “foreach” loop generated 24 Bytes of garbage memory.

And I also see this information here

But is this true?

The paragraph most relevant to my question:

Replace the “foreach” loops with simple “for” loops. For some reason, every iteration of every “foreach” loop generated 24 Bytes of garbage memory. A simple loop iterating 10 times left 240 Bytes of memory ready to be collected which was just unacceptable

neonblitzer
  • 124
  • 1
  • 2
  • 10
jimpanzer
  • 3,470
  • 4
  • 44
  • 84
  • 6
    That's *far* too general a statement to be taken seriously. Different collections behave different ways with `foreach` - the author doesn't specify what he's talking about. (In particular, with arrays the compiler will effectively generate something equivalent to the `for` loop anyway.) – Jon Skeet Sep 10 '13 at 12:02
  • Yes, I thinking the same. End does the 'foreach' slower the 'for'? – jimpanzer Sep 10 '13 at 12:05
  • 1
    It entirely depends on the specific scenario. Any generalization on that front is likely to be incorrect in some situation. – Jon Skeet Sep 10 '13 at 12:06
  • 1
    That whole article is to be read as a "This worked for us in our specific situation". It may or may not be the same for you. – Bart Sep 10 '13 at 12:13
  • 2
    Actually, I have to take that article with a pinch of salt, because it claims "Calling the tag property on an object allocates and copies additional memory" - where `tag` here is a `string` - sorry, but that *simply isn't true* - all that happens is that the reference to the existing string object is copied on the stack - there is no extra object allocation here. – Marc Gravell Sep 10 '13 at 12:26

3 Answers3

14

foreach is an interesting beast; a lot of people mistakenly think it is tied to IEnumerable[<T>] / IEnumerator[<T>], but that simply isn't the case. As such, it is meaningless to say:

Every iteration of every “foreach” loop generated 24 Bytes of garbage memory.

In particular, it depends on exactly what you are looping over. For example, a lot of types have custom iterators - List<T>, for example - has a struct-based iterator. The following allocates zero objects:

// assume someList is a List<SomeType>
foreach(var item in someList) {
   // do something with item
}

However, this one does object allocations:

IList<SomeType> data = someList;
foreach(var item in data) {
   // do something with item
}

Which looks identical, but isn't - by changing the foreach to iterate over IList<SomeType> (which doesn't have a custom GetEnumerator() method, but which implements IEnumerable<SomeType>) we are forcing it to use the IEnumerable[<T>] / IEnumerator<T> API, which much by necessity involve an object.

Edit caveat: note I am assuming here that unity can preserve the value-type semantics of the custom iterator; if unity is forced to elevate structs to objects, then frankly all bets are off.

As it happens, I'd be happy to say "sure, use for and an indexer in that case" if every allocation matters, but fundamentally: a blanket statement on foreach is unhelpful and misleading.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    I would also add that the "Garbage Memory" is not memory that will not be gargage collected. Rather it is memory that is no longer used and will be garbage collected. Games development often has a different focus that typical applications in terms of performance and overhead. – Gary Walker Sep 10 '13 at 12:29
  • 1
    Unfortunately this seems to be incorrect in Unity (possible with Mono in general?) I've added another comment with example code that demonstrates the garbage being created. Every time foreach is called, 24 bytes are allocated. – Chris Blackwell Mar 25 '14 at 17:37
  • 1
    It used to happen in .NET as well. It's not the `GetEnumerator()` call, but the `Dispose()`. [See this.](http://stackoverflow.com/a/26110503/4093018) – hangar Dec 17 '14 at 10:35
  • You say "It's meaningless to say...", and it would have been, unless that's exactly what happens. Using C# and Unity3D 4.x (which uses Mono), *every* iteration of *every* `foreach` loop that I've observed creates 24 bytes of garbage to be collected. – yzt Oct 18 '15 at 13:39
  • @yzt and again; without details of what you are looping over, and what things happen inside the loop (closures, etc), I assert: it *is* meaningless. If you are talking about a **specific scenario**, then sure: you can start to talk about meaning. Context is everything here. – Marc Gravell Oct 19 '15 at 10:01
10

Your friend is correct, the following code will generate 24k of garbage every frame in Unity as of version 4.3.4:

using UnityEngine;
using System.Collections.Generic;

public class ForeachTest : MonoBehaviour 
{
    private string _testString = "this is a test";
    private List<string> _testList;
    private const int _numIterations = 10000;

    void Start()
    {
        _testList = new List<string>();
        for(int i = 0; i < _numIterations; ++i)
        {
            _testList.Add(_testString);
        }
    }

    void Update()
    {
        ForeachIter();
    }

    private void ForeachIter()
    {
        string s;

        for(int i = 0; i < 1000; ++i)
        {
            foreach(string str in _testList)
            {
                s = str;
            }
        }
    }
}

This shows the allocation in the Unity profiler:

Unity Profiler showing 24k of allocation per update

According to the following link it's a bug in the version of mono being used by Unity that forces the struct enumerator to get boxed, which seems like a reasonable explanation though I haven't verified through code inspection: Blog post discussing the boxing bug

Chris Blackwell
  • 9,189
  • 1
  • 25
  • 27
  • 2
    I repeated this test in Unity 5.2.0.f3 and I found that it generates ~39.1 KB each frame (which is 40 bytes per iteration). – Selmar Oct 09 '15 at 09:46
  • 1
    This is fixed in recent Unity versions (5.5+): https://q.unity3d.com/questions/1465/when-does-using-foreach-in-c-cause-an-allocation.html – Søren Løvborg May 16 '18 at 12:06
6

There is some good advice on here about foreach, but I must chime in about the Mark's comment above relating to tags (unfortunately, I don't have enough rep to comment below his comment), where he stated,

Actually, I have to take that article with a pinch of salt, because it claims "Calling the tag property on an object allocates and copies additional memory" - where tag here is a string - sorry, but that simply isn't true - all that happens is that the reference to the existing string object is copied on the stack - there is no extra object allocation here. –

Actually, calling the tag property does create garbage. My guess is that internally the tag's are stored as integers (or some other simple type), and when the tag property is called, Unity converts the int to a string using an internal table.

It defies reason, since the tag property could just as easily return the string from that internal table rather than creating a new string, but for whatever reason, this is how it works.

You can test this yourself using the following simple component:

public class TagGarbageTest : MonoBehaviour
{
    public int iterations = 1000;
    string s;
    void Start()
    {
        for (int i = 0; i < iterations; i++)
            s = gameObject.tag;
    }
}

If gameObject.tag was not producing garbage, then increasing/decreasing the number of iterations would have no effect on the GC Allocation (in the Unity Profiler). In fact, we see a linear increase in GC Allocation as the number of iterations increases.

It is also worth noting that the name property behaves in exactly the same manner (again, the reason why eludes me).

Kyle G
  • 141
  • 3
  • 7