Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How heavy is WaitUntil?

Discussion in 'Scripting' started by qcw27710, Dec 2, 2019.

  1. qcw27710

    qcw27710

    Joined:
    Jul 9, 2019
    Posts:
    139
    I have a certain function that if it were IEnumerator that loops itself it would save me a lot of other programming jobs. The structure of that IEnumerator I'm proposing is as follows:

    Code (CSharp):
    1. string currValue;
    2. string oldValue = "x";
    3.  
    4. public IEnumerator SomeFunction() {
    5.     while (true) {
    6.         yield new WaitUntil(currValue != oldValue);
    7.        
    8.         //
    9.         // Some extensive processing
    10.         //
    11.        
    12.         oldValue = currValue;
    13.     }
    14. }
    Final questions are in bold.

    How expensive (performance wise) is this IEnumerator compared to a similar regular void function? For certain reasons I can't use get/set. My only choice is this IEnumerator or manual call, while IEnumerator method cleans up quite a code clutter. On the other hand, I don't want crazy 250% CPU tax increase.

    How does WaitUntil actually work? Does it have a hash sitting around somewhere that changes and on variable's change have it automatically call for a check? Does it tell processor to check on it every now and then (every ~0.1ms)?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    It has this inside:
    Code (CSharp):
    1.  public sealed class WaitUntil : CustomYieldInstruction
    2.   {
    3.     private Func<bool> m_Predicate;
    4.  
    5.     public WaitUntil(Func<bool> predicate)
    6.     {
    7.       this.m_Predicate = predicate;
    8.     }
    9.  
    10.     public override bool keepWaiting
    11.     {
    12.       get
    13.       {
    14.         return !this.m_Predicate();
    15.       }
    16.     }
    17.   }
    Which basically executes that predicate function each frame.
    TL;DR: It equals to this:
    Code (CSharp):
    1. // In your coroutine
    2. while (!yourStatement()){
    3.    yield return null;
    4. }
    Most of the overhead will be from the alloc of new WaitUntil() and the Func (=>) creation alloc which happens once per loop (if its a delegate capture, e.g. by reference like x => _someObj.SomeBoolValue).

    Also whatever you put inside that predicate function. Which will be executed each frame until succeeds.


    My advice - don't use those custom instructions. They're just GC alloc overhead. Its not even faster to write

    yield return new WaitUntil(yourStatement()) than while (!yourStatement()) yield return null;
     
    Last edited: Dec 2, 2019
    Bunny83 and wizard872 like this.
  3. qcw27710

    qcw27710

    Joined:
    Jul 9, 2019
    Posts:
    139
    GC alloc? What's that?

    That's a particular example you mentioned, but in my case it could more mean clean and organized code and data flow.

    What other alternative exists for IEnumerator?
     
  4. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

    Cache the yield instruction or use plain code w/o CustomYieldInstructions.
    Either by allocating memory in the class, by creating a field for the WaitUntil instruction, or allocating it once before the loop.

    Code (CSharp):
    1. private WaitUntil _yourInstruction;
    2.  
    3. private void Awake(){
    4.    _yourInstruction = new WaitUntil(...);
    5. }
    6.  
    7. // Or
    8.  
    9. private IEnumerator Coroutine() {
    10.     WaitUntil statement = new WaitUntil(....);
    11.  
    12.     while (true) {
    13.          yield return statement;
    14.     }
    15. }
    This will prevent extra memory allocations to be created and picked up by the garbage collector later on.

    Note that this will not prevent from stepping each frame into that Func, but it will reduce garbage allocations.
     
    Last edited: Dec 2, 2019
  5. qcw27710

    qcw27710

    Joined:
    Jul 9, 2019
    Posts:
    139
    I'm kind of confused, does it mean that if I store my WaitUntil statement in a variable (i.e. I cache it), it makes it less performance heavy and I can use it in more "safe" manner? So this code is okay?
    Code (CSharp):
    1. string currValue;
    2. string oldValue = "x";
    3.  
    4.  
    5. public IEnumerator SomeFunction() {
    6.     WaitUntil waitForTrue = new WaitUntil(currValue != oldValue);
    7.  
    8.     while (true) {
    9.         yield return waitForTrue;
    10.      
    11.         //
    12.         // Some extensive processing
    13.         //
    14.      
    15.         oldValue = currValue;
    16.     }
    17. }
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,707
    Until you open the profiler window and profile your game on the actual target hardware, any discussion about performance is no better than water cooler talk. It might be interesting, but you don't want to act on it.
     
    EZaca likes this.
  7. qcw27710

    qcw27710

    Joined:
    Jul 9, 2019
    Posts:
    139
    I disagree, I think using proper cooling liquid (for stable solid performance), avoiding common mistakes in application (to ensure thermoconductivity) and montage (as to prevent leaks) do matter. By the same grace I found out that LINQ is actually slower than foreach because allegedly it uses internal looping to keep track on things, I don't need to test it. Generally assumed knowledge is "generally LINQ is slower than foreach" is a valid statement in most cases, which fully suffices me. I wouldn't even know how to profile parts in question. I could run an empty test, but that wouldn't represent the game, and if I already build one solution, it will be really really hard to switch to the other. I was just making sure I'm not reinventing a wheel or putting a lot of effort into something that someone could talk me out of; as it's bad practice/bad code management/potential security risk/bad performance.
     
  8. EZaca

    EZaca

    Joined:
    Dec 9, 2017
    Posts:
    31
    I do live in a tropical country, and I don't have a water cooler. The standard cooler seems to work nicely.
    Also, I am more afraid of the cost to restore and call the IEnumerator than caring about the yield return performance. You will have other issues to resolve far before the yield return new WaitUntil turns into a problem.