Search Unity

Garbage-free alternative to coroutines?

Discussion in 'Scripting' started by zombox, Nov 25, 2015.

  1. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    It seems that Unity coroutines will always generate a minimum of 17bytes of garbage, per yield.

    Even a coroutine as simple as:

    Code (csharp):
    1.  
    2. public IEnumerator func ()
    3. {
    4.      while (true) {yield return null;}
    5. }
    6.  
    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.
     
  2. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    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?
     
  3. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    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.
     
  4. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    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?
     
  5. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    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
     
  6. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    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.
     
  7. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    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...
     
  8. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    Even more weirdness:

    Set the profiler to Deep Profile and the coroutines no longer report any garbage generation....
     
  9. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    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 :)
     
  10. apsdsm

    apsdsm

    Joined:
    Sep 26, 2013
    Posts:
    56
    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.
     
  11. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    pea likes this.
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    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:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/RadicalCoroutine.cs

    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:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. using com.spacepuppy;
    7.  
    8. public class ZTestScript : MonoBehaviour {
    9.  
    10.  
    11.     private List<RadicalCoroutine> _routines = new List<RadicalCoroutine>();
    12.  
    13.     void Start()
    14.     {
    15.         for(int i = 0; i < 1000; i++)
    16.         {
    17.             //this.StartCoroutine(this.SomeRoutine());
    18.             _routines.Add(new RadicalCoroutine(this.SomeRoutine()));
    19.         }
    20.     }
    21.  
    22.     IEnumerator SomeRoutine()
    23.     {
    24.         while(true)
    25.         {
    26.             yield return null;
    27.         }
    28.     }
    29.  
    30.     void Update()
    31.     {
    32.         for(int i = 0; i < _routines.Count; i++)
    33.         {
    34.             _routines[i].ManualTick(this);
    35.         }
    36.     }
    37.  
    38. }
    39.  
    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.
     
    apsdsm likes this.
  13. zombox

    zombox

    Joined:
    Aug 10, 2011
    Posts:
    119
    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!!!
     
  14. image28

    image28

    Joined:
    Jul 17, 2013
    Posts:
    457


    That was meant to say fixed... doh... [EDITED IMAGE]
     
    Last edited: Jun 22, 2016
  15. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Yeah it'd be nice for unity to roll out their own sort of gc-free approach indeed.
     
  16. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
  17. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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/
     
  18. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    Wasn't this supposedly fixed in issue #784481? Or is this a different issue altogether?