• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

JASS Benchmarking Results

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,526
I benchmarked that with FPS results a couple years ago and FoG was faster if more than 1 or 2 units needed to be enumerated.

Just to clarify, are you confirming that a function like this:

JASS:
scope test
    globals
        private group iterator=CreateGroup()
        private group swap=CreateGroup()
        private group temp
    endglobals
    
    private function fgSwap takes nothing returns nothing
        local unit FoG
        loop
            set FoG=FirstOfGroup(iterator)
            exitwhen FoG==null
            //
            call GroupAddUnit(swap,FoG)
            call GroupRemoveUnit(iterator,FoG)
        endloop
        set temp=iterator
        set iterator=swap
        set swap=temp
    endfunction
endscope

is FASTER than ForGroup for groups of size 2 and greater?
 
ForGroup is pretty heavy internally.

Also can someone test set i = i + 1 vs set this = this.next//set this = next[this] (one char lenght variable ofc)

It could be important for custom camera systems when looping over every playing player with an interval of ~0.01.

It would make no difference at all for any system like that.
These kinds of things are only important when you're dealing with something like Nestharus' Pathfinding library which contains functions that have a run-time measured in whole minutes.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Just to clarify, are you confirming that a function like this:

JASS:
scope test
    globals
        private group iterator=CreateGroup()
        private group swap=CreateGroup()
        private group temp
    endglobals
    
    private function fgSwap takes nothing returns nothing
        local unit FoG
        loop
            set FoG=FirstOfGroup(iterator)
            exitwhen FoG==null
            //
            call GroupAddUnit(swap,FoG)
            call GroupRemoveUnit(iterator,FoG)
        endloop
        set temp=iterator
        set iterator=swap
        set swap=temp
    endfunction
endscope

is FASTER than ForGroup for groups of size 2 and greater?

Hmm nice concept I did not think of a swap group veggie. I have benched with all but the Group Add Unit line; the difference was marginal at 2-3 units but grew more and more substantial. If the ForGroup uses any local variables in that function it will get even slower as the FoG loop allows you to use the same locals for all cycles.
 
Swapping the group is still about 20-30% faster than ForGroup with 10 units.

It was a steady 30% at 20 units, meaning comparatively the performance increases with more units.

JASS:
scope Benchmark initializer onInit
    
    globals
        private group G = CreateGroup()
        private group g = CreateGroup()
        private group t
    endglobals
    
    private function ForGroupCallback takes nothing returns nothing
        local unit u = GetEnumUnit()
        set u = null
    endfunction
    
    private function Actions takes nothing returns nothing
        local unit first = null
        local integer sw = 0
        local real array ticks
        
        set sw = StopWatchCreate()
        call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, null)
        loop
            set first = FirstOfGroup(G)
            exitwhen first == null
            call GroupRemoveUnit(G, first)
            call GroupAddUnit(g, first)
        endloop
        set t = G
        set G = g
        set g = t
        set ticks[0] = StopWatchTicks(sw)
        call StopWatchDestroy(sw)
        
        set sw = StopWatchCreate()
        call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, null)
        call ForGroup(G, function ForGroupCallback)
        set ticks[1] = StopWatchTicks(sw)
        call StopWatchDestroy(sw)

        
        //call ClearTextMessages()
        
        if (ticks[0] > ticks[1]) then
            set ticks[2] = 100 - (ticks[1]/ticks[0] * 100)
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Test #2 was " + R2S(ticks[2]) + "% faster than Test #1\n")
        else
            set ticks[2] = 100 - (ticks[0]/ticks[1] * 100)
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Test #1 is " + R2S(ticks[2]) + "% faster than Test #2\n")
        endif
    endfunction

    //===========================================================================
    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function Actions)
    endfunction

endscope
I'll update the OP later.
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
Can you mark the BJ against this

JASS:
    function RandomUnitFromGroup takes group g, boolean destroyGroup returns unit
        local group g2 = CreateGroup()
        local unit u = FirstOfGroup(g)
        local integer count = 0
        local integer rand
        set returnUnit = null
        loop
            exitwhen u == null
            set count = count + 1
            call GroupRemoveUnit(g,u)
            call GroupAddUnit(g2,u)
            set u = FirstOfGroup(g)
        endloop
        if count == 0 then
            call DestroyGroup(g2)
            if destroyGroup then
                call DestroyGroup(g)
            endif
            return null
        endif
        set u = FirstOfGroup(g2)
        set rand = GetRandomInt(1,count)
        set count = 1
        loop
            exitwhen u == null or (returnUnit != null and destroyGroup)
            if returnUnit == null then
                if count == rand then
                    set returnUnit = u
                else
                    set count = count + 1
                endif
            endif
            call GroupRemoveUnit(g2,u)
            if not destroyGroup then
                call GroupAddUnit(g,u)
            endif
            set u = FirstOfGroup(g2)
        endloop
        set u = null
        call DestroyGroup(g2)
        if destroyGroup then
            call DestroyGroup(g)
        endif
        return returnUnit
    endfunction
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
try inserting, that should get progressively slower because of thr collisions happening


It made no difference.

Inserting does get slower, but you won't see it with a mere 25k entries. Try 1.5m. Wc3 crashed for me at >3m entries because the memory was exhausted. I did it with SaveInteger and what's relevant is that RemoveSavedInteger did not seem to return most of the memory, so probably only blanks out the fields but leaves the data structure intact. Reallocating those fields again is therefore cheap. Destroying the hashtable clears up everything.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,526
Can someone please bench this against Pow()?

JASS:
library Exponent
    struct Exponent
        private static integer array q
        private static integer max = 6
        
        private static method getDenominator takes integer index returns real
            if index>max then
                set q[index]=q[index-1]*(index-1)
                set max=max+1
            endif
            return I2R(q[index])
        endmethod
        
        public static method fromTaylor takes real exp, integer fidelity returns real
            local real sum=1
            local real numerator=1
            local integer index=2
            if fidelity<1 then
                debug call ThrowError(true,"Exponent","fromTaylor","Exponent",-1,"Cannot compute taylor series of negative count!")
                return -1.
            else
                loop
                    exitwhen index>fidelity
                    set numerator=numerator*exp
                    set sum=sum + (numerator / getDenominator(index))
                    set index=index+1
                endloop
            endif
            return sum
        endmethod
        
        private static method onInit takes nothing returns nothing
            set q[1]=1
            set q[2]=1
            set q[3]=2
            set q[4]=6
            set q[5]=24
            set q[6]=120
        endmethod
    endstruct
endlibrary

Fidelities of 5, 10, and 15 should be good.

Edit:
To be clear, Exponent.fromTaylor(5.,<fidelity>) ~ Pow(bj_E,5.)
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
Hum so bad I did this :
JASS:
scope Stopwatch initializer onInit
    
    globals
        private constant integer ITERATIONS = 4000
    endglobals
    
    private function Actions takes nothing returns boolean
        local integer sw
        local integer i
        local real array ticks
        local string output

        set i  = 0
        set sw = StopWatchCreate()
            
        loop
            exitwhen i == ITERATIONS
            call Pow(bj_E,5.)
            set i = i + 1
        endloop
            
        set ticks[0] = StopWatchTicks(sw)
        set output = I2S(ITERATIONS) + " iterations of Test #1 took " + I2S(StopWatchMark(sw)) + " milliseconds to finish.\n"
        call StopWatchDestroy(sw)
            
        set i  = 0
        set sw = StopWatchCreate()
            
        loop
            exitwhen i == ITERATIONS
            call Exponent.fromTaylor(5.,5)
            set i = i + 1
        endloop
            
        set ticks[1] = StopWatchTicks(sw)
        set output = output + I2S(ITERATIONS) + " iterations of Test #2 took " + I2S(StopWatchMark(sw)) + " milliseconds to finish.\n\n"
        call StopWatchDestroy(sw)
            
        if (ticks[0] > ticks[1]) then
            set ticks[2] = 100 - (ticks[1]/ticks[0] * 100)
            set output = output + "Test #2 was " + R2S(ticks[2]) + "% faster than Test #1\n\n"
        else
            set ticks[2] = 100 - (ticks[0]/ticks[1] * 100)
            set output = output + "Test #1 is " + R2S(ticks[2]) + "% faster than Test #2\n\n"
        endif
        
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, output)
        
        return false
    endfunction

    //===========================================================================
    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t, function Actions)
    endfunction

endscope

And when I press Esc it dooesn't do anything :(
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
Ah maybe because of the operation in your code...
I'll check it ^^

EDIT : I put a Debug Message and it starts the matter is a crash during the thread.

With this :
JASS:
scope Stopwatch initializer onInit
    
    globals
        private constant integer ITERATIONS = 1000
    endglobals
    
    private function Actions takes nothing returns boolean
        local integer sw
        local integer i
        local real array ticks
        local string output
        set i  = 0
        set sw = StopWatchCreate()
            
        loop
            exitwhen i == ITERATIONS
            call Pow(bj_E,5.)
            set i = i + 1
        endloop
        call BJDebugMsg("LAUNCHED")
        set ticks[0] = StopWatchTicks(sw)
        set output = I2S(ITERATIONS) + " iterations of Test #1 took " + I2S(StopWatchMark(sw)) + " milliseconds to finish.\n"
        call StopWatchDestroy(sw)
            
        set i  = 0
        set sw = StopWatchCreate()
            
        loop
            exitwhen i == ITERATIONS
            call Exponent.fromTaylor(5.,5)
            set i = i + 1
        endloop
            
        set ticks[1] = StopWatchTicks(sw)
        set output = output + I2S(ITERATIONS) + " iterations of Test #2 took " + I2S(StopWatchMark(sw)) + " milliseconds to finish.\n\n"
        call StopWatchDestroy(sw)
            
        if (ticks[0] > ticks[1]) then
            set ticks[2] = 100 - (ticks[1]/ticks[0] * 100)
            set output = output + "Test #2 was " + R2S(ticks[2]) + "% faster than Test #1\n\n"
        else
            set ticks[2] = 100 - (ticks[0]/ticks[1] * 100)
            set output = output + "Test #1 is " + R2S(ticks[2]) + "% faster than Test #2\n\n"
        endif
        
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, output)
        
        return false
    endfunction

    //===========================================================================
    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t, function Actions)
    endfunction

endscope

Result :
Test #1 is 88.022% faster than Test #2
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
It was obvious anyway, you can hardly be faster with some custom jass function against a native one, it's just not the same scale of efficiency.

Just as an example i expect that

Deg2Rad(r) against r*bj_DEGTORAD would be about the same efficiency or even faster, even if you use a constant with just one letter length instead of the bj one.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,526
It was obvious anyway, you can hardly be faster with some custom jass function against a native one, it's just not the same scale of efficiency.

Not really, Pow() is likely implemented using bisection with a pretty accurate epsilon value, maybe even double-precision numbers. Sure the Taylor series takes a lot of lines (expensive), but it was worth a try.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,526
The main takeaway here is that JASS is an interpreted language and doesn't treat your script nearly the same as its own.

Yes, everyone is well aware that jass is an extremely slow interpreted language, but in this case I didn't rewrite Pow. The taylor expansion of exp() is a special case, and the result was unpredictable.

It's like rewriting the quick sort in Python is always slower than using the List.sort ^^

It's more like implementing a radix sort in python and comparing it to List.sort - radix sort is in theory much faster, but you can't always beat compiled code.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
could you by any chance compare

JASS:
GetTriggerUnit()
GetDyingUnit()
GetSellingUnit()
?

In terms of how, everything to GetTriggerUnit will suffice

Edit: Also, if Jass is ad hoc compiling during runtime, can you check if function recursivelly calling itself is faster than calling N functions? Because if it does ad hoc compiling, it should(but maybe it doesnt, or it doesnt even work this way) run faster, because it tries to call itself, and if it is complied it should try to re-compile itself

Thank you
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
GetTriggerUnit() is generally recommended for the sake of simplicity - if you need to copy&paste it's much better to use the trigger unit than an event-specific unit.

Recursive functions are slower than loops when local variables need to be created. I can't tell you if recursive functions are faster than another function calling over to that function many times, but I can tell you that when I have the opportunity for recursive function calls, I jump on that like Master Roshi on Bulma.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I know about the GetTriggerUnit preference, but on other forum I got asked by GUIer if he should switch from Attacked Unit, Dying Unit etc to GetTriggerUnit if he wants total max performance.

I said to him that it would be 4% difference at max, if any(it could theoretically read the same variable)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Here is my benchmark library :eek:

It accounts for StopWatchCreate and StopWatchTicks

Measured a function call to be .5 ms with this ^)^

JASS:
//.5 ms
function p takes nothing returns nothing
endfunction

//.8 ms
R2I

//1 ms
TriggerEvaluate

//1.55 ms
TriggerEvaluate(empty trigger)

//12.5 ms
TriggerEvaluate(trigger with 1 function)

//22 ms (no dif with returns nothing functions)
TriggerEvaluate(trigger with 2 functions)

//10 ms per triggercondition

//.95 ms
//faster because of 1 less letter
TriggerExecute

//7.5 ms
TriggerExecute(empty trigger)

//18.5 ms
TriggerExecute(trigger with 1 function)

//28 ms
TriggerExecute(trigger with 2 functions)

//10 ms per triggeraction

//trigger executes get slower the more that are called in a single thread
//it adds about 0.00055 ms per execution

//.33 ms
global variable assignment

//.12 ms
global variable lookup

//local variables are the same as locals depending on number
//of symbols. We've already known this.

//.02 ms
initialization of variable

//0.38 ms
variable declaration

//1.375 ms
LoadInteger(all initialized)

//.47 ms
set d[0] = 0

//.55 ms
set d[0] = d[1]

//1.75 ms
SaveInteger(all initialized)

//3.63 ms
set d[8191] = 0 //not initialized

//1.05 ms
set d[0] = 0 //not initialized

//1.32 ms
set d[512] = 0 //not initialized

JASS:
/*
*   may not use symbols
*
*       s,l,g,o,r
*/
//! textmacro benchmark_locals
//! endtextmacro
    //put locals here
//! textmacro benchmark
    //put code here
//! endtextmacro
//! textmacro benchmark_cleanup
    //cleanup code
//! endtextmacro

































function s takes nothing returns real
    local integer l = 0
    local real g = 0
    local real o = 0
    local real r = 0

    //! runtextmacro benchmark_locals()
    set l = StopWatchCreate()
    set g = StopWatchTicks(l)
    set o = StopWatchTicks(l) - g
    //! runtextmacro benchmark()
    set r = StopWatchTicks(l)
    call StopWatchDestroy(l)
    
    set r = r - g - o - o - .33 - .068 + .014 + .008
    
    //! runtextmacro benchmark_cleanup()
    
    return r
endfunction
scope zeskazfe initializer init
    globals
        private real j = 0
        private trigger a = null
        private trigger e = null
    endglobals
    private function f takes nothing returns boolean
        local real x = 0
        
        set x = s()
        set j = j + x
        
        return false
    endfunction
    private function b takes nothing returns boolean
        local integer i = 0
        local integer h = 2000
        
        loop
            exitwhen i == h
            
            call TriggerEvaluate(e)
            set i = i + 1
        endloop
        
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,R2SW(j/h, 0, 5) + " ms")
        
        set j = 0
        
        return false
    endfunction

    private function d takes nothing returns nothing
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
        call TriggerEvaluate(a)
    endfunction

    private function c takes nothing returns nothing
        call DestroyTimer(GetExpiredTimer())
        
        call d()
    endfunction
    private function init takes nothing returns nothing
        set a = CreateTrigger()
        set e = CreateTrigger()
        call TriggerAddCondition(a, Condition(function b))
        call TriggerAddCondition(e, Condition(function f))
        call TimerStart(CreateTimer(), .1, false, function c)
    endfunction
endscope
 
Last edited:
Here's my updated benchmark library. This version allows iteration stacking to get more refined results. It's also formatted better so it should make the testing process a bit easier.

JASS:
library Stopwatch initializer onInit
    
    //===========================================================================
    
    //! textmacro STOPWATCH_BENCHMARK_LOCAL_VARIABLES
        local real c
    //! endtextmacro
    
    //! textmacro STOPWATCH_BENCHMARK_TEST_1
        set c = Pow(5, 2)
    //! endtextmacro 
    
    //! textmacro STOPWATCH_BENCHMARK_TEST_2
        set c = (5 * 5)
    //! endtextmacro 
    
    //! textmacro STOPWATCH_BENCHMARK_INIT
    //! endtextmacro
    
    globals
        private constant integer ITERATIONS = 1000 // how many times to run each test
        private constant string  TEST_1     = "Pow(5, 2)"
        private constant string  TEST_2     = "(5 * 5)"
        private constant boolean USE_SLEEP  = false // add TriggerSleepAction's between tests
        private constant boolean STACK      = true // stack iterations
        
        // personal variables here
    endglobals
    
    //===========================================================================
    
    globals
        private integer C=1 // stack count
        private real array R // results
    endglobals
    
    // Declare the StopWatch natives
    native StopWatchCreate  takes nothing returns integer
    native StopWatchTicks   takes integer id returns integer
    native StopWatchMark    takes integer id returns integer
    native StopWatchDestroy takes integer id returns nothing

    private function Actions takes nothing returns nothing
        local integer sw
        local integer i
        local string output = ""
        
        //! runtextmacro STOPWATCH_BENCHMARK_LOCAL_VARIABLES()
        
        // Disable the trigger so it can't be interrupted
        call DisableTrigger(GetTriggeringTrigger())
        
        static if (USE_SLEEP) then 
            call TriggerSleepAction(0) 
        endif
        
        set i  = 0
        set sw = StopWatchCreate() // Create the StopWatch
        
        loop
            exitwhen i == ITERATIONS // Run test #1 X amount of times
            //! runtextmacro STOPWATCH_BENCHMARK_TEST_1()
            set i = i + 1
        endloop
        
        // If stacking is enabled, the results will add
        static if (STACK) then
            set R[0] = R[0] + StopWatchTicks(sw)
            set R[3] = R[3] + StopWatchMark(sw)
        else
            set R[0] = StopWatchTicks(sw)
            set R[3] = StopWatchMark(sw) // the length in milliseconds it took to run
        endif
        
        call StopWatchDestroy(sw)
        
        static if (USE_SLEEP) then 
            call TriggerSleepAction(0) 
        endif
        
        set output = "|cff008200===========================================================================|r\n"
        set output =  output + "|cffffcc00" + I2S(ITERATIONS * C) + "|r iterations of " + TEST_1 + " took |cffffcc00" + I2S(R2I(R[3])) + "|r milliseconds to finish.\n"
        
        static if (USE_SLEEP) then 
            call TriggerSleepAction(0) 
        endif
        
        // Set the iteration count to 0
        set i  = 0
        set sw = StopWatchCreate()
        
        loop
            exitwhen i == ITERATIONS
            // This is where test #2 is being executed
            //! runtextmacro STOPWATCH_BENCHMARK_TEST_2()
            set i = i + 1
        endloop
        
        static if (STACK) then
            set R[1] = R[1] + StopWatchTicks(sw)
            set R[4] = R[4] + StopWatchMark(sw)
        else
            set R[1] = StopWatchTicks(sw)
            set R[4] = StopWatchMark(sw)
        endif
        
        call StopWatchDestroy(sw)
        
        // Enable the trigger
        call EnableTrigger(GetTriggeringTrigger())
        
        set output = output + "|cffffcc00" + I2S(ITERATIONS * C) + "|r iterations of " + TEST_2 + " took |cffffcc00" + I2S(R2I(R[4])) + "|r milliseconds to finish.\n\n"
    
        // Display the results
        if (R[0] > R[1]) then 
            set R[2] = 100 - (R[1]/R[0] * 100)
            set output = output + TEST_2 + " was " + R2S(R[2]) + "% |cff80ff80faster|r than " + TEST_1 + "\n\n"
        else
            set R[2] = 100 - (R[0]/R[1] * 100)
            set output = output + TEST_1 + " is " + R2S(R[2]) + "% |cff80ff80faster|r than " + TEST_2 + "\n\n"
        endif
        
        static if (STACK) then
            set C=C+1
        endif
        
        set output = output + "|cff008200===========================================================================|r\n\n\n\n\n"
        
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, output)
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i == 15
            call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_END_CINEMATIC)
            set i = i + 1
        endloop
        call TriggerAddAction(t, function Actions)
        //! runtextmacro STOPWATCH_BENCHMARK_INIT()
    endfunction
    
    //===========================================================================
    
endlibrary

I've also updated the main original post with some information on TriggerEvaluate and ExecuteFunc.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,195
People are biting at straws in this thread. So what if one method is 30% slower than the other? In most maps there are bigger fish to fry.

Has anyone actually benchmarked how much wasted time all those trigger evaluations cause when a custom ability is cast? The common way of doing a fully MUI custom ability is a any unit begins effect event followed by some kind of test to kill the thread if the wrong ability is cast. This is fine for 1, 2 and even 5 abilities but some maps have dozens of custom abilities doing this. The overhead of evaluating custom abilities is thus O(n) per cast where n is the number in the map. This technically means a map with hundreds of custom abilities would have a pretty huge overhead per ability cast just to check if it was the correct ability being cast. Worse is these evaluations apply to even non-triggered abilities as they still are tested if they are triggered.

So the solution? Well two spring to mind...
1. Use a single ability cast system that registers functions and the ability to trigger them. When an ability is cast by a unit it then performs a hashtable lookup for the ability function. If none exists then it was not a triggered ability and nothing happens. If one exists then it evaluates it. This results in a O(1) overhead no mater how many triggered abilities are registered.
2. Only register units which have the ability to the ability trigger using a specific unit event. This still results in O(n) tests but n is only the triggered abilities that the specific unit has so is considerably smaller and a non-issue. The system needs to manage removed units to be MUI so that adds some additional overhead. The advantage of this approach is no tests for units that do not have triggered abilities at all.

The potential savings is small but I imagine it will add up over time. Especially seeing how each evaluation creates a separate trigger thread.
 
Top