Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 916
jimpanzer Avatar asked Sep 10 '13 11:09

jimpanzer


People also ask

Does foreach generate garbage?

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

Does foreach allocate?

Foreach can cause allocations, but at least in newer versions . NET and Mono, it doesn't if you're dealing with the concrete System. Collections. Generic types or arrays.

What is garbage collection in Unity?

Garbage collection is an operation that occurs as part of how Unity manages memory. The way that our code uses memory determines the frequency and CPU cost of garbage collection, so it's important that we understand how garbage collection works.

What is foreach loop with example?

In this program, foreach loop is used to traverse through a collection. Traversing a collection is similar to traversing through an array. The first element of collection is selected on the first iteration, second element on second iteration and so on till the last element.


3 Answers

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).

like image 174
Kyle G Avatar answered Oct 10 '22 08:10

Kyle G


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.

like image 14
Marc Gravell Avatar answered Oct 10 '22 10:10

Marc Gravell


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

like image 10
Chris Blackwell Avatar answered Oct 10 '22 09:10

Chris Blackwell