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

[vJass] ForLoopHelper

Level 14
Joined
Nov 20, 2005
Messages
1,156
vJass script for substantially increasing the speed of looping through integers (gets inlined by JassHelper).

Example use:

JASS:
function X takes nothing returns nothing
    local integer i = 0
    loop
        set i = Increment(i)
        // Do something here.
        exitwhen i>=100
    endloop
    // Something will have been done 100 times.
endfunction

JASS:
library ForLoopHelper

//*********************************************************************
//* ForLoopHelper
//* ----------
//*
//*  This library has been designed to improve the performance of for
//* loops. Whenever you would normally use "set i=i+1" in a loop,
//* replace it with "set i=Increment(i)". Likewise, "set i=i-1" can be
//* replaced with "set i=Decrement(i)". The two functions get inlined
//* resulting in a simple array read, which is 12% faster than adding
//* or subtracting 1 to/from the variable i.
//*
//*  The obvious limitation here is that the value being incremented
//* must not be smaller than 0 or larger than 8190 and it can only be
//* incremented or decremented by 1, but this should be enough to
//* cover nearly all for loops.
//*********************************************************************

    globals
        // This determines how many indexes will be initialized and affects loading time.
        // The number has to be larger than the longest for loop that uses this library.
        // This number may not be larger than 8190, since that is the maximum array size.
        private constant integer MAX_INDEX = 8190
    endglobals

//=====================================================================

    private module Initialization
        static method onInit takes nothing returns nothing
            local integer i=0
            local integer j
            loop
                exitwhen i>MAX_INDEX
                set j=i+1
                set .next[i]=j
                set .prev[j]=i
                set i=j
            endloop
            set .prev[0]=-1
        endmethod
    endmodule
    private struct Inc extends array
        static integer array next
        static integer array prev
        implement Initialization
    endstruct

//=====================================================================

    function Increment takes integer i returns integer
        debug if i<0 or i>8190 then
        debug    call BJDebugMsg("Increment error: The integer being incremented must not be less than 0 or more than "+I2S(MAX_INDEX)+".")
        debug    return i+1
        debug endif
        return Inc.next[i]
    endfunction

    function Decrement takes integer i returns integer
        debug if i<0 or i>8190 then
        debug    call BJDebugMsg("Decrement error: The integer being decremented must not be less than 0 or more than "+I2S(MAX_INDEX)+".")
        debug    return i-1
        debug endif
        return Inc.prev[i]
    endfunction

endlibrary
 
Last edited by a moderator:
Pros:
-Allows loops to be substantially faster
-Jass math is slow, so this helps
-Functions are inlined

Cons:
-Limits indices in loops to be between 0 and 8190

This is actually pretty good :]

Just one tip:
To reduce the load on the CPU, you can change "next" and "prev" to "n" and "p"
Nestharus' Short-Variable-Naming convention isn't THAT bad :D
 
Level 14
Joined
Nov 20, 2005
Messages
1,156
Pros:
-Allows loops to be substantially faster
-Jass math is slow, so this helps
-Functions are inlined

Cons:
-Limits indices in loops to be between 0 and 8190

This is actually pretty good :]

Just one tip:
To reduce the load on the CPU, you can change "next" and "prev" to "n" and "p"
Nestharus' Short-Variable-Naming convention isn't THAT bad :D

Thanks. :) The name length shouldn't matter, since you should already be using Vex's optimiser.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Thanks. :) The name length shouldn't matter, since you should already be using Vex's optimiser.

You can't use vex's optimizer since it's broken..

if you have any AI natives in the map, it'll explode.

Also
This depends on how many functions/variables/etc are in the scope. A map with a large amount of variables would actually be slower running on this rather than the +-1.

edit
Just a little thing that prob doesn't matter, but could help decrease loading time by a millisecond or too

exitwhen i>MAX_INDEX

It's faster to loop backwards and compare to 0 =)
 
Level 3
Joined
Jun 3, 2010
Messages
47
I suspect either of the following
  • WC3 uses the same size byte for all integers so comparing to zero is equally slow
  • WC3 uses a one-bit byte for zero and the speed difference is completely negligible.

EDIT Interesting, so this replaces JASS arithmetic with an array lookup, using a two way list. Very clever.

I'm wondering if even a hashtable read is faster than a math operation.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I don't know about the 0 comparaison, i would just assume as the truth what a such speedfreak as Nestharus says (i mean it's most likely he has already made such speed tests).

But one thing is obvious, backward is indeed faster.
Since arrays are dynamic is jass, it's just faster to size the array only one time (max index directly used), instead of X times (heard it's made by power of 2, but i don't see how to test it).
You probably figured it, but i guess more precisions can't be bad.

PS : Jass is so fun, an array read faster than a simple math operation.
 
Off-topic:

Someone making a working optimizer to shorten names wouldn't be too bad. That someone will probably end up being me, I'll just break off a part of Lucky Parser's optimization process and make it a standalone thing.

On-topic:

Array read speed being faster than JASS math simply makes my head spin. Someone needs to make a stress test (high-speed timer running lots of iterations) and paste it here within a map. That someone will probably end up being me as well...

Did you test this percentage with or without the optimizer's "shorten names" feature?

If you want completeness for this library, include an array read for zero: "IsZero".

JASS:
boolean array iszero
 
onInit:
    set iszero[0] = true
 
function IsZero takes integer i returns boolean
    return iszero[i]
endfunction

Increment and Decrement are pretty generic names which may conflict with other setups. IncInteger or DecInteger sound more appropriate.

Applying these suggestions and those in the previous posts (compare against 0 in the initialization, fixing the debug things) this looks plenty reasonable.
 
Is this what you mean?

JASS:
library ForLoopHelper
    private module I
        static method onInit takes nothing returns nothing
            local integer i=0
            local integer j
            loop
                exitwhen i>8190
                set j=i+1
                set thistype.n[i]=j
                set thistype.p[j]=i
                set i=j
            endloop
            set thistype.p[0]=8190
            set thistype.n[8190]=0
        endmethod
    endmodule
    
    private struct Loop extends array
        readonly static integer array n
        readonly static integer array p
        implement I
    endstruct
endlibrary
 
Nah, it's fine the way it is :p
Or:

JASS:
library Loop
    globals
        public integer array n
        public integer array p
    endglobals
    
    private module I
        private static method onInit takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i > 8191
                set p[i] = i-1
                set n[i] = i+1
                set i=n[i]
            endloop
        endmethod
    endmodule
    private struct Loop
        implement I
    endstruct
endlibrary
 
Last edited:
:p
That made me laugh a bit :ogre_hurrhurr:
I know it's best to be readonly, but.. nah :p
Anyway, third time's the charm:

JASS:
library Loop
    private module I
        private static method onInit takes nothing returns nothing
            local thistype this = 1
            loop
                exitwhen integer(this) > 8190
                set this.next = this + 1
                set this.prev = this - 1
                set this = this.next
            endloop
        endmethod
    endmodule
    struct Loop extends array
        readonly thistype next
        readonly thistype prev
        implement I
    endstruct
endlibrary
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
I've myself tested simple incrementation VS array read, and array read seems 2 times less efficient (or at least it's less efficient, because i don't know if the fps drop should be linear or not) than simple incrementation.

20 fps for array vs 40 fps for simple incrementation with this code :

JASS:
library BenchmarkTest initializer init

globals
    private constant real TIMEOUT = 0.02
    private constant boolean ARRAY_TEST = false
endglobals

globals
    integer array I
    integer array J
    integer array K
    integer array L
    integer array M
endglobals

function F takes nothing returns nothing
    local integer i = 0
    local integer j = 0
    local integer k = 0
    local integer l = 0
    local integer m = 0
    local integer x = 0
    
    loop
    
        static if ARRAY_TEST then
            set i = I[i]
            set j = J[i]
            set k = K[i]
            set l = L[i]
            set m = M[i]
        else
            set i = i+1
            set j = j+1
            set k = k+1
            set l = l+1
            set m = m+1
        endif
            
    exitwhen x == 6950
    set x = x+1
    endloop
    
    //call BJDebugMsg("limit op not reached")
endfunction

private function init takes nothing returns nothing
    local integer i = 0
    
    loop
        set I[i] = i+1
    exitwhen i == 6950
    set i = i+1
    endloop
    
    call TriggerSleepAction(0)
    set i = 0
    loop
        set J[i] = i+1
    exitwhen i == 6950
    set i = i+1
    endloop
    
    call TriggerSleepAction(0)
    set i = 0
    loop
        set K[i] = i+1
    exitwhen i == 6950
    set i = i+1
    endloop
    
    call TriggerSleepAction(0)
    set i = 0
    loop
        set L[i] = i+1
    exitwhen i == 6950
    set i = i+1
    endloop
    
    call TriggerSleepAction(0)
    set i = 0
    loop
        set M[i] = i+1
    exitwhen i == 6950
    set i = i+1
    endloop
    
    call TimerStart(CreateTimer(),TIMEOUT,true,function F)
    call BJDebugMsg("init done")
endfunction

endlibrary

EDIT : Btw, from Captain Griffen i wouldn't be suprised if it was just an evil joke for speedfreaks.
 
Ok.. I just stress tested small operations like +,-,*,/ against array reads and the small operations won.

45 fps vs 60 fps (math had yet to go down)


Math is faster than an array read

Looping through an array is faster than looping through a linked list

Myth debunked.



This is officially useless.

Don't you mean:

20100517_134555_MythBusted.gif
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Hm... I tested it by just having tons of identical lines inside of a method (avoiding loops and etc).

The math operations still hadn't gone down tho, so I stopped testing ;D.

I think you can do "textmacros loops" in cjass (at least it was one of my suggestion), maybe that's what you did, while i'm totally agree that you have a more accurate result than mine, it's also just a theoric case.
But in fact you should be shot if you do something like that without "loop and such" stuff in a real map. :p
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Looping through an array is faster than looping through a linked list
Can you please post the code that you used? Because if it is true (and I don't really doubt your tests), we can bid farewell to Timer32.

I wonder where did Jesus4Lyf come up with those benchmarks of his (it was something like 36k execs/s vs 55k execs/s for array vs doubly linked list).
 
This doesn't mean to stop using linked lists, it just means to not use
arrays instead of simple math.

JASS:
local thistype this
local integer i = stackTop - 1
loop
    exitwhen i < 0
    set this = stack[i]
    
    //Loop - actions
    
    set i = i - 1
endloop

The benchmark has definitely not proven that the above is faster than:

JASS:
local thistype this = next[0]
loop
    exitwhen this == 0
    
    //Loop - actions
    
    set this = next[this]
endloop
 
Top