What's new
  • Visit Rebornbuddy
  • Visit Panda Profiles
  • Visit LLamamMagic
  • Visit Resources
  • Visit Downloads
  • Visit Portal

Pro tip: avoid calling WoWSpell.Cooldown, cache and check less often.

regecksqt

New Member
Joined
Dec 19, 2010
Messages
49
Reaction score
4
I've been profiling my arena bots for various characters with the purpose of making them faster, and have noticed that an insane amount of execution time has been spent inside WoWSpell.get_Cooldown.

Turns out, this property is nothing but a wrapper for a LUA call to get the cooldown duration, which explains why its execution time is so high. This came rather as a shock to me, as checking cooldowns is one of the main things your CustomClasses and whatever else must do:

W2ftT.png


30% of total execution inside one property, ouch. Of course, this is higher than a usual use case as the interrupt routine runs every tick.

By caching the expected cooldown expiration time, and then polling WoWSpell.Cooldown to update that time at a greater interval, you can increase the speed (reduce the execution time) of your bot significantly without sacrificing much in terms of accuracy:

QkOXo.png


That is with a 2 second interval on cooldown checking, plus a reset when the spell is used or the expected cd time elapses. This very generic advice of course goes for many things, but for such an important part of a bot such as cooldown checking I believe is worth mentioning on its own. Another strategy is to defer the cooldown checking and trying less expensive checks first.

Compare Stealthers() % in first and second pics. lol. ;) Using this optimization method I've managed to get the entire bot to run in under ~100ms at a maximum tick rate. Those are some damn fast reactions.
 
Last edited:
Please teach me how to analyze CCs like that T_T

I've been trying to figure out how to use visual studio's performance analysis, but i can't figure out how to do it >_>

edit: maybe i got it, created new blank analysis, then added Honorbuddy process to that :P

edit edit: i got data, but can't make heads or tails of it :S
 
Last edited:
I launch HonorBuddy via dotTrace. I can't say one way or another what kind of exposure this creates to detection, I just hope I don't get banned ;). Also anything that is written using BehaviorTrees is going to be very irritating to profile.
 
I launch HonorBuddy via dotTrace. I can't say one way or another what kind of exposure this creates to detection, I just hope I don't get banned ;). Also anything that is written using BehaviorTrees is going to be very irritating to profile.

dotTrace seems to be working, very, very well :D

It's fancy...
 
Using this optimization method I've managed to get the entire bot to run in under ~100ms at a maximum tick rate. Those are some damn fast reactions.
Can you please explain more detail about optimization

My healer code take 2-4sec to complete the rotation (2 sec for 10 man and 4-6 sec for 40 man raid) :|
 
Can you please explain more detail about optimization

My healer code take 2-4sec to complete the rotation (2 sec for 10 man and 4-6 sec for 40 man raid) :|

Holyshit, I'm mad that I somehow got an extra 10ms tacked onto my rotation; 2,000-4,000ms is insane.

Mind giving me a link to your code?
 
This is my Priest PvP code,

I tried to do a lot of thing but then is take 6sec to complete the rotation.

Can you please show me how to make it run in 1 sec :)
 

Attachments

The advice that I can give you strictly relating to this thread is that SpellManager.CanCast suffers from the same slowness as WoWSpell.Cooldown. I avoid calling it ever, though I see you have it in your CastSpell wrapper.

That said I have no idea where the bottlenecks could be in your CustomClass without profiling it myself, which is something you could do if you wanted. It could be one other thing or just the sheer amount of code you have.
 
Last edited:
This program is very nice! Now what the hell do I do to fix the slow zones....
 
How do you avoid calling WoWSpell.Cooldown?

Do you run several stopwatch to check cooldown?

Please share it.
 
This is pretty awesome! I have a few sluggish CCs I would like to fix.

But how do you not use CanCast and Cooldown? Wouldn't it make sense to check to see if a spell is available before you use it?

I guess the other option is try to use it and see if it fails?
 
Oh fck me.. I kept optimizing and optimizing and finally managed 500ms for the entire rotation...

But this will help so much, tHankyou!
 
Oh fck me.. I kept optimizing and optimizing and finally managed 500ms for the entire rotation...

But this will help so much, tHankyou!

I still dont see how this was ever useful as he never clarified how you optimized code
 
How do you avoid calling WoWSpell.Cooldown?
Do you run several stopwatch to check cooldown?
Please share it.

In Buddywing's WingIt, we had to implement our own CooldownTracker for other reasons, but it should be easy to use and adapt if you like:

Code:
        /// <summary CooldownTracker>
        /// <para>The CooldownTracker keeps up with when abilities are last used, and whether enough time has elapsed for them to
        /// be used again. The tracker makes its decisions based upon the maximum of <c>CooldownTime</c> or <c>GlobalCooldownTime</c>.
        /// For spammable abilities, the latter is the limiting factor.</para>
        /// <para>To use this class:<list type="bullet">
        /// <item><description><para> * Call <c>CooldownTracker.IsOnCooldown</c> any time you need to find out if ability is on cooldown</para></description></item>
        /// <item><description><para> * Call <c>CooldownTracker.AbilityUsed</c> any time you use an ability.
        /// This keeps the tracker up to date.</para></description></item>
        /// </list></para>
        /// </summary>
        // NB: The BW API has similar functionality built in for use with TorCaracters.  You find this functionality
        // in TorPlayer.IsAbilityReady() and AbilityManager.CanCast().  However, there is no corresponding functionality
        // for abilities that are used 'on the ground' instead of TorCharacters.  Thus, the need for this class.
        public static class CooldownTracker
        {
            /// <summary>
            /// Call this method to determine if an ability is on Cooldown.  Both the ability's particular cooldown
            /// and the global cooldown are taking into account.  For spammable abilities, the latter is the limiting
            /// factor.
            /// </summary>
            /// <param name="ability"></param>
            /// <returns></returns>
            public static bool IsOnCooldown(TorAbility ability)
            {
                DateTime lastUsed;

                if (cooldowns.TryGetValue(ability, out lastUsed))
                {
                    TimeSpan maxCooldown = TimeSpan.FromSeconds(Math.Max(ability.CooldownTime, ability.GlobalCooldownTime));
                    return ((DateTime.Now - lastUsed) <= maxCooldown);
                }

                return (false); // Ability hasn't been used yet
            }

            /// <summary>
            /// Call this method any time you use an ability to keep the CooldownTracker up-to-date.
            /// </summary>
            /// <param name="ability"></param>
            public static void AbilityUsed(TorAbility ability)
            {
                cooldowns[ability] = DateTime.Now + cheatFactorDelay;
            }

            private static Dictionary<TorAbility, DateTime> cooldowns = new Dictionary<TorAbility, DateTime>();
            private static TimeSpan cheatFactorDelay = TimeSpan.FromMilliseconds(100);
        }

cheers,
chinajade
 
oops.... old post
 
Last edited:
I still dont see how this was ever useful as he never clarified how you optimized code

Well reading the thread pointed me to "DotTrace" as I was already utilizing "Resharper".
 
I still dont see how this was ever useful as he never clarified how you optimized code
wat?

I (thought) I made this clear in the OP:
By caching the expected cooldown expiration time

i.e ...
Code:
            cs = WoWSpell.FromId(2139);
            csCooldownEnd = 0;
Code:
            if (DateTime.Now.Ticks < csCooldownEnd)
            {
                return;
            }
            if (cs.Cooldown)
            {
                csCooldownEnd = cs.CooldownTimeLeft.Ticks + DateTime.Now.Ticks;
            }
            else
            { // Continue to evaluate interrupt logic

For the 24 seconds that Counterspell is guaranteed to be on cooldown you are wasting MULTIPLE SECONDS of execution time by pointlessly invoking the WowSpell.Cooldown property because it is so expensively implemented. If you can guarantee that you won't manually put the spell on cooldown you can further optimize by not even checking .Cooldown when it's known to be off cooldown.
 
Last edited:
Since getting the heads up in another thread on LUA calls I've started to cache loads of stuff, eventually even writing a cache class that caches everything rather then
handling caching on the spot. This improves code readability and code re-use as most of the spell/buff code in the CCs are "wrappers in a way" themselves. Tbh. the best
way to handle this is to have the caching done in HB itself tho.
 
There's allot of HB's api that run off a frame, even ones that had me surprised e.g. WoWObject.Location. This is why I prefer to wrap all my behaviors with with Framelock. All Framelock does is increment/decrement an internal value within the using block and anytime HB needs a frame it doesn't release the frame until this value is set to 0 which happens after leaving the outer most 'using' block (when Dispose on the Framelock is called) so it has no real overhead.

This will help tremendously with reducing execution time when your behavior makes 2+ api calls that require a frame because they all run off the same frame rather then each running off their own frame.

Take a look at Raid Bot if you need examples. Not directed at OP but anyone looking for additional advice on speeding up their CC.
 
Last edited:
I've taken a slightly different approach that has worked really well for me. I keep a List of cast history with spellname, expiry time(time it was cast + the cooldown), and wowunit. Then I only check for spellcooldown when the expiry time has elapsed. It does require some pruning but it has worked really well.

Anyone know of a free tool that has similar features as the one described by the OP. It looks really nice but was pricey.
 
Back
Top