1. The 5.5 Beta is now available. Click here for more details!
  2. Become a closed-alpha partner on the new Facebook games platform. Read more about it here.
  3. Enter the Samsung TIZEN App Challenge for a chance to win prizes. Read more about it here.

Coroutines vs Update

Discussion in 'Scripting' started by Staples, Nov 20, 2010.

  1. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Hi guys, I haven't really used coroutines much, just wondering about performance.

    Let's say you have a unit with a 'poison' debuff that lasts for 20 seconds, and damages them every second.

    Are you better off using Update(), applying the damage when the time is appropriate (say nextDamage = Time.time + 1 second), or are you better off somehow doing this in a coroutine, perhaps in a while loop?

    As a second question relating to this, Is one or the other better for a multiplayer game? I don't think it would matter either way in this regard, but just thought I would ask.


    Thanks.
     
  2. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Messages:
    26,595
    For something like this which basically applies 1 event 20 times and then 1 a single time you better use coroutines as you can have a simple for loop with yield for 1 second and then just do the debuff and exit of it.

    update would take significantly more performance
     
  3. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Thanks, at what point do you think you are better off using Update()? Even the poison was to be applied every 0.1 seconds would coroutines still be better?

    I guess at the end of the day Update() is better used for checking input and physics/movement?
     
  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Messages:
    26,595
    Update is then better if you need to check it each frame or if you need more complex branching / state handling where you can't rely on pausing.

    But as long as pausing is the primary thing you do, coroutines are always better.
    never forget, an update thats present will be called through a send message alike mechanism which isn't exactly lightweight, while a coroutine is just there and sleeping :)

    Especially on more limited devices like mobiles the difference can become impacting if you follow it on all objects
     
  5. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Great, thanks for the tips.

    I noticed they used coroutines in the lerp example even to update the enemy units movement, I wouldn't normally have done this, but I guess there is no input being checked.
     
  6. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Just a few questions extending on this.

    1) If you happen to have an update function running anyway, is it better to use that instead of creating separate coroutines.

    2) Let's say you are updating the players health, mana and stamina, regenerating them at separate intervals. Is it still more efficient to have 3 separate coroutines using infinite loops to update these values each tick, as opposed to a single Update()? I guess the Update() is still going to run much more often?
     
  7. ivkoni

    ivkoni

    Joined:
    Jan 26, 2009
    Messages:
    978
    1) no
    2) it's hard to tell. If you really want to know you will have to test it in your case. I don't think there will be much of a difference. Coroutines though will result in much better looking code.
     
  8. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Thanks. I guess another option is to have a single constant coroutine loop that runs at a specified interval, every time it runs you could check if it's time to regenerate the players health etc.

    I suppose 1 coroutine should still be better than 3 coroutines, depending on any extra complexity that it takes to manage the 3 tasks.

    Anyhow I'll just create separate coroutines for now, it's good that I found out about them earlier, otherwise I would have ended up using Update() for everything!
     
  9. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    Actually it's best to use InvokeRepeating where possible. I did a test on 5000 objects, where each one runs this:

    Code (csharp):
    1. private var i = 0;
    2.  
    3. function Start () {
    4.     InvokeRepeating("Test", .01, 1.0);
    5. }
    6.  
    7. function Test () {
    8.     i++;
    9. }
    That normally takes .03 milliseconds per frame, with spikes of 2.38 ms every second (when all 5000 functions run).

    Using this code instead:

    Code (csharp):
    1. function Start () {
    2.     var i = 0;
    3.     while (true) {
    4.         i++;
    5.        
    6.         yield WaitForSeconds(1.0);
    7.     }
    8. }
    also takes .03 ms per frame, but the spikes are 4.03 ms every second. Using Update:

    Code (csharp):
    1. private var timer = 0.0;
    2. private var i = 0;
    3.  
    4. function Update () {
    5.     if (Time.time >= timer) {
    6.         timer = Time.time + 1.0;
    7.         i++;
    8.     }
    9. }
    takes an average of .93 ms per frame (no noticeable spikes every second, since the overhead overwhelms whatever CPU time the actual code uses, even multiplied by 5000). At about 100fps, that's a total of 93 ms per second, whereas the coroutine is a total of 7.03 ms per second and InvokeRepeating is 5.38 ms per second.

    --Eric
     
    nxtboyIII and lermy3d like this.
  10. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Thanks for that, seems a bit more elegant for things that are infinitely repeating as well. I suppose coroutines are still useful because of how you can use them.
     
  11. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Just a question relating to the use of InvokeRepeating, if you had a function that was repeating and wanted to change the rate at which was repeating, can you just call InvokeRepeating again on the same function - I assume this will cancel the existing invokation and start a new one?
     
  12. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    Yes, coroutines are very useful for scheduling a series of events:

    Code (csharp):
    1. function Start () {
    2.     yield ShowTitleGraphic();
    3.     yield FadeTitleGraphic();
    4.     yield FadeInMainMenu();
    5.     // etc.
    6. }
    And other things where InvokeRepeating is too limited. (For one thing, you can't pass any parameters to functions called with Invoke.)

    You'd call CancelInvoke before doing InvokeRepeating again, otherwise you'd have it running twice, at two different rates.

    --Eric
     
    Last edited: Nov 21, 2010
  13. Staples

    Staples

    Joined:
    Jan 14, 2009
    Messages:
    224
    Thanks, yeah just realized that.

    I guess the performance difference is minimal and coroutines would be somewhat simpler for regenerating health if the rate can change. But usinng invoke just means that you need to re-invoke it when your rate changes, so not exactly a big deal if it means extra performance.

    I'm hardly up to the performance optimize stage yet, but it's nice to know these things so you can do it right to begin with, so thanks for running the numbers on them Eric.
     
  14. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    Yeah, basically if you can do the same thing just as easily with either coroutines or InvokeRepeating, then go with the latter. But both of them are a lot better than Update if you don't need something to run every frame so there's no point going through contortions to use InvokeRepeating if coroutines are better.

    --Eric
     
  15. tbryant

    tbryant

    Joined:
    Aug 12, 2009
    Messages:
    17
    I'd like to have a routine run at a high rate, for example repeating every 1 ms, but it seems that InvokeRepeating ("FastUpdate", 0.0f, 0.001f);
    won't remain constant if the frame-rate is getting low. I take that to mean that the mean that it depends on the Unity Update()... First of all, is that true? Second, could I get around that with coroutines?
    not sure if this is the best thread, but it seems like the right people. :)
     
  16. Hodor

    Hodor

    Joined:
    Feb 1, 2013
    Messages:
    1
    I just started studying Unity Script and the use of Coroutine instead of FSM is something that I'm having some trouble to understand.

    The C# compiler converts the IEnumerator into a state machine. Using a Coroutine in Unity is the same as using a state machine, but just easier to read to some programmers. You will still need to verify the conditions on every frame, even if you would like to do something just once.

    I understand that callbacks might be usefull here, but can you please explain why running a code in a Coroutine is better for performance than having a FSM in the update function?
     
    Last edited: Feb 2, 2013
  17. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Messages:
    1,410
    There is a more elegant way of doing this - the asynchronous way (without coroutines or constant Update checks).

    Check out this post on using a Timer.

    Note: this approach introduces no performance gain over the Update way of doing things: its purpose is a better written code.

    Example code:

    Code (csharp):
    1. public class TimerTest : MonoBehaviour {
    2.  
    3.     private Timer _timer;
    4.    
    5.     void Start()
    6.     {
    7.         Debug.Log("Start");
    8.         _timer = new Timer(1); // tick each second
    9.         _timer.Tick += TickHandler; // subscribe
    10.         _timer.Start();
    11.     }
    12.  
    13.     void TickHandler(Event e)
    14.     {
    15.         Debug.Log("Tick!");
    16.     }
    17.  
    18.     void OnDestroy()
    19.     {
    20.         Debug.Log("OnDestroy");
    21.         _timer.Tick -= TickHandler; // unsubscribe
    22.         _timer.Stop(); // stop the timer
    23.         _timer.Dispose(); // dispose?
    24.     }
    25. }
     
    Last edited: Feb 2, 2013
  18. leegod

    leegod

    Joined:
    May 5, 2010
    Messages:
    1,480
    Tested in visual studio and unity newest version, compiling says

    _timer.Tick method does not exist.
     
  19. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
    You can implement the same logic using either Update or Coroutine. The latter comes with more mechanism to make your code cleaner (if not abused).

    If term of performance, they should be nearly equivalent, the only difference would be that extra data structures are required in the background to support coroutines, thought that should be insignificant. Note that coroutines are very likely to be called on every Update, even if you use yield instructions such as WaitForSeconds: nothing is done magically, there must be a check in the background evaluating whether the yield instruction is done or not (see how CustomYieldInstruction works).

    Personally, I try to avoid coroutines as much as possible and prefer Update for simple task. I found coroutines not that intuitive and hard to debug, particularly when they are nested, which makes the stacktrace helpless when using break point debugging, leaving me with very few clues about the overall context of execution. For any more complex task, I would organize my code around an FSM or a Behaviour Tree.
     
  20. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    They're not, though; Update is called every frame and has some relatively significant overhead for each call. A "yield WaitForSeconds(1)" is quite a bit cheaper vs. running code in Update 60 times (assuming 60fps). While the coroutine still needs to be evaluated every frame, the Update overhead is not involved. It's better to measure actual performance rather than just guessing based on assumptions. See posts above (from years ago, but still valid...I re-tested it now with 20K objects, and the total time per frame with WaitForSeconds(1) is 0.34ms, vs. 12ms for Update. The coroutines do cause a spike to 30ms as well as GC allocation once per second when the code runs, so that's something to be aware of).

    --Eric
     
  21. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
    I call this insignificant. In a real development, you are not going to worry about that overhead. Long before the number of active GameObjects is going to reach 20K, you'll be more worried about optimizing other stuffs. Choosing between updates or coroutines won't make a clear cut in a real project, in terms of performance.
     
    Last edited: Jul 29, 2016
  22. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    It's significant enough to be aware of. A couple objects, whatever, a couple hundred (feasible), it starts to be noticeable. No point wasting that many CPU cycles if you don't have to.

    --Eric
     
  23. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Messages:
    17,768
    While this is a bit off track, I'm fairly against co-routines. It basically meant the build I showed Sony had to be rewritten from scratch. It had bugs that I couldn't trace down, it began to bog down my large project's development times.

    I liked the idea of co-routines: I was able to chain together a lot of interesting code, but ultimately with a manager, there is no performance benefit to them. It's more of a convenience... but yeah, off track :)

    My pattern these days is to disable a component (which stops update) rather than fire off co-routines, or use a manager to loop through a lot of elements when they need to be refreshed. Because you can choose when to run heavy code anyway with a simple timer.

    For me, co-routines was never about performance to begin with, but about easily managing a code that had to advance every so often. Wish there was an equivalent easy way to do it.
     
    Ryiah and ericbegue like this.
  24. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
    Noticeable, really? According to your own data, using Update for 100 objects, that would be (12ms/20K)*100 = 0.06 ms. Given that a frame duration is ~17ms (at 60 fps), you would need about 300 active objects to start having a drop of 1 single frame. A frame drop from 60 fps to 59 fps is not noticeable.

    Of course you should not waste CPU time. But more importantly, you should not waste programmers time, you better focus only were it is necessary to optimize and not to do early micro-optimizations just to safe some CPU cycles.

    Coroutine are fine when they are kept simple and used for what they are designed for, that is for simple sequences. But, imho, (ab)using them for more complex logic is likely to introduce bugs, makes your code less readable and soaks up programmers time into debugging.

    Sorry to go a bit off track with you... :) I've experienced similar situations using coroutines: it's just a pain to debug.

    Wish no more, have a look at Panda BT.
     
  25. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    It could be; with vsync on you'd be getting 30fps instead. Aside from that, visual perception of framerate isn't the only thing that matters...watching your battery drain faster is noticeable. (I mean, I'm using a desktop right now, but mobile/battery is an important consideration in general.) Obviously I'm not advocating writing bad code to save a few CPU cycles, but a simple coroutine that replaces more complex Update code is easier to maintain and more efficient, so win/win. Sure, coroutines can be abused. Anything can be abused. If you've gotten yourself into a situation where coroutines are causing bugs and other problems, yeah, don't do that.

    --Eric
     
  26. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
    @Eric5h5
    You're right about vsync + double buffering. Though with triple buffering you won't cap at 30 fps.

    Unless your more complex Update is doing nothing, I doubt removing that overhead of 0.6 micro second per call would be the major optimization.

    I'm not advocating writing unjustified cpu expensive code neither. I value readable and maintainable code, even if that means trading some cpu cycles (in the limit of the cpu budget).

    Abusing coroutines for complex code, of course I won't do that. For this particular situation, I took over existing code of an ongoing project. It was not a matter of choice.
     
  27. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Messages:
    17,768
    Unity doesn't and won't support triple buffering.
     
  28. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
  29. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Messages:
    17,768
    That's driver buffering ahead frames. Kinda useless unless you're using windows. It's not triple buffering, it's called Render Ahead, and will store up to 8 frames ahead.

     
    ericbegue likes this.
  30. testure

    testure

    Joined:
    Jul 3, 2011
    Messages:
    78
    Out of curiosity, what was unique about what you were doing for Sony that didn't show up in normal (standalone/editor) builds? Is there something specific about the hardware that prevents coroutine timing from working properly? I'm working on a game right now that I'd really like to bring to PSN, but- like you I really like the elegance of coroutines for organizing the flow of things.

    As a side note- if a core feature in Unity isn't working correctly on one of their supported platforms, isn't that technically a bug that they need to fix? I mean, reality not withstanding.. you still have to get your game out of course, but IMO something like that should be addressed directly by UT core-tech if they want to claim support for sony's consoles.
     
  31. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    He means his code had bugs. :) Not Unity.

    --Eric
     
  32. testure

    testure

    Joined:
    Jul 3, 2011
    Messages:
    78
    oh haha.. well then. GIT GUD :p
     
  33. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Messages:
    17,768
    I was just using them badly. The thing with co-routines is you shouldn't just use them. They have to exactly fit a task really well to be used. Sadly people (me included) abuse them.
     
    LeftyRighty and BoredMormon like this.
  34. BoredMormon

    BoredMormon

    Joined:
    Dec 5, 2013
    Messages:
    10,385
    There are plenty of core features that make your life a headache if you abuse them. That's true of every single programming feature out there. Coroutines in particular can get hard to debug if you have a large project or multiple coders.

    A fork does a really good job for eating food. It also can kill you if you use it to get toast out of the toaster. We don't consider forks to be a problem because they can kill people if used improperly. But its also good advice to only use a fork for eating.
     
  35. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Messages:
    30,146
    So...you're saying coroutines are...for eating? But they...kill people if you toast them? (Am I doing this analogy thing right?)

    --Eric
     
  36. testure

    testure

    Joined:
    Jul 3, 2011
    Messages:
    78
    Right, but in the context of my comment- I was under the impression that something specific about sony's hardware was causing a coroutine to fail, which ended up not being the case.
     
  37. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Messages:
    3,114
    Nah, coroutines generate garbage so they're really unhealthy to eat with.

    Eating with invokerepeating is often preferable.
     
    BoredMormon likes this.
  38. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Messages:
    17,768
    No it was just a story. See, I needed to get a demo out quickly, so the co-routines were added in as they quickly provided an answer to doing lots of things. This turned out to be abusing them. Thus, people should consider them a late-resort not a first resort.

    What annoys me is Unity's learning materials and the forum in general push them as a first resort for many things, and most of these things are bad fits.
     
    BoredMormon likes this.
  39. TTTTTa

    TTTTTa

    Joined:
    Mar 3, 2015
    Messages:
    1,001
    I only use coroutines which require you to wait a certain amount of time before doing it. Like if a player closes a door, wait 1 second then play the door slam audio:
    Code (csharp):
    1.  
    2. private IEnumerator WaitThen_PlayAudio(float waitTime, AudioSource audioSource)
    3. {
    4. yield return new WaitForSeconds(waitTime);
    5. audioSource.Play();
    6. }
    7.  
    Simple. Now using an Update function, you need to maintain a list of all audios you are waiting to play, the time it started, then determine the amount of time which has elapsed, Play the audio, then remove the item from the list. A lot of work when you can just kick off coroutines using one line of code.
     
  40. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Messages:
    1,037
    Coroutines can get abused very quickly. Since the Unity API is not thread safe, multi-threading is quiet restricted. So when it comes to write long running and asynchronous code, there are not so many options than hacking around coroutines.