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

[Benchmarks] The truth is (out) there.

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
45 (stays constant regardless of how many events I register)
JASS:
globals
    unit u
    trigger t = CreateTrigger()
endglobals
function i takes nothing returns nothing
    local integer s = 500
    
    loop
        set u = CreateUnit(Player(15), 'ushd', -1000, -1000, 0)
        exitwhen 0 == s
        set s = s - 1
    endloop
    
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
    call TriggerRegisterUnitInRange(t, u, 1000, null)
endfunction

struct tester extends array
    private static method onInit takes nothing returns nothing
        call i()
    endmethod
endstruct

30-36
JASS:
globals
    group g = CreateGroup()
    unit u
    trigger t = CreateTrigger()
endglobals
function d takes nothing returns boolean
    call GroupEnumUnitsInRange(g, -1000, -1000, 1000, null)
    loop
        set u = FirstOfGroup(g)
        exitwhen null == u
        call GroupRemoveUnit(g, u)
    endloop
    return false
endfunction
function r takes nothing returns nothing
    call TriggerEvaluate(t)
endfunction
function i takes nothing returns nothing
    local integer s = 500
    local boolexpr b = Condition(function d)
    
    loop
        call CreateUnit(Player(15), 'ushd', -1000, -1000, 0)
        exitwhen 0 == s
        set s = s - 1
    endloop
    
    call TriggerAddCondition(t, Or(b,b))
    
    call TimerStart(CreateTimer(), .01, true, function r)
endfunction

struct tester extends array
    private static method onInit takes nothing returns nothing
        call i()
    endmethod
endstruct

not sure how often the UnitEventInRange fires though ; P.

edit
45
JASS:
globals
    unit u
endglobals
function i takes nothing returns nothing
    local integer s = 500
    local integer ev = 40
    
    loop
        set u = CreateUnit(Player(15), 'ushd', -1000, -1000, 0)
        if (0 < ev) then
            set ev = ev - 1
            call TriggerRegisterUnitInRange(CreateTrigger(), u, 1000, null)
        endif
        exitwhen 0 == s
        set s = s - 1
    endloop
endfunction

struct tester extends array
    private static method onInit takes nothing returns nothing
        call i()
    endmethod
endstruct

I think that UnitInRangeEvent wins hands down >.>.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
0
JASS:
globals
    group g = CreateGroup()
    unit u
    trigger t = CreateTrigger()
endglobals
function d takes nothing returns boolean
    call GroupEnumUnitsInRange(g, -1000, -1000, 1000, null)
    loop
        set u = FirstOfGroup(g)
        exitwhen null == u
        call GroupRemoveUnit(g, u)
    endloop
    return false
endfunction
function r takes nothing returns nothing
    call TriggerEvaluate(t)
endfunction
function i takes nothing returns nothing
    local integer s = 500
    local boolexpr b = Condition(function d)
    
    loop
        call CreateUnit(Player(15), 'ushd', -1000, -1000, 0)
        exitwhen 0 == s
        set s = s - 1
    endloop
    
    set b = Or(b,b) //2
    set b = Or(b,b) //4
    set b = Or(b,b) //8
    set b = Or(b,b) //16
    set b = Or(b,b) //32
    set b = Or(b,b) //64
    call TriggerAddCondition(t, b)
    
    call TimerStart(CreateTimer(), .1, true, function r)
endfunction

struct tester extends array
    private static method onInit takes nothing returns nothing
        call i()
    endmethod
endstruct

38
JASS:
globals
    unit u
endglobals
function i takes nothing returns nothing
    local integer s = 500
    local integer ev = 64
    
    loop
        set u = CreateUnit(Player(15), 'ushd', -1000, -1000, 0)
        if (0 < ev) then
            set ev = ev - 1
            call TriggerRegisterUnitInRange(CreateTrigger(), u, 1000, null)
        endif
        exitwhen 0 == s
        set s = s - 1
    endloop
endfunction

struct tester extends array
    private static method onInit takes nothing returns nothing
        call i()
    endmethod
endstruct

And I put the second one at as much of a disadvantage as I could.

First one is ok up to like 16, but it does have a massive amount of overhead compared to the second. If you can use UnitInRangeEvent, use it : O.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
As stated below FirstOfGroup loop can't work properly in some situations (i will share a test code later, if i will be in the mood of it):

http://www.hiveworkshop.com/forums/2024054-post22.html

In short, NEVER use a FirstOfGroup loop, since we don't know how groups are handled internally.

Well, more i'm thinking about it, more i think that i finally should improve and submit my unit linked list thing ...

EDIT : Nevermind, i was wrong even if there is the "ghost" unit problem (removed unit, this is nothing about the ghost ability)

http://www.hiveworkshop.com/forums/...tinrangeevent-205036-post2024255/#post2024255
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
It depends mostly of your hardware, there is not only one answer.

But the wc3 engine tends to "lag" with many units (path checking).
I've also heard that 10 units owned by 10 players is better than 100 units of 1 player (path checking/attack engine), but i've never tested myself.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Setting an array vs creating and setting a local.

Setting a global array with fully-shortened name is about 2x as fast as creating and setting a single local variable.

Without the optimizer in place, an average-name length global array is about the same speed.

I have run this test dozens of times in order to try to get the smallest error margin possible.

JASS:
globals
    integer array XXXX
endglobals
struct Tester extends array
    private static integer array myArray
    private static method proxy takes nothing returns nothing
        //! textmacro SpreadBench takes A
            /*
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            set XXXX[$A$] = $A$
            */
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            set .myArray[$A$] = $A$
            /*
            local integer a$A$ = $A$
            local integer b$A$ = $A$
            local integer c$A$ = $A$
            local integer d$A$ = $A$
            local integer e$A$ = $A$
            local integer f$A$ = $A$
            local integer g$A$ = $A$
            local integer h$A$ = $A$
            local integer i$A$ = $A$
            local integer j$A$ = $A$
            */
        //! endtextmacro
        //! runtextmacro SpreadBench("0")
        //! runtextmacro SpreadBench("1")
        //! runtextmacro SpreadBench("2")
        //! runtextmacro SpreadBench("3")
        //! runtextmacro SpreadBench("4")
        //! runtextmacro SpreadBench("5")
        //! runtextmacro SpreadBench("6")
        //! runtextmacro SpreadBench("7")
        //! runtextmacro SpreadBench("8")
        //! runtextmacro SpreadBench("9")
    endmethod
    //! textmacro Bench
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
            call .proxy()
    //! endtextmacro
    private static method repeat takes nothing returns nothing
        local integer i = 2 //Configurable.
        loop
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            //! runtextmacro Bench()
            set i = i - 1
            exitwhen i == 0
        endloop
    endmethod
    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.05, true, function thistype.repeat)
    endmethod
endstruct

Keep in mind that my computer totally sux. Intel Atom was not designed for speed. Many people will have to edit the number of times the loop iterates or shorten the timer's period. This was just the benchmark configuration that was best for my machine.

The conclusion to take away from this test is to use locals for when you reference an array 2 or more times.

That said, function calls should be more expensive than arrays. My next tests will show the difference using "GetTriggerUnit()" X times than to declare, set, reference and null a local to it.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
This is a bad test, because no matter which type of variable it is (except maybe an array) a variable read/write should be ligthning fast comparing to a function call, same for the loop.
I mean you are adding revelant overheads which are making the test innacurate.

cJass is the way to go here (or any other copy/paste).

Also, a local variable which is only defined one time and no more read/written is pointless.
I mean, it can't exist in a real code, or you made something wrong.

Plus, the speed difference between a global and a local depends how many times they are used in the same thread (heavy usage or not).

Finally, locals should be one-character length while globals shoud be two-characters.

Btw, i hope you are not making a direct relation between fps and speed, because fps drop is not supposed to be linear.
The best way is to get the same fps with the "same code" (ofc the tested codes are different, i mean the same number of operations) by changing the period of the timer.
Also, you should share the fps you got, not directly the difference scale between code A and code B (but as you understand, in fact you should share the different timer period you had for the same fps X).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That makes sense. There are also other flaws weighing against arrays, such as me using direct constants 0-9 instead of using a variable in their place.

I was trying to weigh things against local variables, not vice-versa. And I was quite surprised how efficient it is to create one.

The global is four characters because I wanted to test it worst case scenario (where there are tons of globals clogging up the namespace).
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
With a good optimizer and a "real" map 2 characters length is more than enough.
Even with an heavy scripted one, you are not likely to need more than 3 characters.
4 characters will come with a bad script optimizer or/and with a really fool coder.

http://www.hiveworkshop.com/forums/2016447-post11.html

Also, there is a reason for the one character length.
Just because of the variable name re-usability, you will be able to use the same character many more times with a local than a global.
And if you are a speedfreak you could even still use some of the unused letter, since in a real map the function which will have the most locals (functions arguments included) will hardly use more than 52 locals, probably much less than 20 in fact.

Oh ,and to reduce the overhead of the timer callback by itself, you have to make the code running as heavy as possible (nearly reach the limit op but not).
NEVER try to decrease the tested code, just change the timer period.

Hence the reason of my cJass template in the first post, to make such valid tests more friendly.
But i will improve it soon.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
The only time this stuff is going to matter is in code that is called massively, like projectile systems or timer systems.


The biggest overhead I had in the timer system I wrote was actually global arrays. There were specifically 6 lines of global array reads/writes that caused the performance hit.


Also keep in mind that most of the overhead from calling functions is the arguments. Compare a function call with 6 arguments to a function call with none and you will see a major speed difference. When Starcraft 2 beta first came out, one person benched JASS stuff and Galaxy stuff and this is where I saw the stats for a function call with 6 arguments =).


Code readability, maintainability, and modularity is more important in most cases, but if you have a timer running 32x a second or some piece of code that is executing at obscene rates, you'll want to squeeze out every last ounce of performance you can =).
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
The only time this stuff is going to matter is in code that is called massively, like projectile systems or timer systems.

Ofc, i think we all have this in mind, or at least i hope...


The biggest overhead I had in the timer system I wrote was actually global arrays. There were specifically 6 lines of global array reads/writes that caused the performance hit.

Even with short names ?


Also keep in mind that most of the overhead from calling functions is the arguments. Compare a function call with 6 arguments to a function call with none and you will see a major speed difference. When Starcraft 2 beta first came out, one person benched JASS stuff and Galaxy stuff and this is where I saw the stats for a function call with 6 arguments =).

That is one of my possible test, i mean a function with X arguments against a function with 0 arguments and X locals (and X temp global variables for the values arguments).
Intuitively i would say the 0 arguments is going to win, but i'm not that sure.


Code readability, maintainability, and modularity is more important in most cases, but if you have a timer running 32x a second or some piece of code that is executing at obscene rates, you'll want to squeeze out every last ounce of performance you can =).

Glad to hear it from you.
Now, what about use your own advices ? :p

I means it's up to a script optimizer, a coder/user is not an optimizer, he is a human ...
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,192
The problem with JASS variables is that JASS itself is interpreted directly from string fragments. This means that variables are actually hashtable lookups of some kind.

To process a variable name the JASS interpreter needs to itterate through the entire string atleast 3 times. No result caching is also present so "set a = a + 1" will look up a atleast twice.

This contrasts a proper language like C where a = a + 1 should be optimized to one ore 2 instructions (singular instructions). In Java it also optimizes to a one or two virtual machine instructions which are much faster to interprete.
 
Status
Not open for further replies.
Top