Events, Tweens, Timers. Timers have worse performance than tweens?! Events are best... WHAT?

3 favourites
From the Asset Store
Best car suspension with spring effect and very cool terrain generation.
  • If I have an object that needs to do something in 10 seconds...

    I can set a tween "" from x->y for 10 seconds.

    I can set a timer "" for 10 seconds.

    I can use a instance variable, set it to 10, and subtract dt every tick.

    (If I didn't need bulletTime / scaled dt, I could also just store the current timestamp and check that against the current time.

    For both tween and timer I can use the trigger onTimer "" and OnFinished "".

    For the event based method, I can simply check if variable <= 0;

    I wasn't too surprised that tweens perform worse than events. They have alot of extra fluff not needed for a simple linear 1:1 dt:value change.

    But timers!!!! They are the worst even though the tween is mimicking a timer and can do so much more.

    In a project with 20k objects tested with event based timers, tween based timers, and timer based timers... the event based timers were the only ones that could run at 60fps. The downside is that they had the overhead of needing to check if the timer variable was <= 0 every tick. The suprise is that the "trigger" for ontimer has exactly the same overhead, but the tween doesn't.

    With the timer test, if no timers are running, the "OnTimer" trigger alone causes 30% cpu usage. When activating all timers, the project runs at around 27 fps.

    With the Tween test, if no tweens are being run, the project sits at o-1% cpu usage. PERFECT... until you run the tweens and then you get around 27 fps. This indicates that tweens ARE slower than timers, but since timers have the overhead for the trigger, it evens out.

    Events use around 30% cpu for the conditional check to see if a timer is done, and an additional 15% to simply add dt to the timer. Total 45%.

    So basically, at scale, depending on needs and how often the timer is operational vs inactive, I would use either custom events or tweens.

    HOW can events outperform the timer behavior!!!! The timer should be king in performance for all timer related needs... but it isn't.

  • jup I brought this up before and wrote a suggestion to turn timers into true triggers.

    It's not only bad for perf it also leads to the weird case where -> on timer triggers with multiple instances picked. (this lead to c3 user actually adding for each to every trigger as they stopped trusting triggers altogether, leading to even worse performance xD)

  • Downside of tweens is that "is tween running" is super slow. Value tween checks seem to be fast (tested with sprite.twee.value(tag) != 0) but when it also has to check the property tweens it becomes slow af.

  • jup I brought this up before and wrote a suggestion to turn timers into true triggers.

    It's not only bad for perf it also leads to the weird case where -> on timer triggers with multiple instances picked. (this lead to c3 user actually adding for each to every trigger as they stopped trusting triggers altogether, leading to even worse performance xD)

    Was there a discussion at that time? How long ago was it? Is there a way to know if a trigger is fake? In c2 you could always check the behaviors code... because, you know... it was useful like that :p

  • I'm looking over the behaviors code, I can understand why one might want a fake trigger for order of events stuff. (Right, true triggers can fire at any time? Or is the order in the event sheet still protected?)

    But it still seems like there should be a true trigger for performance reasons.

  • jup I brought this up before and wrote a suggestion to turn timers into true triggers.

    It's not only bad for perf it also leads to the weird case where -> on timer triggers with multiple instances picked. (this lead to c3 user actually adding for each to every trigger as they stopped trusting triggers altogether, leading to even worse performance xD)

    This held me back for some time--timer complete might generate multiple instances picked. Because it happened rarely it was hard to track down as a problem. Now I just for each on almost everything to be on the safe side since I am never really sure when I just have one instance.

    yours

    winkr7

  • Thanks, great post!

    I use a billion timers throughout my events, they're so efficiently convenient!

    Sounds like it's better to let them go and to just use the old fashioned DT event method, I need all the performance I can get.

  • I have a feeling that the main reason why timers are fake triggers is simply because of backwards compatibility. The timer behavior is ancient and changing it will probably cause a ton of older (but also current) projects to break. I wonder what the best course of action would be... deprecating it and make a new one with real triggers?

    Arguably, in any case the timer behavior should be the best performing option.

  • I'd really caution against kneejerk reaction to performance numbers like this. It can really send you on a wild goose chase and end up in giving people bad advice.

    To illustrate how subtle performance is as a topic, you can take the "quad issue performance" benchmark, add the Solid behavior to the main sprite being created, and the benchmark drops about 3%. "OMG! Is the Solid behavior slow? Scirra should delete the extra code it's running to ensure maximum performance!"

    The thing is it is literally not running a single line of extra code. How could it be slower then? It uses a tiny bit more memory per instance. That means the hardware memory caching systems are a tiny bit less effective. So it takes a bit longer to run identical code. This goes further: you'll be able to measure bigger differences the more features you add, solely due to the increase in memory usage and caching effects. This is just a fact of computing. Advising people to avoid the Solid behavior due to this overhead is firstly pretty much useless advice, and secondly irrelevant to 99.9% of projects anyway, which don't have such demanding performance requirements.

    I can indeed measure a fairly big performance difference between the Timer behavior and using an instance variable. This is for three reasons:

    1. The Timer behavior uses the behavior ticking system, which for compatibility reasons must preserve a specific ordering. This means internally it iterates a special data structure which has a small overhead compared to iterating a simpler data structure like an array.
    2. The Timer behavior does not merely subtract from a variable. If you do that, you will find the timer drifts off from a real clock over time, because floating point precision errors accumulate. The solution to this is to use the Kaham summation algorithm, which improves precision enough to be suitable for practical purposes. Since it tracks both the current and total time, it in fact does kahan summation twice per tick.
    3. The Timer behavior has more features, like being able to dynamically add timers with a string expression for its tag.

    You can start by subtracting from an instance variable for your timer. But you may well find over time you run in to these same problems. If you want accuracy over time, then you need kahan summation; if you want a predictable timer finish order, you'll need to sequence things somehow; if you want dynamic timers, you'll need a dictionary or some other such feature to track state by string.

    All of those things add a small amount of performance overhead. When you do that with tens of thousands of sprites, the difference can add up and show up as significant results. However JavaScript is still super fast, the Timer behavior really is already about as minimal and optimal as it can be given its features, and I'm sure for 99% of projects it's perfectly fine. I don't think it's right to say "the timer behavior is slow": if you did all the same things it does in event sheets, you'd probably find your events are actually slower still.

    Sure, if you want a buggy and limited version of a timer solely to maximize performance at all costs, then you can do the instance variable thing. I suppose a few particularly intensive games might need that. But the Timer behavior still performs well given its feature set. There is no obvious way to further optimize it without deleting features. No, a "true trigger" won't help, because internally it still has to do the same checks, and that just means it fires the timer a different way. In fact the trigger infrastructure has a high performance overhead (as it's designed for things like input events, not performance critical stuff) and so making it a real trigger may well degrade performance further. In that case you can view the fact it's not a real trigger as an optimization.

    All software is about tradeoffs, and there is usually a fundamental choice: do you want it to have lots of features and work correctly, or maximize performance? If you improve one it will come at the cost of the other. We try to get a good balance with Construct, but if you have a specific situation, maybe you want a different tradeoff. That's OK but it doesn't mean the built-in stuff sucks.

  • Ashley so if you are checking for 10 different timers in the event sheet, enemy on timer X, enemy on timer y etc. You have the overhead 10 times. With true triggers the behaviour processing might be slightly higher but you wouldn't have the overhead againfor each on timer check.

  • It's not 10x the overhead of the entire behavior again, it's just the timer complete check, which is a very small and simple check. If you use triggers it still does that check but internally, and then has to fire a trigger which could well be 100x slower or more than the simple check, which would make rapidly firing timers much slower.

    As I say, it's all tradeoffs, and there is usually no perfect solution. Changing A for B usually means you tip the scales another way and some other case becomes worse. A general-purpose engine usually tries to avoid making any one case particularly bad.

  • The big issue with True Triggers is that AFAIK, it would trigger all the "on timer" events of a behavior each time any timer is finished (can end up being a big overhead if you use the same behavior for many timers and triggers), only to check which trigger event was the one corresponding to the timer that just ended.

    For example if you have "attack1cooldown", "attack2cooldown", "attack3cooldown" and so on.

    When any timer is finished it would have the performance overhead for every single "On timer" Trigger you have for this behavior even if only one is in fact corresponding to the timer tag you want.

    This is a big limitation of c3, there is no efficient way for plugins and behaviors to do that kind of effective true triggers condition based on "tags" or "function names". It scales very poorly in term of performance as the number of different "tags" you're using grows. We have built-in functions when we want to do that kind of stuff in Eventsheet but sadly there is nothing that allows to do that in the Addon SDK right now. This is probably why fake triggers are a thing.

    Ashley do you think there is a chance that one day the Addon SDK would supports new "function-like True Triggers" for Timers or any plugin/behavior that wants to implement some kind of "Self-Functions" ? For example if the issue is that tags or names are strings that can be based on expression, making it impossible for C3 engine to map the right triggers for the right calls efficiently based on a tag, couldn't c3 users create our own tags enums/drop-down list for those new "Functions-like True Triggers based on Tags" ? (Instead of letting the user put any expression in there, this way C3 engine know for sure what to trigger in a performant way like built in Function)

    I suppose the drop-down/enums choice would only need to be done on the Trigger event but there could be both a drop-down and a "by name" variant for their related Call action event.

    Not only those potential Addon SDK "function Trigger" would be useful for Timers but it would also allow to implement a behavior-based "Better Signals"/performant Self Functions supporting multiple params similar to this request : construct23.ideas.aha.io/ideas/C23-I-165

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I did not expect timers to be this complex :V

  • I did not expect timers to be this complex :V

    and you are an old timer too.

  • I often use Timer instead of Function because it provides a delayed execution solution.

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)