Hi all,
Wondering if this is a compiler problem or if someone can enlighten me further…
In the profiler I noticed that iterating through a list using foreach(…) was allocating 24 bytes of memory. As far as I know, List's enumerator is a struct, ie: stored on the stack… So I tried an empty loop to be sure, but it seems the foreach loop is still allocating 24 bytes… Got me thinking that maybe Unity’s compiler is playing tricks on me.
I suspect it could be compiling any foreach loop to a generic while loop that includes a finally block with an IDisposable cast which is where the 24 bytes is coming from… hmm. I’m not aware of a code reflector for OSX so I can’t confirm myself…
Enumerators are a class… whether it’s a standard Enumerator or an Enumerator… they implement IEnumerator. Any time your do a foreach loop you are going to allocate for the enumerator. It’s even worse if you have nested foreach loops. Let’s say you do this:
foreach(var obj in myObjects)
{
foreach(innerObj in obj)
{
//do something
}
}
In the example above we assume we have something like a IEnumerable<IEnumerable>. Now, in the above, you will allocate for the outer enumerator and for each object in “myObjects” you’ll also allocated another Enumerator.
For some types this can’t be avoided. But if you’re using a List, you should use your index instead:
for(var i = 0; i < myList.Count; i++)
{
var obj = myList[i];
}
This indexed based example does not create an Enumerator and therefore will not allocate. This has nothing to do with Unity’s compiler, it has to do with the simple fact that you’re creating an instance of a reference type.
Yes, I’m very much aware of how both memory allocation and enumerators function (I say that informatively not indignantly )
I’m sorry that’s incorrect. Most enumerators are reference type variables. In the case of a List it’s enumerator is actually a mutable struct. One of those oddities that works in our favor most times in terms of memory->performance. Which is why I posted this question - Because it should not be generating any garbage!
Now I’m wondering why it does… the only explanation I can think of is the compiler is being lazy as explained above and treating all loops the same, ie: foreach on a List like a IList, ie: boxing the returned enumerator to an IEnumerator
You’re right, I had to do a sanity check. List.Enumerator is a struct. I also checked the Mono source repository to make sure it was consistent and it is. List should not generate garbage in most cases. However, Collection will. If you foreach over a Dictionary for instance, you’ll generate garbage.
Now, I said “most” cases because it can. Take this example… this should not generate garbage in the foreach:
var myList = new List<int>();
myList.Add(1);
myList.Add(2);
foreach(var myVal in myList)
{
Debug.Log(myVal);
}
However, if you’ve cast back to an IEnumerable, which you may have unknowingly… then it will. Take this as an example:
public void Foo()
{
var myList = new List<int>();
myList.Add(1);
myList.Add(2);
Bar(myList);
}
public void Bar(IEnumerable<int> myList)
{
foreach(var myVal in myList)
{
Debug.Log(myVal);
}
}
That example will actually generate garbage even though your original type was a List. I can run it through CLR Profiler to verify if you’d like. If we saw your actual code that you’re using, I bet it’s the case.
No funny code business going on here
If anyone want’s to profile this in Unity for me to confirm, the results would be interesting…
using UnityEngine;
using System.Collections.Generic;
public class Test : MonoBehaviour
{
List<int> _list;
void Start ()
{
_list = new List<int>();
for(int i=0; i<10; i++) {
_list.Add(1);
}
}
void Update ()
{
foreach(var element in _list) {
}
}
}
It will generate garbage if it is indeed the compiler doing what I think it’s doing…
I would like you to do it, because I don’t see why it wouldn’t call the proper implementation. You can implement your own IEnumerable and IEnumerator that have their own behaviour. I found out IEnumerator can implement IDisposable because you can make a IEnumerator that enumerate over marshallable data, such as a stream, which could be rather handy to automatically dispose of it after reading it!
List<>'s enumerator is List<>.Enumerator (nested class). It would break the behaviour if GetEnumerator() would return something else than an instance of this type.
In background, the foreach is suppose to be;
enumerator = stack.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
//statement
}
}
finally
{
IDisposable disposable = (enumerator as IDisposable);
if (disposable != null)
disposable.Dispose();
}
Looking at this, if the Enumerator if a struct, it shouldn’t allocate anything on the heap. Casting something into an interface (IDisposable) shouldn’t change the memory behaviour of a struct. At best, it should create a pointer on the stack.
From what I understand, casting it to IEnumerable boxes and unboxes the enumerator which is the problem. However, I didn’t see this behavior at all. The CLR Profiler won’t attach to the game executable for some reason… However I just threw in some quick GC code to check the allocated memory and ran against two tests. One with a List and the other with a List passed into a method that accepts IEnumerable as the parameter. In both cases there was zero allocation for the foreach iteration. You can test yourself:
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;
public class ForEachTest : MonoBehaviour {
public List<int> _list;
private long mem;
private long mem2;
// Use this for initialization
void Start () {
_list = new List<int>{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
}
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.A))
{
Test1(_list);
}
if (Input.GetKeyDown(KeyCode.B))
{
Test2(_list);
}
}
private void Test1(List<int> myList)
{
mem = GC.GetTotalMemory(false);
foreach (var num in myList)
{
continue;
}
mem2 = GC.GetTotalMemory(false);
Debug.Log("A: " + mem + " / B: " + mem2);
}
private void Test2(IEnumerable<int> myList)
{
mem = GC.GetTotalMemory(false);
foreach (var num in myList)
{
continue;
}
mem2 = GC.GetTotalMemory(false);
Debug.Log("A: " + mem + " / B: " + mem2);
}
}
Sums it up really
After running some more tests this morning, I think it’s safe to say that the compiler is boxing the List.Enumerator to an IEnumerator.
Now just need a way of confirming…
Hmm interesting… I can confirm your results Dustin, no allocation. However, if I move the Test1 loop into the if statement, the memory allocation is back. Have to go out now, but will test more later
Which “if statement” are you referring to? You mean the:
if(Input.GetKeyDown(KeyCode.A))
{
//////
}
?? That’s the first test I ran before splitting it out and testing within methods so I could do the IEnumerable parameter. I still had zero allocation. You must have something else that’s allocating.
Yep.
For the sake of testing, can we keep the test in its most basic form like I posted above —
using UnityEngine;
using System.Collections.Generic;
public class Test : MonoBehaviour
{
List<int> _list;
void Start ()
{
_list = new List<int>();
for(int i=0; i<10; i++) {
_list.Add(1);
}
}
void Update ()
{
foreach(var element in _list) {
}
}
}
All the other implementations work as expected and are self-explanatory. I’m only dealing with iterating over a vanilla List.
Just out of curiosity, which OS are you using? Windows/OSX? Visual Studio/Mono, etc.
The reason I ask is that any code compiled with VS should be fine…
I’m on OSX and I suspect it is a problem with the Mono compiler.
If anyone else is curious enough to test can they include their environment specs as well?
So maybe I’m not going crazy after all! Still, it would be nice to confirm it. Might have to load up a VM and see if I can get a reflector on it and see what’s actually happening…