• 🏆 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

Level 4
Joined
Jan 7, 2014
Messages
69
FoG loops are only meant to be used when the group is done with the units it contains, as the group is empty afterward. The next thing to consider is that having a "null" filter requires the least work from the game engine as it doesn't need to open up a new thread per unit it evaluates.

99% of the time what I see is a group being used for quick "is that unit in range, and if so do actions" and then the group is obsolete. A single unit group can be used for each of these tasks, unless you need to nest a FoG loop within another one. Then you use a second permanent unit group for that second enumeration.

If you are part of the minority who will do things with that unit group and not just use it for those quick enums, then ForGroup or a unit array should be used for iterating. A unit array will be faster if more than 1 or 2 units need to be iterated over.

FirstOfGroup (and unit arrays) allows you to access local variables; ForGroup and the filterfunc passed to GroupEnum/ requires they all be globals.

i do smth like this if there is no DoT or smth like that:
call Groupenumunitsinrange(g,x,y,range,some global filter)

and in this filterfunc do
function filter takes nothing retuns boolean
local\or global unit = GetFilterUnit()
if UnitAlive(unit) and not IsUnitType(unit,unitmagicimmune) then
some actions
//if need to add this unit in special group(for index for example) then
add unit in group
return return true
if not need then just
endif
return false
endfunction

and later if need do smth with group with units i do if only one action need without reverse groups then do FoG if not, then do ForGroup()

in another side, i just do all needed actions in filter function.
and works perfect, mb its not really good way, but i think its a fastest method qoz only one loop with enum units action. :) thats why i asked. in another way, i have no open source of wc3 for look whats happens there in real in that functions, i can only imagine, that this forgroup its the same loop emulation with virtual functions like FoG in wc3 engine and mb with some delays between actions with every units for sync every action in this loop instead of just loop wich do everythin without any delays(i mean in 1-2 ns or smth like that) and thats why need everytime to destroy group after that, and enumirate its same loop with check every units in area or rect wich returns true in conditions.

and its looks smth like:
function forgroup takes group g func a return => create thread(code) for group(g)
thread g():
loop
unit enumunit[x] = unit in group(g)/simulate as firstofgroup(g) = get max handle of units in group g...
do actions....
x ++
if x > maxX then
endloop
endif
finish thread.

and enumunits its smth the same for me but with conditions.
is unit in range\rect then return true. and etc

so if thats true, then we can use loop with indexed units like unit[1] unit[2] and etc without firstofgroup...like

groupenumunitsinrange function
set unit u = GetFilterUnit()
if .... then
set unit[x] = u
set x = x + 1
set maxX = x
return true
endif
return false

and after use loops with indexed units in array
loop
exitwhen x > maxX
if unit[x] blabla then
some actions
endif
set x = x + 1
endloop
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
ForceEnum... cannot be used unless it's a single-player game, as it will pick all humans.

ExecuteFunc is twice as slow as TriggerExecute , which is twice as slow as TriggerEvaluate. ForForce(force, code) is about on par with TriggerEvaluate probably since the type of code they run is similar (ie. not allowing thread sleeps).
 
Could someone make a short benchmarking test?

In the demo map a unit has 2 abilties, and i would want to meassure the time difference between onOrder and onCast events of these 2 abilites.

One is based on channel, set to instant cast, and uses an instant orderid.
One is based on devineshield, which is also uses an instant order.

I have the feeling channel based spells are not as instant as originaly based spells of instant abilties.

edit: it had a small syntax error I recognized; fixed it. Sorry I can't compile atm, but it should work now
 

Attachments

  • Test.w3x
    13.2 KB · Views: 92
Last edited:
Level 19
Joined
Dec 12, 2010
Messages
2,069
Could someone make a short benchmarking test?

In the demo map a unit has 2 abilties, and i would want to meassure the time difference between onOrder and onCast events of these 2 abilites.

One is based on channel, set to instant cast, and uses an instant orderid.
One is based on devineshield, which is also uses an instant order.

I have the feeling channel based spells are not as instant as originaly based spells of instant abilties.
nxSQ.png

spell 1 tends to happen 1 mcs later, maybe its heavy inside (its not guaranteed "later", it can be the same mcs as well if lucky). spell 2 always proc both triggers at the same mcs with rare exceptions. there are no diff for the hoomans
 
Level 8
Joined
Jan 23, 2015
Messages
121
I wonder what difference is in between evaluating single trigger with several conditions and several triggers with single conditions?
Also, how does it differs with this way Magtheridon once suggested:
JASS:
function RunFunction takes code c returns nothing
    call ForForce(bj_FORCE_PLAYER[0], c)
endfunction

local code f = function lambda
call RunFunction(f)
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Game doesn't cache anything regardin abilities obtained by unit. Than means when you're gonna call GetUnitAbilityLevel it will go through every ability unit has, starting from the very last added, and iterate every ability until will find required.
If you just added this ability to unit, it will be the last one and result will be given almost instantly. But in case if you're used to flood your map with tons of abilities, for instance - 2-base systems for dynamic armor, damage, movespeed, etc, search will be very slow.

In some cases you may think about storing data in hashtable (arrays if you have something to adjust them properly), since lookup inside hashtable is much faster than those.

That's how perfomance looks like (500 calls for every line).
HNDk0nm.png

zKQsfjs.png


As you can see, I just added few more abils after adding the one I want to check for. It drastically reduced GetUnitAbilitLevel perfomance, which being equal or faster to hashtable lookup.
The more abilities unit has, the worse perfomance become.

So, in a long term, it's rather make sense to store ability's level inside variable (array/hashtable) rather than retrieving it everytime it needed.

Also, just in case, comparing HaveSavedBoolean vs LoadBoolean, the HaveSavedBoolean is a little bit slower (literally less than 1%, about 3-5 mcs).


Ok, this one is long known, yet still
GetWidgetLife(u) faster than GetUnitState(u,UNIT_STATE_LIFE)
for 500 calls it took ~200 mcs and ~300 mcs respectively
since op is very small in terms of calcs it barely matters unless you gonna compare tons of HP in a single step
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
is there another wayts to spawn unit?

Kind of:
JASS:
native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit
native CreateUnitByName takes player whichPlayer, string unitname, real x, real y, real face returns unit
native CreateUnitAtLoc takes player id, integer unitid, location whichLocation, real face returns unit
native CreateUnitAtLocByName takes player id, string unitname, location whichLocation, real face returns unit

// first create a corpse then revive the corpse (using a revive ability, I guess)
native CreateCorpse takes player whichPlayer, integer unitid, real x, real y, real face returns unit

// first store the unit in a gamecache then restore it (the result is an imperfect clone)
native StoreUnit takes gamecache cache, string missionKey, string key, unit whichUnit returns boolean
native RestoreUnit takes gamecache cache, string missionKey, string key, player forWhichPlayer, real x, real y, real facing returns unit

// create a unitpool with a single unit =)
native CreateUnitPool takes nothing returns unitpool
native PlaceRandomUnit takes unitpool whichPool, player forWhichPlayer, real x, real y, real facing returns unit

// a bit too specific
native CreateBlightedGoldmine takes player id, real x, real y, real face returns unit
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
JASS:
native CreateUnitByName takes player whichPlayer, string unitname, real x, real y, real face returns unit
native CreateUnitAtLoc takes player id, integer unitid, location whichLocation, real face returns unit
native CreateUnitAtLocByName takes player id, string unitname, location whichLocation, real face returns unit
calls for CreateUnit inside of them

rest funcions calls for sub-functions of CreateUnit, basically doing all the same plus additional functionality.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Do you know how Blizzard resurrects dead units? Such that their handle-id, unit-user-data is preserved and UnitAlive returns true of course.

I think there are 2 abilities that do this: 'AHre' (Resurrection) and 'Aast' (Ancestral Spirit) and people have used those for resurrecting units but that doesn't work for units that don't leave a corpse (as far as I know).

There's ReviveHero for heroes but no equivalent for units.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Do you know how Blizzard resurrects dead units?
once corpse removed, unit non-existant anymore.
ReviveHero has check for target being hero, as well as plenty of it's subfunctions. im too lazy to edit that much code.
basically death is complex of procedures - every ability being silenced and hidden, used special flag "dead", etc etc.
everything of that is revertable like np, one Issue i can imagine is how to stop corpse being removed by garbage removal later

edit: creep didn't get removed, so it's just fine. issue is, he doesnt want to listen to commands anymore :(

edit of edit:
actually wait, lol, I successfully ressurected creep, removing only 2 first conditions IsHero from those functions
What if instead of ExecuteFunc we just call func() , any benchmark results on that?
func() 100+ times faster, since it compiled directly into bytecode
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
actually wait, lol, I successfully ressurected creep, removing only 2 first conditions IsHero from those functions
You've just edited a native? (if I'm not wrong thinking you're talking about ReviveHero

Does that mean that ReviveHero is a generic (or calls some generic) revive unit function but Blizzard added a is-unit-hero check and made it hero specific 0.O?

When you patched the ReviveHero native to work with units that change would last until the game is closed/exited correct?

PS: patching natives sounds pretty cool, I wonder how that works... idea for a tut ;P
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
If you have tons of fast-fast-fast timers and triggers like I do, you probably bothers about their speed.
I did some tests and yet again found nothing new, but still worthy noting as actually proved.

Declaring locals is slow process. Even passing them as arguments doesn't help as it's basically all the same local variable declared automatically.
JASS:
function TestA takes nothing returns nothing
    call EnableTrigger(GetTriggeringTrigger())
endfunction

function TestB takes trigger t returns nothing
    call EnableTrigger(t)
endfunction

function test takes nothing returns nothing
call TestA() //x500 times
call TestB(GetTriggeringTrigger()) //x500 times
endfunction
Even though both of functions makes calls to the same native, due to local declaration second one has reduced speed. Only 15% of speed been lost, less than quart of microsecond for 500 calls, yet useful ticks of CPU, especially when you works with low-tier CPUs.

Note that difference highly variate depending on other ingame cycles so rarely 2nd option lose to 1st one or takes equally long. But in like 90% cases passing value is still slower than using a native.

And yet again - there's no way to be faster than JASS native inside the jass itself. No way your custom distance check function will be faster than IsUnitInRange() or IsUnitInRangeXY() natives.

JASS:
function TestA takes nothing returns nothing
    local trigger t=GetTriggeringTrigger()
    call EnableTrigger(t)
endfunction

function TestB takes trigger t returns nothing
    call EnableTrigger(t)
endfunction
TestA only a bit slower than TestB (less than 10% difference)

but things come bit different when we're talking about multiple passing args:
JASS:
function TestA takes nothing returns nothing
    local trigger t=GetTriggeringTrigger()
    local integer h=GetHandleId(t)
    local integer c=GetTriggerEvalCount(t)
    call EnableTrigger(t)
endfunction

function TestB takes trigger t, integer h, integer c returns nothing
    call EnableTrigger(GetTriggeringTrigger())
endfunction

function test takes nothing returns nothing
 set tt_trig1=GetTriggeringTrigger()
 call TestA()// x500
 call TestB(tt_trig1,GetHandleId(tt_trig1),GetTriggerEvalCount(tt_trig1))// x500
endfunction
They finishes nose to nose, literally less than few mcs difference. Means it still makes sense to pass variables in case there's plenty of them.

JASS:
function TestA takes nothing returns nothing
    local integer a
    call EnableTrigger(GetTriggeringTrigger())
endfunction

function TestB takes nothing returns nothing
 
    call EnableTrigger(GetTriggeringTrigger())
endfunction
TestB been faster for ~15%

Setting value to the variable does affect perfomance yet .

JASS:
function TestA takes nothing returns nothing
    local integer a=1
    local boolean b=true
    local real r=10.
    call EnableTrigger(GetTriggeringTrigger())
endfunction

function TestB takes nothing returns nothing
    local integer a
    local boolean b
    local real r
    call EnableTrigger(GetTriggeringTrigger())
endfunction

TestB faster for ~30%


So, what does it mean?
In case if your algo works in a long fast loop with 0.02s interval or so, it's better to remove unused local/passed variables at all.
In case if passed value can be found in a native and you only use it once (meaning you don't need to store it into variable) you better call native inside instead of passing value. Means
JASS:
function TestA takes integer h returns nothing
    call SaveInteger(HY,h,'_key',1)
endfunction

function TestB takes nothing returns nothing
    call SaveInteger(HY,GetHandleId(GetTriggerPlayer()),'_key',1)
endfunction

function SpeedRun takes nothing returns nothing

call TestA(GetHandleId(GetTriggerPlayer()))
call TestB()
endfunction
TestB faster than A for ~30%
 
Last edited:
Level 19
Joined
Dec 12, 2010
Messages
2,069
man, im using global damage detection which fires ~20 different functions at fire with dozen branches of "if-then-else" stuff. note that various aruas and buff places keep calling 0 damage instance which is registered too. that matters for me as there's like 10k+ damage events of this kind per 5 mins. it doesn't matter even for 0.02 timers, but in case if your script already overloaded thats something you may shrink
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
there's specific variations. some skills use 0 damage instance to provide effect, since they're casted through dummies its easy to distinct. Im currently Opting things, will use 0.1 damage instead to ignore stupid debuffs. and there's many on attack/attack landing/attack fire events in dota for various heroes, custom crit/bash/orb effects systems
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
it could be anything
IsUnitImmune(u)==false or not myBoolean
idk what are you refering to, I never wrote "==true " or something. using ==false next to boolean is just fine, as well as "not", can't see why not. dont forget it could be complex condition, not a simple toggle
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,178
DSG means using "==true/false" on boolean is bad. I only used == on booleans when comparing booleans.
Well in most languages it is. Why else was logic not created?

Exception obviously is JASS where one of the boolean values is... not boolean. I forget which, but I think it is the unit classification comparison. It returns a true that is not true unless tested against true.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
idk just tried IsUnitType(GetTriggerUnit(),UNIT_TYPE_FLYING) in a filter for UnitEntersRegion event
worked just fine, firing for flying only. and this one returns 0xD. So I guess thats no longer case for 1.26 or newer.
At least I didn't try to mess with boolexpr ops And, Or and following (who the hell uses them anyway?)
 
Top