Garbage-free alternative to coroutines?

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.

Why are you instantiating the coroutines?

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?

look up caching your ienumerator.

you can create a single instance and re-use it. Dont have any links, but with teh above you should be able to search for it

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ā€¦

Even more weirdness:

Set the profiler to Deep Profile and the coroutines no longer report any garbage generationā€¦

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?

Wellā€¦ For that Iā€™ve no idea at all :slight_smile:

I believe you can cache the solved results of a coroutine, but not the running of it, if itā€™s calculating new data each time itā€™s run.

Try checking gc by attaching profiler to it running on device instead, Iā€™ve seen various gc just vanish under those circumstances. As for co-routines and garbage you will want this thread: C# Coroutine WaitForSeconds Garbage Collection tip - Unity Engine - Unity Discussions

1 Like

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.

1 Like

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!!!

That was meant to say fixedā€¦ dohā€¦ [EDITED IMAGE]

Yeah itā€™d be nice for unity to roll out their own sort of gc-free approach indeed.

Iā€™m not sure if itā€™s garbage free, but there was also this

I hadnā€™t had a chance to try it out myself, but I had seen it suggested in some blog before and it is rated 5 stars.

A good alternative to coroutine is Behaviour Tree, which is an appropriate tool to describe logic that unfold in time (spanning several frames).

Have a look at Panda BT. Itā€™s a script-based Behaviour Tree framework. The core engine is GC allocation-free after initialization.

http://www.pandabehaviour.com/

Wasnā€™t this supposedly fixed in issue #784481? Or is this a different issue altogether?