Like the title says, I just realised that every yield on a coroutine generates a “small” memory allocation.
While lots of resources around the web on the subject suggest that when returning null or when locally caching the yield instruction that can be avoided, that’s not what I verify.
while (true)
{
yield return null;
}
while (true)
{
yield return new WaitForFixedUpdate();
}
var waitForFixedUpdate = new WaitForFixedUpdate();
while (true)
{
yield return waitForFixedUpdate;
}
Is this to be expected and every resource out there that states that returning null or caching doesn’t generate memory allocation is wrong or is there a bug introduced recently that makes this happen? I’ve tested with Unity 4 and 5 with exactly the same results.
First off, your second one is definitely going to have more memory allocation, you’re creating a new object… that takes up memory.
As for when yielding null. This really doesn’t have that much to do with unity, and more to do with .net/mono. Unity uses iterator functions from .net/mono for its coroutines, and this system is closely related to linq, which is notorious for generating little bits of garbage like this.
They get cleaned up in time, it’s fine and dandy. You shouldn’t have to worry to much about them. Take your example… it’s 17 bytes of memory. That’s about the size of a Quaternion… (just on the heap instead). This really is only going to effect you if you’ve got a massive number of coroutines running at the same time.
Things like this aren’t “bugs”. We’re using a memory managed language, the heap is there FOR A REASON. It’s what allows the framework to be memory managed. This automatic memory management comes at the cost of the garbage collector.
The only bug I’d say that exists is the fact that Unity still uses such an outdated version of Mono. Mono has since optimized their garbage collector. But there’s licensing disputes with Xamarin… so… ugh.
The coroutine only has what you see on the code I posted.
Yeah, me too… That’s why I created this thread. It seems every other resource online about this matter is wrong.
Totally understood what you’re saying. The reason I called this a potential bug is because pretty much every resource online says you can avoid that memory allocation if you cache the enumerator or simply return null. That doesn’t seem to be the case.
While 17B is definitely not much, if you have a bunch of coroutines yielding on every frame it will eventually add up and trigger an “unnecessary” GC.
To update on what @lordofduct wrote, this is probably closely related to the issue where using foreach to iterate over a Collection (not an array) will create some garbage.
In both instances, it is possible to do garbage-less iteration, as long as the generated IEnumerator is a struct. But, the version of Mono that Unity uses boxes that struct-IEnumerator in an object, and this creates garbage. I’m pretty sure that I’ve seen an Unity engineer mentuion that this is on the to-fix list, but as every other Mono-fix (as opposed to fixes on the Unity-side), it’s probably not going to get worked on until the IL2CPP compilation is stable enough. Which is probably in a while.
In the meantime, you’ll have to live with those 17 bytes.
EDIT: I pulled my info from here, which contains a short sum-up of the techinal side and some links.
This is only true in some cases. For instance, foreach on a dictionary produces garbage but not on a generic list. Also, there isn’t a foreach in his hide, just a while and no collection but if you wanted to test you could try:
May I ask why not? Being able to avoid having an Update/FixedUpdate function in a lot of my scripts - face it not everything needs to run everyframe , by using coroutines has saved me some big performance.
Just once though or every frame? If it only generates 49 bytes of garbage when first invoked then its not a big deal. I intentionally left out the i++ by the way as I wanted it to keep running. You could say i < int.MaxValue.
yeah, I would expect it to generate garbage each time you start it (not necessarily while it’s running though). It’s because the return type is IEnumerator. It’s returning an Enumerator. Internally it’s creating one which is what the garbage is about.
Here’s an example that shows that too… let’s say you have a dictionary that has 100 entries. When you do a foreach on the dictionary an enumerator is generated and put on the heap, but just once.
Now let’s say you have two dictionaries both with 100 entries. You do the following:
foreach(var kvp in dictionary1)
{
foreach(var kvp2 in dictionary2)
{
//do something
}
}
This will generate 101 enumerators on the heap… one for the outer dictionary and one for the inner dictionary for every item in the outer. The StartCoroutine is doing something similar in that it’s generating an Enumerator every time it’s called.