It seems that Unity coroutines will always generate a minimum of 17bytes of garbage, per yield.
Even a coroutine as simple as:
public IEnumerator func ()
{
while (true) {yield return null;}
}
Generates 17bytes of garbage per frame, according to the Profiler.
Is there any C# alternative to built-in coroutines that is analogous in functionality, and runs garbage-free?
17bytes might not seem like a lot, but with a dozen coroutines running simultaneously, on mobile, that results in a lot of garbage that will increase calls to the collector and decrease overall performance.
Unity coroutines, unless Iām understanding it wrong, are C# coroutines, so youāll always have that memory being taken up. Iād suggest that if youāre using too many coroutines, you can probably refactor your code in some other way so that you donāt have to use them.
Sorry I should have been clearer. Iām not asking if there are C# coroutines that are not Unity coroutines (I realize theyāre the same). Iām asking if there are any alternative data structures that have functionality to coroutines, but do not allocate garbage.
I have a lot of heavy algorithms that I spread out over multiple frames with coroutines to increase framerate.
Ah, I see. I think in terms of how youāre using them currently probably the only thing that will do what a coroutine does is a coroutine - but that said, as far as I know (happy to be proven wrong on this) coroutines arenāt a good match for spreading out work over time, but rather for anything which is just a delayed loop (like something that implements ienumerator)ā¦
What about using threads? I have no idea off the top of my mind if Unity is thread safe, but if what youāre doing requires thinking in the background while frames render, you might be able to run it in parallel process?
Do you mean cache the Coroutine somehow? Or cache the object it returns? My coroutines themselves are static functions.
I tried caching the object it returns, but I still get 17bytes of garbage each yield. That goes for both āyield return [myCachedWaitForSecondsObject]ā and āyield return nullāā¦they all produce garbage.
Hmm, I guess threading is an option. Iām already using a separate thread for other heavy computationsā¦Iām afraid that without something like a coroutine to force a function to yield until the next frame, I could end up in situations where the game lags if the 2nd extra thread get processed by the core executing the appās main thread (Iām targeting devices that are only guaranteed to have at least 2 cores).
The nice thing about coroutines is I can force them to stop executing during heavy computations to ensure they never block the main threadā¦
That would make it difficult then, yesā¦ You might have to live with the overhead, unless you can figure out some way to reduce the number of coroutines that are running at once? Maybe delegate that work to fewer dedicated solver objects or something?
Caching the ienumerator wonāt do you any good (especially since it canāt be reused either).
The problem is just rooted in the way unity deals with the yield instruction. It must create a wrapper object for it.
Iām willing to bet that the garbage might be editor specificā¦ and will disapear in a build. Thereās a lot of overhead that comes with the profiler as well. But Iām not for sure, never tested it.
Of course all a coroutine is, is an IEnumerator that has āMoveNextā called once per frame. If one of the yield instructions is returned, it doesnāt call MoveNext until that yield instruction is complete. You could just manually tick your IEnumerators in an Update callā¦ but youād have to deal with those yield instructions manually.
This foundation is what I built my RadicalCoroutine on:
Of course it has a lot of overhead for other things. But it has a āManualTickā method, and I tested, and it does not create any garbage if manually ticked in the Update method:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using com.spacepuppy;
public class ZTestScript : MonoBehaviour {
private List<RadicalCoroutine> _routines = new List<RadicalCoroutine>();
void Start()
{
for(int i = 0; i < 1000; i++)
{
//this.StartCoroutine(this.SomeRoutine());
_routines.Add(new RadicalCoroutine(this.SomeRoutine()));
}
}
IEnumerator SomeRoutine()
{
while(true)
{
yield return null;
}
}
void Update()
{
for(int i = 0; i < _routines.Count; i++)
{
_routines[i].ManualTick(this);
}
}
}
Of course, the way my manualtick works, it comes with extra cost when a yieldinstruction is returned during a ManualTickā¦ because we have to deal with it in some manner, and unity is the only place I had to go to do so. So it spins up a Coroutine for that yield instruction (accept for WaitForSeconds, since I had my own pooled WaitForDuration instruction to replace it with).
You could create a much more slimmed down version of what I have to get what you need.
I have been working on breaking RadicalCoroutine out of the overall framework, but Iām not done yet. Need to write some better documentation for it.
Thank you** lordofduct**! Your code was a perfect pointer in the right direction! I can confirm that using a custom coroutine inspired by your RadicalCoroutine class does not generate any garbage. Fantastic!!!