HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

[Benchmarks] Tables vs. Direct Game Cache / return bug usage

05-25-2006, 12:21 PM#1
Vexorian
Is the speed difference between direct gamecache native + return bug exploitters and tables worth enough to use the gamecache natives and add non-sense to your code?

Collapse Common for both tests:

function H2I takes handle h returns integer
    return h
    return 0
endfunction

// ...

function testthread takes nothing returns nothing
 local integer j=1
 local location loc
    loop
        exitwhen (j>100)
        call SetLoc("a",I2S(j),Location(0,j*100))
        set j=j+1
    endloop
    set j=1
    loop
        exitwhen (j>100)
        set loc= GetLoc( "a",I2S(j) )
        call RemoveLocation(loc)
        set j=j+1
    endloop
endfunction

function test takes nothing returns nothing
 local integer j=1
    loop
        exitwhen (j>2500)
        call ExecuteFunc("testthread")
        set j=j+1
    endloop

endfunction



//===========================================================================
function InitTrig_bench_Copy takes nothing returns nothing
local trigger t=CreateTrigger()
set udg_G=InitGameCache("CasterSystem.vx")

        call TriggerAddAction(t, function test)
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)

endfunction

comparission:
Code
Duration
Collapse Direct native usage:
function In2Loc takes integer i returns location
    return i
    return null
endfunction


function testthread takes nothing returns nothing
 local integer j=1
 local location loc
    loop
        exitwhen (j>100)
        call StoreInteger(udg_G,"a",I2S(j),H2I(Location(0,j*100)))
        set j=j+1
    endloop
    set j=1
    loop
        exitwhen (j>100)
        set loc= In2Loc( GetStoredInteger(udg_G,"a",I2S(j) ) )
        call RemoveLocation(loc)
        set j=j+1
    endloop
endfunction
27.66 seconds
Collapse Tables:
function GetLoc takes string a , string b returns location
    return GetStoredInteger(udg_G,a,b)
    return null
endfunction

function SetHnd takes string a, string b, handle h returns nothing
    if (h==null) then
        call FlushStoredInteger(udg_G,a,b)
    else
        call StoreInteger(udg_G,a,b,H2I(h))
    endif
endfunction

function testthread takes nothing returns nothing
 local integer j=1
 local location loc
    loop
        exitwhen (j>100)
        call SetHnd("a",I2S(j),Location(0,j*100))
        set j=j+1
    endloop
    set j=1
    loop
        exitwhen (j>100)
        set loc= GetLoc( "a",I2S(j) )
        call RemoveLocation(loc)
        set j=j+1
    endloop
endfunction
29.4 seconds


29.4 / 27.66 equals 1.062 . So tables are 6.2% slower than natives+return bug

all right 200*2500 equals 500000 that's the amount of set / get operations. If we devide things we get that in one get / set combo tables take 0.00000696 more seconds than direct native+return bug usage

What to do now? That's a good question


Edit: I am using gamecache variable directly on the table function simulation, considering that either the optimizer or someone with a couple of seconds and control on the map's initialization order can get rid of functions like CSCache() and use a well initialized variable instead
05-25-2006, 01:20 PM#2
Chuckle_Brother
Proves once again that all this "its more efficient" is not necessarily true. To me, saving 0.00000696 seconds does not make up for the extra time it would take for people to understand the nonsense that the code would become when using natives.
05-25-2006, 02:20 PM#3
Vexorian
well that's as relative as it can get
05-25-2006, 02:51 PM#4
Vuen
Quote:
To me, saving 0.00000696 seconds does not make up for the extra time it would take for people to understand the nonsense that the code would become when using natives.

See now this is what I say all the time about BJ calls. Yes, many of them are very redundant, but what does it matter if you can call them a million times with negligible speed difference? I'd rather use things like UnitRemoveBuffBJ even though it's just a wrapper because when I read the code I know I'm dealing with a buff.

I treat these table calls the same way. So what if it takes an extra 7 microseconds for a table call? If tables are slowing down your map, there is something wrong with your algorithm.


While I'm here, can someone please post the speed comparisons for BJ calls? Something like using ConvertedPlayer instead of Player, or any other straight wrapper function? I'm so sick of this "BJ IS BAD" movement. It's the most irritating thing about asking for help around here.

The last time a guy wanted help speeding up his code, everyone just told him to get rid of his BJ calls when that wasn't even the problem. I was the only one who actually bothered to look at the code and fix the algorithm; BJ calls had nothing to do with it. This happens all the time on this forum, and a lot of new mapmakers get discouraged with how their code is treated here.

People need to take a more realistic standpoint on things like this.
05-25-2006, 03:36 PM#5
Vexorian
All right , the common things are still there

Benchmark:
Collapse Native:
function testthread takes nothing returns nothing
 local integer j=1
 local location loc
    loop
        exitwhen (j>500)
        call AddHeroXP(gg_unit_Hamg_0002,1,false)
        set j=j+1
    endloop
endfunction
21.1 seconds
Collapse Dumb BJ swap function:
function testthread takes nothing returns nothing
 local integer j=1
 local location loc
    loop
        exitwhen (j>500)
        call AddHeroXPSwapped(1,gg_unit_Hamg_0002,false)
        set j=j+1
    endloop
endfunction
25.34 seconds

all right 25.34 - 21.1 seconds equals 4.24 seconds . That by 1250000 is 0.000003392 seconds Of course, they don't make things slower than when you use tables.

But:

Did you notice that there is no reason to use a BJ swapped function at all? In JASS SetUnitAbilityLevel has more sense to have the unit argument before the others cause the name of the function is SetUnitAbilityLevel not SetAbilityLevelForUnit in which using the Swapped version would have more sense.

Tables do much more than just swapping the natives or renaming them, they call the CSCache() function, they handle return bug exploiting and they have auto flushing.

So tables do have an use unlike BJ swapped functions which don't have any. BJ functions won't make your code easier to read, it will make your code look retarded, they will not save you time either because you will have to write extra BJ or Swapped.

Thus their increment of execution time is really worthless.


Tables vs. BJ functions:
TablesBJ functions
Add 6e-6 secondsAdd 3e-6 seconds
Make code easier to read:
GetTableUnit("[A]","B") vs I2U(GetStoredInteger(CSCache(),"[A]","B"))
Make code harder to read :
UnitAddAbilityBJ('Aloc',u) vs UnitAddAbility(u,'Aloc')
notice that the native makes much more sense because in the name of the function Unit is before Ability.
Make code shorter (previous example)
Make code longer (previous example)
Do have an use, the SetTable#### functions have auto flushing, The GetTableUnit do return bug automatically, also encapsullate the usage of CSCache() and everything, besides of not leading to confussions, gamecache is supposed to be used to save things in hard drive and not to store things in labels.
They are useless



Finally you should not really consider the speed difference on the number of seconds they add cause that tends to make things look harmless.

Instead you should watch percentages. in this case 25.34 / 24.1 equals 1.051 so BJ are 5.1 % slower.

Is what bj functions do worth the extra 5.1% of time wasted?
05-25-2006, 03:50 PM#6
Chuckle_Brother
Oh man, I think you may have annoyed Vex, do we tremble in fear or what(I dunno if we should, its not like this is Tim. or something).

Anyway, BJs probably don't make that much of a difference, but I still do find, as vex mentioned, that the argument list tends to be more intuitive in the natives.

As for Tables, tables > all. Mainly cause of lameass shit with Attachables(attaching shit to reused handles is so evil, imagine it with a unit, you think you are done so you flush it, but you flush anything else stored on that unit....dumbass).
05-25-2006, 04:26 PM#7
Vexorian
Anyways Vuen, there are times where not even what tables do are worth the performance hit. Take a look at the caster system's movement core I did not use tables there and the reason is that direct gamecache usage ended with more framerate when seeing the missiles.
05-25-2006, 04:32 PM#8
Vuen
Quote:
Did you notice that there is no reason to use a BJ swapped function at all?

Yes, I agree that BJ wrappers are useless. You're completely missing the point of what I'm saying.

What I'm saying is that people around here don't understand how insignificant a BJ call is; they think that calling a BJ is what slows maps to a crawl, and they pass this bad advice on to others as though it's the one thing that's breaking everyone's map. They don't even care how algorithms work; they just care how many BJ functions they call. People ask for help on this forum, and instead of being helped, they get told to "clean out their BJs" when for 99.99999% of applications it makes no difference whatsoever.

My point is that everyone around here treats BJ calls as the most critical factor in the speed of an algorithm, when really it's by orders of magnitude the LEAST important part. Yes, in something computationally intensive like the caster system, BJ calls can make a difference. But for what nearly everyone brings to this forum in search of help, cleaning out BJs is about the most useless thing they can do to improve their algorithms.
05-25-2006, 04:42 PM#9
Vexorian
Quote:
My point is that everyone around here treats BJ calls as the most critical factor in the speed of an algorithm, when really it's by orders of magnitude the LEAST important part.

Well besides of the swapped ones there are other BJ functions. Some BJ functions cause memory leaks because they not set stuff to null. Some help you fall in the leak world. Some add operations that are not needed.

I wouldn't say that they are the most critical factor, but I don't remember anyone here saying so. You'd have to link to them

Fact is that getting rid of them is a kinda good optimization that is really easy to make so I would recommend doing so as the first step when entering to JASS, it is a matter of habits. They have to start by getting rid of them. It is also good for them because that lets them understand more about natives and common.j .

It is a great first step when optimizing your map, that was the first thing I done to mine after joining the JASS club and it did affect map speed overall.

Anyways this is what I myself tend to say

Recipe to make your map faster in order of priority
- Remove memory leaks
- Remove calls to blizzard.j functions that leak.
- Optimize your code more, beware of unnecesary operations, get rid of loops when you can use hash tables. Replace calls to bj functions that swap arguments.
- Use the optimizer to make your function / variable names shorter.
05-25-2006, 04:42 PM#10
Anitarf
Quote:
Originally Posted by Vexorian
Anyways Vuen, there are times where not even what tables do are worth the performance hit. Take a look at the caster system's movement core I did not use tables there and the reason is that direct gamecache usage ended with more framerate when seeing the missiles.
Yeah, I had to code one map's projectile engine using globals instead of game cache because otherwise things lagged too much.

But unless you're moving a hundred projectiles around and calculating collision for them like I did, there's no reason to drop game cache for globals.
05-25-2006, 05:11 PM#11
Vuen
Quote:
Originally Posted by Vexorian
Recipe to make your map faster in order of priority

Your "recipe" is missing the most important step, which is whether you can rewrite the algorithm to reduce unnecessary calls to very processor intensive functions.

For example, suppose you set up a custom missile system. But instead of storing the active missiles in a group, you simply request a "Units of Type Missile in Playable Map Area" to see what missiles are currently in motion. Of course you diligently clean up the group leak, but requesting such a group every, say, 0.02 seconds slows the map to a crawl. This could trivially be replaced with a cached group, which you add the missiles to when spells get cast.

Seems like an obvious speed drain, doesn't it? Well this is exactly what happened in this thread, and nobody noticed, because once the leaks were fixed everyone immediately dismissed it; they simply blamed it on BJ calls:

http://wc3campaigns.net/showthread.php?t=82336&page=5

5 pages of various people telling him to uselessly convert his triggers to JASS. Had anyone actually looked at his code, they would have immediately seen this obvious mistake; instead the reaction from the forum was "Clean your BJs, or we can't help you."

After all this mess, turns out all he needed to do was change exactly 4 lines of GUI to solve all his problems.


The most important part your recipe is missing is actually looking at whether the algorithm can be improved. I consider this to be far more important than even memory leaks. Here a simple cached unit group improved the speed of his algorithm more than fixing a million BJ calls per second would have. This is the part that people need to be aware of; going around barking at newbies to "fix their BJs" is both irritating and pointless, and it makes this forum a very bad place to get help.
05-25-2006, 05:12 PM#12
BDSM
It all comes down to personal preference. Tables effectively are just native game cache functions dressed up. Not everyone needs, or wants, that. Claiming that the natives add 'non-sense' to code, that's an opinion. I'd rather keep the 'non-sense' as performance wise it is better and in my opinion is still extremely readable.

As for using globals over game cache there is one simple reason: performance. Will everyone notice it? Of course not. But the fact remains that it is still there. And globals versus game cache isn't black versus white; you can integrate them together.
05-25-2006, 05:18 PM#13
Vexorian
natives don't add nonsense to code, return bug exploiters do.

game cache will always be structured in tables and globals will not, even if you used some thing to make globals behave in some kind of structure you would be limited and have to use at least 2 of them one for integers and one for strings, later you'll have to deal with the little 8192 array limit
05-25-2006, 05:22 PM#14
Vexorian
Quote:
Your "recipe" is missing the most important step, which is whether you can rewrite the algorithm to reduce unnecessary calls to very processor intensive functions.`

Read it again. It is third step the one about unnecesary operations. It is not the most important step though, it is after leaks, unless your algorythm is really terribly wrong it won't hit performance as much as memory leaks.


In that link the algorythm problems were obvious for example.

edit: And you didn't really fix it you just saved one of the issues but it still has like 50 problems, err, where was I back then? those guys need some kicking also that trigger is so wrong.

But after solving the mistakes there, if it stays in GUI it will still use BJ functions and still be executed each 0.02 BJ functions will still matter.


Edit: The lag there was so excessive because of wrongly coded GetUnitsOfTypeIdAll BJ function

Collapse the devil:
function GetUnitsOfTypeIdAll takes integer unitid returns group
    local group   result = CreateGroup()
    local group   g      = CreateGroup()
    local integer index

    set index = 0
    loop
        set bj_groupEnumTypeId = unitid
        call GroupClear(g)
        call GroupEnumUnitsOfPlayer(g, Player(index), filterGetUnitsOfTypeIdAll)
        call GroupAddGroup(g, result)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
    call DestroyGroup(g)

    return result
endfunction


Really, if he replaced it with something like native call GroupEnumUnitsOfType(group,UnitId2String(rawcode),null)

The algorythm would have stayed the same and that would have been a similar speed boost than the one you accomplished. Also you optimized the code by getting rid of an evil BJ function (Irony)

There are still many algorythm - related optimizations in that trigger. Wonder if that guy is still interested in optimizing it
05-25-2006, 05:31 PM#15
BDSM
As I said before, integration of globals and game cache. I use the return bug to get a integer 'key' for a unit from the game cache. Then I can access numerous globals for a multitude of stored information, at a faster speed than trying to retrieve everything from game cache. I still use game cache; just as minimal as possible.

I could use the unit's user data for the key storage as well; but personally I prefer to use this for other applications.