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

[General] JASS Efficiency Question

Status
Not open for further replies.
Level 11
Joined
Aug 6, 2009
Messages
697
If I were to convert all of my GUI code to custom script, then go through it all and replace all the BJs with natives, what would be the efficiency gain?
I believe that there would be a gain, but is it worth it?
Assume that there is a map with 50k lines of GUI code(when converted to JASS).
Please tell me what you think the efficiency gain would be, percentage wise, assuming anything above one percent.
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
If I were to convert all of my GUI code to custom script, then go through it all and replace all the BJs with natives, what would be the efficiency gain?
Hardly noticeable I would guess.
It's only important for actions that run often (periodic trigger + unit group/loop).
If you want to have more efficient triggers, you should definitely use JASS instead of GUI, but you also need to use the advantages of JASS. That requires a lot of work though, because you will have to change quite a lot.
If you only want to avoid BJs then it's not worth it.

Let's assume you have a trigger that has 10 actions in GUI. Converting it will result in 10 BJs. If these are BJs, that only call one native, you would have only 50% of the function calls.
I would assume that a simple function call of a BJ, that only calls a native is a lot faster than the native itself.
So if you have KillUnitBJ and KillUnit (I don't know if KillUnitBJ exists, but that's just to explain):
KillUnit: takes a certain time: T1
KillUnitBj: takes the time of KillUnit plus time to call KillUnit: T1 + T2
In this case T1 >> T2, because for killing a unit the game has to do a lot more things than for calling a function
So KillUnit is by no means twice as fast as KillUnitBJ just because it's half the amount of function calls.
Another thing to keep in mind is, that a lot of BJs call multiple natives, so the realtive time the BJ takes longer than all the natives is even lower.

Sections that run very often are worth it to be changed, but apart from that I doubt it's necessary. In general you should rather try to find more efficient algorithms to solve your problems.
For example avoiding "count units in unit group" by keeping track of the number of units in a group.
 
What Jampion says is good.

I've seen also one critical aspect in a map once, that made the game unplayable:

There was a trigger running each 0.02 seconds picking all units on map of type "x" and making check if a hero is in range of one of them. A unit group was created each 0.02 seconds for all units on map, and then a group enumeration (unit group loop) has run.

^This made the map unplayable and spiky, because the map was quite big.

What completly solved the problem was to put all relevant units into a unit[array] variable and to loop always through this unit array, also each 0.02 seconds, instead. So no group enum was needed at all, and the spikes were gone.

Keep in mind that looping through integers is a hell lot of faster than enumerating custom objects and looping though them.
Also in general-- group, and other object creations should be kept at a minimum, especially if it a trigger/functions runs very much.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
Biggest optimizations are reducing the complexity of operations, eg an O(n^2) algorithm to O(nlog2(n)).

After that it becomes micro optimization. This only ever makes a difference with performance critical code. A missile system handling hundreds of missiles would benefit from micro optimization however a quest system which events only fire a few times in total would not.
 
Biggest optimizations are reducing the complexity of operations, eg an O(n^2) algorithm to O(nlog2(n)).

After that it becomes micro optimization. This only ever makes a difference with performance critical code.
Of course other things can matter, as well, like using different approaches, as shown above; the only thing that matters for the user is that wanted semantic is met without running unwanted semantic. Operations that are used badly, wrong, or redundant do not directly matter him as long as it does not heavily affect the game in a bad way. Holding code complexity very low is a very good thing, but does also only help as long much data is handled -- if I make a loop through an array of 2, or make a hash lookup, I doubt that we will benefit too much from the O(n). Also comes by, the amount of executions that is done.
Just wanted to state that we should not simply say something like mostly code complexity matters, because it does not even necessarily have something to do with time complexity. Sometimes we just do much other useless crap (like using groups when not needed, not recycling objects in case we use massive data, periodic checks when we maybe can use internal one-shot events, massive code redundancy), that should be also cared about definitly, and sometimes it can do more than mainly focusing on code complexity.
 
Last edited:
I'd say that if the amount of function calls in a BJ function is at least 3, then it would be preferable to just inline the functions using the natives which it calls. If it uses more than 4 function calls or exceeds 4 lines, then you should let it be. For example:

JASS:
function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
    local integer index

    set index = 0
    loop
        call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
endfunction

(...)

function SomeOtherFunc takes nothing returns boolean
    return false
endfunction

// Redundant function, but a function which will illustrate my point
// TriggerSaveAndAddCondition is for exemplary purposes only. It is not encouraged for practical coding.
function TriggerSaveAndAddCondition takes trigger trig, boolexpr bool returns integer
    local integer id = <unique_index> + 1
    set someTrig[id] = trig
    set someBoolxpr[id] = bool

    call DoNothing()
    call DoNothing()
    call DoNothing()
    call TriggerAddCondition(trig, Filter(function SomeOtherFunc))

    return id
endfunction

No need to inline the TriggerRegisterAnyUnitEventBJ and TriggerSaveAndAddCondition above into this {...}

JASS:
function InitTrig_SomeTrig_Inlined takes nothing returns nothing
    local integer index
    local integer id
    local trigger trig = CreateTrigger()

    set index = 0
    loop
        call TriggerRegisterPlayerUnitEvent(trig, Player(index), <insert_event>, null)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop

    set id = <unique_index> + 1
    set someTrig[id]  = trig
    set someBoolxpr[id] = Filter(function SomeOtherFunc)

    call DoNothing()
    call DoNothing()
    call DoNothing()
    call TriggerAddCondition(trig, Filter(function SomeOtherFunc))
endfunction

{...} When you can focus on something else like this.

JASS:
function InitTrig_SomeTrig_LessText takes nothing returns nothing
    local trigger trig = CreateTrigger()
    local integer id
    call TriggerRegisterAnyUnitEventBJ(trig, <insert_event>)
    call TriggerSaveAndAddCondition(trig, Filter(function SomeOtherFunc))
    set trig = null
endfunction
 
Status
Not open for further replies.
Top