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:
string currValue;
string oldValue = "x";
public IEnumerator SomeFunction() {
while (true) {
yield new WaitUntil(currValue != oldValue);
//
// Some extensive processing
//
oldValue = currValue;
}
}
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 WaitUntilactually 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)?
public sealed class WaitUntil : CustomYieldInstruction
{
private Func<bool> m_Predicate;
public WaitUntil(Func<bool> predicate)
{
this.m_Predicate = predicate;
}
public override bool keepWaiting
{
get
{
return !this.m_Predicate();
}
}
}
Which basically executes that predicate function each frame.
TL;DR: It equals to this:
// In your coroutine
while (!yourStatement()){
yield return null;
}
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;
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.
private WaitUntil _yourInstruction;
private void Awake(){
_yourInstruction = new WaitUntil(...);
}
// Or
private IEnumerator Coroutine() {
WaitUntil statement = new WaitUntil(....);
while (true) {
yield return statement;
}
}
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.
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?
string currValue;
string oldValue = "x";
public IEnumerator SomeFunction() {
WaitUntil waitForTrue = new WaitUntil(currValue != oldValue);
while (true) {
yield return waitForTrue;
//
// Some extensive processing
//
oldValue = currValue;
}
}
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.
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.
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.