• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Benchmark] Fastest way to run code

Status
Not open for further replies.
ForForce and TriggerEvaluate run at about the same speed, which is about 4x faster than TriggerExecute, which is about 50-100% faster than ExecuteFunc. So ExecuteFunc is about 8x slower than TriggerEvaluate, making TriggerEvaluate by far the best option for running dynamic code.

ExecuteFunc's main benefit being that it doesn't use any handles, means it's fine to use it for occasional code threading but not nearly as useful for dynamic purposes.

This can be used as a reference for anyone who's running a test.

JASS:
scope Poop initializer Init
    globals 
        private trigger trig = CreateTrigger()
        private force f = CreateForce()
    endglobals
    function TestFunc takes nothing returns nothing
    endfunction
    private function Tester takes nothing returns nothing
        local integer i = 500
        loop
            set i = i - 1
            //call ExecuteFunc("TestFunc")
            //call TriggerExecute(trig)
            //call TriggerEvaluate(trig)
            call ForForce(f, function TestFunc)
            exitwhen i == 0
        endloop
    endfunction
    private function Init takes nothing returns nothing
        local code c = function TestFunc
        //call TriggerAddAction(trig, c)
        //call TriggerAddCondition(trig, Filter(c))
        call ForceAddPlayer(f, Player(0))
        call TimerStart(CreateTimer(), 0.03125, true, function Tester)
    endfunction
endscope
 
Nice benchmark, but seriously, when is that really important? It's not like you would ever need running thousands of triggers in extremely short intervals...

I can see benchmarks being important for higher complexity stuff, but dynamic triggers usually are O(1) anyway (cause that's why they exist).

And usually you use TriggerEvaluate anyway, as triggerconditions don't leak, whereas triggeractions do if not cleaned properly.
Also I can not think of much reasons where you'd actually need to use dynamic triggers. The "prime" example is damage detection, yes, but the most common systems use TriggerConditions and TriggerEvaluate anyway.

ExecuteFunc still has some advantages over TriggerEvaluate, as you do not need to store your trigger in some kind of table but can instantly access the function just by naming.

I used ExecuteFunc for AI scripts of units, where I name the function after the I2S() of the unit's type ID.
when doing this with TriggerEvaluate, I'd need to retrieve the trigger from the table before using it with trigger evaluate. I can't imagine this being faster than using executeFunc in the first place.

Can you please add a hashtable lookup to your benchmark for trigger evaluate? That would make a more realistic comparison.
 
Well doing I2S in the first way makes the optimizer's "compress names" feature unusable because it can't handle concatenation. You could store the triggers into an array and access them by array index.

Dynamic function calling is pretty commonplace at this point, if you look in our JASS Resource section it's used all over the place. It is also used every time you see ".execute", ".evaluate" or struct interfaces/stub methods.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Why shouldn't triggerconditions leak in comparison to triggeractions?

The "prime" example is damage detection, yes, but the most common systems use TriggerConditions and TriggerEvaluate anyway.

Those are dynamic triggers.

Dynamic triggers are essential. Just imagine you want to create a buff spell and upon death, an action is done. If your triggers were static, you would have to analyze any unit's death to check for the buff. The larger the map grows, the more triggers unnecessarily fire. It also means you have to put in more filtering conditions and scopes are less likely independent. You are lucky that wc3 already hands out native event routines for multiple use but that does not account for custom content, you could build a unit is dispelled event for example and we know there is no good TriggerRemoveEvent-function.
 
Well doing I2S in the first way makes the optimizer's "compress names" feature unusable because it can't handle concatenation. You could store the triggers into an array and access them by array index.
This is true. I agree on this point.

However, there are still things you can not do that easy without ExecuteFunc:
Let's say every UnitTypeID in your map got it's own function that needs to be run periodically (i.e. AI scripts).
When doing this with TriggerEvaluate, you'd need to:
- Write the function for every unit type
- Store the function to a trigger (note that this would require a seperate registry step for all unit types existing, there's no 'elegant' way of doing this automaticly, as you'd need to put every function into each trigger manually)
- add the trigger to a table

When doing this with ExecuteFunc, all you need is a naming convention for those functions based on your unit ID. No registry step, no table required.
The amount of code that is required is basicly halfed in case of one-liner functions.
Let's say you got like 200 different unit types ... In addition to writing 200 functions, you need 200 registry calls. That's 200 more lines of code resulting in a larger filesize. A bad trade for just turning of 'compress names', if you ask me - but sure, it depends on how much code there is in your map and how much space 'compress names' saves.

Dynamic function calling is pretty commonplace at this point, if you look in our JASS Resource section it's used all over the place. It is also used every time you see ".execute", ".evaluate" or struct interfaces/stub methods.
Dynamic functions are a true problem with wc3. I can't understand why Blizzard didnt allow us access to the 'code' variable.
However, I can't see all those dynamic function systems being faster than simply calling ExecuteFunc once. It's just a horrible long and complicated workaround for one simple native call - at least when we are indeed talking about dynamic functions, not dynamic triggers.

Why shouldn't triggerconditions leak in comparison to triggeractions?
I don't quite know, really. Some guy on TheHelper found that one. I can't really remember the source. He somehow proved that the memory usage when using triggeractions on dynamic triggers that got cleaned afterwards doesn't go back to it's initial value, whereas on triggerconditions it does. Somehow triggeractions don't get properly removed from memory when the trigger is destroyed.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
It's not elegant to run stuff scope-crossing via ExecuteFunc without input as it would require the name convention just like you said.

You'd need to write the function for every unit type anyway, since you want it to differ.
Of course you can just pass the function from the individual unit type and table or framework are in the header.
With macros, you could shorten/avoid the extra registry.
I also have AI scripts but I dynamically add components to the units. It's not even always about the unit type but bound to other features such like abilities they possess. Statically defining the unit type would be very inflexible.
You can retrieve data from passed code arguments and therefore identify it, no need to store the code type unless you want to run via ForForce for example, which takes it directly.
 
It's not elegant to run stuff scope-crossing via ExecuteFunc without input as it would require the name convention just like you said.
Well, I agree. But we are talking about map-specific code here. When writing a public module, there should be a registry. However in most cases where you need a 'function database', the module is map specific, not public.

You'd need to write the function for every unit type anyway, since you want it to differ.
That's true. However, when using triggers, you'd need to manually attach that function to one trigger for each function, resulting in another step to do for every unit type registered and longer code.

Of course you can just pass the function from the individual unit type and table or framework are in the header.
You'd still need to do it, no matter HOW you do it. And it can't be automated.

With macros, you could shorten/avoid the extra registry.
When compiling all macros are replaced by the actual code. You might save the extra coding time, but the code (file size) won't get shorter just by this.

I also have AI scripts but I dynamically add components to the units. It's not even always about the unit type but bound to other features such like abilities they possess. Statically defining the unit type would be very inflexible.
You can retrieve data from passed code arguments and therefore identify it, no need to store the code type unless you want to run via ForForce for example, which takes it directly.
Depends on what you want to do. I personally need one seperate function for each unit type, as I need direct control over everything it does, not just some kind of template I use. Of course I could write extremely flexible templates, but then again, the effort might be higher than just using predefined functions - especially for bossfights.


Another example:
I use ExecuteFunc for running my spell triggers, using a general spellcast event.
When a unit starts casting an ability, you simply get the object name of that ability and run the function by that name (in case there is a naming convention).
Without doing that, you'd again need to have a registry for all abilities existing. And sure in case of abilities (as you won't have thousands of them) that would be no problem ... but then again I like my systems to work without any additional interaction from me when implementing new content.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Well, I agree. But we are talking about map-specific code here. When writing a public module, there should be a registry. However in most cases where you need a 'function database', the module is map specific, not public.

I would not do this in my map either to maintain order and accessability. And if it's so private, you can collect all the registries in one function. By the way, you need to register your unit types anyway unless you require every unit to possess one or inelegantly try to run functions that do not exist.

That's true. However, when using triggers, you'd need to manually attach that function to one trigger for each function, resulting in another step to do for every unit type registered and longer code.

No, you don't. Passing the function as code to header is enough.

When compiling all macros are replaced by the actual code. You might save the extra coding time, but the code (file size) won't get shorter just by this.

Does not matter and is tiny.

Depends on what you want to do. I personally need one seperate function for each unit type, as I need direct control over everything it does, not just some kind of template I use. Of course I could write extremely flexible templates, but then again, the effort might be higher than just using predefined functions - especially for bossfights.

Well, you cannot add behaviors to units or unit types during run-time this way. For example, when your boss steals an ability/buff.

but then again I like my systems to work without any additional interaction from me when implementing new content.

JASS:
//! runtextmacro Spell_OpenScope("/")

//! runtextmacro BaseStruct("Vomit", "VOMIT")
    static real array DAMAGE
    //! runtextmacro DummyUnit_CreateSimpleType("/", "dVom", "Vomit", "DUMMY_UNIT_ID", "Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl")
    static real array POISON_DURATION
    static real array POISON_HERO_DURATION

    static Spell THIS_SPELL

    Unit caster
    integer level
    Unit target
    SpellInstance whichInstance
    real x
    real y

    static method Impact takes nothing returns nothing
        local Missile dummyMissile = MISSILE.Event.GetTrigger()

        local thistype this = dummyMissile.GetData()

        local Unit caster = this.caster
        local integer level = this.level
        local Unit target = this.target
        local SpellInstance whichInstance = this.whichInstance

        call this.deallocate()
        call dummyMissile.Destroy()

        if (target.Classes.Contains(UnitClass.HERO)) then
            call target.Poisoned.AddTimed(thistype.POISON_HERO_DURATION[level])
        else
            call target.Poisoned.AddTimed(thistype.POISON_DURATION[level])
        endif

        call caster.DamageUnitBySpell(target, thistype.DAMAGE[level], true, false)

        call whichInstance.Destroy()
    endmethod

    static method Event_SpellEffect takes nothing returns nothing
        local Unit caster = UNIT.Event.GetTrigger()
        local Missile dummyMissile = Missile.Create()
        local DummyUnit dummyUnit
        local Unit target = UNIT.Event.GetTarget()
        local thistype this = thistype.allocate()
        local SpellInstance whichInstance = SpellInstance.Create(caster, thistype.THIS_SPELL)

        set this.caster = caster
        set this.level = whichInstance.GetLevel()
        set this.target = target
        set this.whichInstance = whichInstance
        set this.x = caster.Position.X.Get()
        set this.y = caster.Position.Y.Get()

        call dummyMissile.Arc.SetByPerc(0.06)
        call dummyMissile.CollisionSize.Set(10.)
        call dummyMissile.DummyUnit.Create(thistype.DUMMY_UNIT_ID, 1.)
        call dummyMissile.Impact.SetAction(function thistype.Impact)
        call dummyMissile.SetData(this)
        call dummyMissile.Speed.Set(900.)
        call dummyMissile.Position.SetFromUnit(caster)

        call dummyMissile.GoToUnit.Start(target, false)
    endmethod

    static method Init takes nothing returns nothing
        //! runtextmacro Spell_Create("/", "THIS_SPELL", "AVom", "Vomit")

        //! runtextmacro Spell_SetTypes("/", "NORMAL", "ARTIFACT")

        //! runtextmacro Spell_SetAnimation("/", "spell")
        //! runtextmacro Spell_SetCooldown5("/", "5.", "5.", "5.", "5.", "5.")
        //! runtextmacro Spell_SetData5("/", "DAMAGE", "damage", "30.", "40.", "50.", "60.", "70.")
        //! runtextmacro Spell_SetData5("/", "POISON_DURATION", "poisonDuration", "3.", "4.", "5.", "6.", "7.")
        //! runtextmacro Spell_SetData5("/", "POISON_HERO_DURATION", "poisonHeroDuration", "2.", "2.", "2.", "2.", "2.")
        //! runtextmacro Spell_SetIcon("/", "ReplaceableTextures\\CommandButtons\\BTNCorrosiveBreath.blp")
        //! runtextmacro Spell_SetManaCost5("/", "15", "20", "25", "30", "35")
        //! runtextmacro Spell_SetOrder("/", "firebolt")
        //! runtextmacro Spell_SetRange5("/", "700.", "700.", "700.", "700.", "700.")
        //! runtextmacro Spell_SetTargets("/", "ground,enemies,structure,air")
        //! runtextmacro Spell_SetTargetType("/", "UNIT")

        call thistype.THIS_SPELL.Event.Add(Event.Create(UNIT.Abilities.Events.Effect.DUMMY_EVENT_TYPE, EventPriority.SPELLS, function thistype.Event_SpellEffect))

        //! runtextmacro Spell_Finalize("/")
    endmethod
endstruct

This is how a spell of mine looks like for example. The only thing that I need to do outside of this scope concerning it, is running the Init function to be able to set the order of initializations (although I have plans to avoid this in the future).
 
If it's map specific, keep in mind that ExecuteFunc has as much overhead as roughly 300 function calls, so you might as well just use a huge if/then/else block (I think such a block can support up to 50 elseifs unless I am getting my sources wrong).
Yeah ... but at some place of mapping, you simply can not stand that awkward feeling when using giant if blocks anymore... :/
I know that some of the systems I use could be faster when just using hardcoded elseif blocks... but then I am one of those kind thinking that code should look 'good', even if nobody sees it when playing.

It's the same with your girlfriend's genitalia. Nobody except you should see it, but you do... all your friends say she is hot, but you know her unshaved secret... =/

And yes, this was probably to most weird comparison I ever made...

I would not do this in my map either to maintain order and accessability. And if it's so private, you can collect all the registries in one function. By the way, you need to register your unit types anyway unless you require every unit to possess one or inelegantly try to run functions that do not exist.
I avoided that by simply adding a passive ability that does nothing to all units with scripts. I always try to avoid additional registration work by using object data. That way I can go sure that I won't 'forget' something when creating large amounts of content.
I try to avoid redundancy whereever I can. And I know ... macros can be used to avoid this redundancy, but then again, I like my code clean.

Does not matter and is tiny.
Just added this because of the compress names issue. You'll get to a point in coding where compress names yields less space saved than using executeFunc here and there.

Well, you cannot add behaviors to units or unit types during run-time this way. For example, when your boss steals an ability/buff.
That's quite awesome stuff, gotta agree on that. Then again, I - up until now - had no intentions of doing something like this so I think it's fine. It's almost impossible to balance such encounters anyway.

errr ... I think the topic drifted away.
I didn't want to say that all the stuff you said was wrong. It clearly isn't. But in some situations, executeFunc might simply be easier than all the trigger workaround stuff. That's all I wanted to say.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
I avoided that by simply adding a passive ability that does nothing to all units with scripts. I always try to avoid additional registration work by using object data.

Eh, object editor is slow, it requires globally stored abilities, also affects the file size, you can only do this with unit types. You have to do this manually too and it's error-prone. You might not even notice it when you delete the script reference but the object data is still there and vice-versa without testing.

but then again, I like my code clean.

Where did your girlfriend just disappear to?
 
Eh, object editor is slow, it requires globally stored abilities, also affects the file size, you can only do this with unit types. You have to do this manually too and it's error-prone. You might not even notice it when you delete the script reference but the object data is still there and vice-versa without testing.
Sure thing. It's a matter of how your preferences are when making content. I try to add as many information as possible in object data, as I need to use the object editor anyway and like to have a full "overview" about everything when checking that unit. I don't like to collect information spread around.

But everyone got its own style of programing, I agree on that. We are not talking about speed anymore, so I think that discussion has become pointless. :/
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Sure thing. It's a matter of how your preferences are when making content. I try to add as many information as possible in object data, as I need to use the object editor anyway and like to have a full "overview" about everything when checking that unit. I don't like to collect information spread around.

You can do this in script too. Only that you have the option here. I agree however that you need the object data for certain other things and therefore have split vision when working with the unpleasant object editor. Then again, this vision might be divided anyway if you want to implement non-boolean states.

As you may see in my above spell, I create object data from vJass code but it's kind of troublesome too (performance-wise) since the language is not that mighty either. Hopefully, at some time, there will be better tools that allow for a better interface between object editor stuff and triggers.

so I think that discussion has become pointless. :/

We are talking about how to develop things effectively, deciding what to use and the relevance of the topic. And better than no discussion.

@Bribe: Only 1%? What's so difficult about evaluating in comparison to other jass actions?

edit: Now I remember. TriggerClearActions does not kill the triggeractions, they only do not get called anymore when the trigger fires. But TriggerRemoveAction should be okay.
 
The only flaw with TriggerRemoveAction is that it doesn't allow the action's TriggerSleepActions to finish (will be like hitting a return statement). But you can remove them without interrupting the following actions unlike TriggerRemoveCondition how it aborts the entire evaluation queue.

Still, multiple trigger actions is not really an option because each action must wait for the previous action's thread-sleeping to finish in order to proceed, which will make precise-timing a very difficult thing to predict.

I think looping through a list of boolexprs will have the best compromise of: being able to unregister in real-time, being lighter on handles (not requiring a trigger per condition) and being barely slower (since evaluating alone uses up way more resources).
 
Ok, ran some more tests. Not even if there is just one player in the map will ForceEnumPlayers be faster than TriggerEvaluate, it is about 20-30% slower than TriggerEvaluate, so for those of us still holding on for efficient, handle-friendly alternatives we will not get them. The TriggerAddCondition/TriggerEvaluate/TriggerClearConditions thing is even slower than an ExecuteFunc so that approach can also stop.

"Filter" is quite an expensive call. Replacing it with static boolexpr's made a sizable performance jump.

So for need-for-speed situations, the fastest way to run code on-the-fly is ForForce, and by far the fastest and safest way to run code dynamically is by having conditions perpetually added to their corresponding triggers, and evaluate the triggers when necessary.

For functions that don't need to be called dozens of times per second, like occasionally delegating tasks over to another thread, ExecuteFunc shines brightly. For everything else, I recommend ForForce or trigger conditions. Gotta suck it up.

Perhaps Cohadar's new function interfaces will not generate as much garbage code as we currently get with .evaluate(), because .evaluate() is much more seamless than typing all that junk up yourself.

JASS:
scope Poop initializer Init
    globals 
        private trigger trig = CreateTrigger()
        private force f = CreateForce()
        private code c
        private boolexpr b
    endglobals
    function TestFunc takes nothing returns nothing
    endfunction
    private function Run takes boolexpr x returns nothing
        //call ForceEnumPlayers(f, b)
        call TriggerAddCondition(trig, x)
        call TriggerEvaluate(trig)
        call TriggerClearConditions(trig)
    endfunction
    private function Tester takes nothing returns nothing
        local integer i = 500
        loop
            set i = i - 1
            call Run(b)
            //call ExecuteFunc("TestFunc")
            //call TriggerExecute(trig)
            //call TriggerEvaluate(trig)
            //call ForForce(f, c)
            exitwhen i == 0
        endloop
    endfunction
    private function Init takes nothing returns nothing
        set c = function TestFunc
        set b = Filter(c)
        call TriggerAddAction(trig, c)
        call TriggerAddCondition(trig, b)
        call ForceAddPlayer(f, Player(0))
        call TimerStart(CreateTimer(), 0.03125, true, function Tester)
    endfunction
endscope
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Probably slower but for the records. How fast is

JASS:
function RunCode takes code c returns nothing
    set TRIGGER = LoadTriggerHandle(TABLE, GetHandleId(Condition(c)), 0)

    if (TRIGGER == null) then
        set TRIGGER = CreateTrigger()

        call TriggerAddCondition(TRIGGER, Condition(c))
        call SaveTriggerHandle(TABLE, GetHandleId(Condition(c)), 0, TRIGGER)
    endif

    call TriggerEvaluate(TRIGGER)
endfunction

?

Well, most occasions where I do not want to run it on the fly do require additional registrations and you would and have to cache it in a container anyway since you cannot store code directly. Then use the finished trigger to skip rebuilding it from boolexpr everytime.

The Missile struct from above has

JASS:
        Trigger action

        method SetAction takes code actionFunction returns nothing
            set this.action = Trigger.GetFromCode(actionFunction)
        endmethod

JASS:
    static method GetFromCode takes code action returns thistype
        local thistype this
        local integer actionId = Code.GetId(action)

        set this = thistype.TABLE.Integer.Get(actionId, CODE_KEY)

        if (this == HASH_TABLE.Integer.DEFAULT_VALUE) then
            call thistype.TABLE.Integer.Set(actionId, CODE_KEY, this)

            return thistype.CreateFromCode(action)
        endif

        return this
    endmethod

    method Run takes nothing returns nothing
        call TriggerEvaluate(this.actions)
    endmethod

So yeah, converting the code to the executing object in one line without the need of destroying it is quite comfortable.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
I just tested this and got different results:
ForceEnumPlayers is a lot faster then TriggerEvaluate.

JASS:
scope Benchmark initializer Init
    globals 
        private force f = CreateForce()
        private boolexpr b
        private trigger trig = CreateTrigger()
    endglobals
    
    function TestFunc takes nothing returns boolean
        return false
    endfunction
    
    function BenchmarkCode takes nothing returns nothing
        // ForceEnumPlayers. 5 instances: 36-39, 4 instances: 43-47 fps, 3 instances: 57-60 fps
        //call ForceEnumPlayers(f, b)
        
        // TriggerEvaluate. 4 instances: dropping to zero, 3 instances: 33-37 fps, 2 instances: 47-53 fps
        //call TriggerAddCondition(trig, b)
        //call TriggerEvaluate(trig)
        //call TriggerClearConditions(trig)
        
        // TriggerExecute. 5 instances: 13-15 fps, 4 instances: 33-38, 3 instances: 42-48 fps, 2 instances: 50-55 fps
        //call TriggerAddCondition(trig, b)
        //call TriggerExecute(trig)
        //call TriggerClearConditions(trig)
    endfunction
    
    // ---------------------------------------------------
    
    private function Test takes nothing returns nothing
        local integer i = 500
        loop
            set i = i - 1
            call BenchmarkCode()
            exitwhen i == 0
        endloop
    endfunction
    
    private function Init takes nothing returns nothing
        set b = Filter(function TestFunc)
        call ForceAddPlayer(f, Player(0))
        // Number of instances:
        call TimerStart(CreateTimer(), 0.03125, true, function Test)
        call TimerStart(CreateTimer(), 0.03125, true, function Test)
        call TimerStart(CreateTimer(), 0.03125, true, function Test)
        call TimerStart(CreateTimer(), 0.03125, true, function Test)
        call TimerStart(CreateTimer(), 0.03125, true, function Test)
    endfunction
endscope
 
That is not a proper testing environment.

1) TriggerAddCondition and TriggerClearConditions should not be included in the benchmark. The trigger and its condition need to be pre-built, otherwise these other two functions are ridiculously slow. The benchmark is ONLY against "TriggerEvaluate" because TriggerEvaluate is meant to be used statically (pre-built) whereas ForceEnumPlayersCounted is meant to be used dynamically (running a non-specific code).

2) It's ForceEnumPlayersCounted, not ForceEnumPlayers. And the code passed to it must return "true" otherwise there will be errors with multiple players.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
1) TriggerAddCondition and TriggerClearConditions should not be included in the benchmark. The trigger and its condition need to be pre-built, otherwise these other two functions are ridiculously slow. The benchmark is ONLY against "TriggerEvaluate" because TriggerEvaluate is meant to be used statically (pre-built) whereas ForceEnumPlayersCounted is meant to be used dynamically (running a non-specific code).
Uhm then i misinterpreted your test, im only interested in dynamic code. Anyway, imo boolexpr are the nicer way to store code in arrays then triggers. And then ForceEnum is obv. better then creating TriggerConditions everytime you want to run a boolexpr.
2) It's ForceEnumPlayersCounted, not ForceEnumPlayers. And the code passed to it must return "true" otherwise there will be errors with multiple players.
Could you specify? What exactly is the problem. As long as i have only one player in the force it should make any difference...?
 
Status
Not open for further replies.
Top