Hey all,
So its a little known fact but SaveAgentHandle fails on storing null in the hashtable. This totally sucks, but as a result I decided to collect some benchmarks.
There are 9 results here, #1 is the iteration penalty, quiet frankly its result may be ignored. The other 8 results are basically testing array set (the control in the group) vs hashtable set, vs safe hashtable set*, vs dynamic array.
Safe hashtable set is testing to see if the value trying to be stored is null as hashtables can't store null handles.
I'll compare the first 4 real results against array set, and the last 4 result against their array set.
I am using different parent keys in the hashtable in case there is something with hashtable nodes being used again that might give some speed gain and make the tests unfair for the next set.
The metric of testing is doing each action 10 times in a loop that is run 500 times, collecting that result and multiplying it by 100. Then after that, each set is tried 10 times and the result is averaged.
Comparison:
Iteration penalty
[LJASS]/* Do nothing */[/ljass]
vs
When u is not null
when u is null
Approximate Results & Conclusions:
Tested on Warcraft III Version 1.24c.
Everything is measured as % of norm = u.
Iteration penalty (cost of testing):: .033
Set 1 (u != null)
Set 2 (u == null)
Comments & Personal Criticism:
Code:
I am slightly out of standard approach, as I like the ability to enter the number of iterations. 500 is what I tested with though.
If you really want I can change the trigger so that it uses Escape.
So its a little known fact but SaveAgentHandle fails on storing null in the hashtable. This totally sucks, but as a result I decided to collect some benchmarks.
There are 9 results here, #1 is the iteration penalty, quiet frankly its result may be ignored. The other 8 results are basically testing array set (the control in the group) vs hashtable set, vs safe hashtable set*, vs dynamic array.
Safe hashtable set is testing to see if the value trying to be stored is null as hashtables can't store null handles.
I'll compare the first 4 real results against array set, and the last 4 result against their array set.
I am using different parent keys in the hashtable in case there is something with hashtable nodes being used again that might give some speed gain and make the tests unfair for the next set.
The metric of testing is doing each action 10 times in a loop that is run 500 times, collecting that result and multiplying it by 100. Then after that, each set is tried 10 times and the result is averaged.
Comparison:
Iteration penalty
[LJASS]/* Do nothing */[/ljass]
vs
When u is not null
[LJASS]norm = u[/LJASS] vs [LJASS]SaveUnitHandle(ht,0,i,u)[/LJASS] vs [ljass]if(!SaveUnitHandle(ht,1,i,u)) RemoveSavedHandle(ht,1,i);[/ljass]vs [ljass]dynam = u;[/ljass]
when u is null
[LJASS]norm = u[/LJASS] vs [LJASS]RemoveSavedHandle(ht,2,i)[/LJASS] vs [ljass]if(!SaveUnitHandle(ht,3,i,u)) RemoveSavedHandle(ht,3,i);[/ljass]vs [ljass]dynam = u;[/ljass]
Approximate Results & Conclusions:
Tested on Warcraft III Version 1.24c.
Everything is measured as % of norm = u.
Iteration penalty (cost of testing):: .033
Set 1 (u != null)
- [LJASS]norm = u;[/LJASS] 100% (.270)
[*][LJASS]SaveUnitHandle(ht,0,i,u);[/ljass] 233% (.630)
[*][LJASS]if(!SaveUnitHandle(ht,1,i,u)) RemoveSavedHandle(ht,1,i);[/ljass] 242% (.655)
[*][LJASS]dynam = u [/ljass] 492% (1.329)
Set 2 (u == null)
- [LJASS]norm = u[/LJASS] 100% (.274)
[*][LJASS]RemoveSavedHandle(ht,2,i);[/ljass] 188% (.514)
[*][LJASS]if(!SaveUnitHandle(ht,1,3,u)) RemoveSavedHandle(ht,3,i);[/ljass] 418% (1.146)
[*][LJASS]dynam = u [/ljass] 486% (1.332)
Comments & Personal Criticism:
- Use an array when you can. The hashtable is about 2x slower then using a regular array.
- A hashtable is ALWAYS faster then a dynamic array. Even if you are using the safe testing way.
- The remove native is faster then using the set native.
- Unshown here, but testing if(u == null) SetUnitHandle else RemoveSavedHandle is obviously much faster, but this isn't good because you can't just treat it like a function call. (i.e. if( CreateUnit(...) == null) SetUnitHandle(...,CreateUnit(...)) is very bad).
- Conditional evaluation seems to be very cheap and fast in jass.
- The penalty isn't bad for the safe set when storing non-null values. the cost of calling both natives though is pretty high, but still better then using a dynamic array.
- The difference between trying to save something stored in a variable that is null vs something that is not null is pretty small. And would go either way in my tests.
- The iteration cost is near nothing for only 500 iterations. However if this was 5000 the cost would be quiet severe proving that Jesus4lyf was right about this testing business
Code:
JASS:
///////////////////////////////////////////////
// Native declarations for stopwatch natives //
// - Requires no modified common.j import //
///////////////////////////////////////////////
native StopWatchCreate takes nothing returns integer
native StopWatchMark takes integer stopwatch returns real
native StopWatchDestroy takes integer stopwatch returns nothing
// Why this is in Zinc
//! textmacro TestPattern takes code
TriggerSleepAction(0);
testNum+=1;
testNames[testNum] = "$code$";
//BJDebugMsg("Test Name::$code$");
sw = StopWatchCreate();
for(i = 0; i < iterations; i+=1)
{
$code$
$code$
$code$
$code$
$code$
$code$
$code$
$code$
$code$
$code$
}
results[testNum] += (StopWatchMark(sw) * 100);
StopWatchDestroy(sw);
//! endtextmacro
//! zinc
library Tester
{
type Hugearray extends unit[9000,81000];
Hugearray dynam;
hashtable ht;
unit norm[];
unit someunit;
function SafeSet(hashtable ht,integer pk,integer ck,unit ux)
{
if(ux == null) RemoveSavedHandle(ht,pk,ck); else SaveUnitHandle(ht,pk,ck,ux);
}
public function Trig_Text_Actions()
{
real results[];
string testNames[];
integer testNum;
integer sw;
integer i;
integer j;
unit u;
integer iterations = S2I(GetEventPlayerChatString());
BJDebugMsg(I2S(GetHandleId(someunit)));
for( j = 0; j < 10; j+=1)
{
testNum = -1;
//! runtextmacro TestPattern("/* do nothing */")
u = someunit;
//! runtextmacro TestPattern("norm<i> = u;")
//! runtextmacro TestPattern("SaveUnitHandle(ht,0,i,u);")
//! runtextmacro TestPattern("if(!SaveUnitHandle(ht,1,i,u)) RemoveSavedHandle(ht,1,i);")
//! runtextmacro TestPattern("dynam<i> = u;")
u = null;
//! runtextmacro TestPattern("norm<i> = u;")
//! runtextmacro TestPattern("RemoveSavedHandle(ht,2,i);")
//! runtextmacro TestPattern("if(!SaveUnitHandle(ht,3,i,u)) RemoveSavedHandle(ht,3,i);")
//! runtextmacro TestPattern("dynam<i> = u;")
}
TriggerSleepAction(0);
BJDebugMsg("u is not null");
for(i = 0; i < 9 ; i+=1)
{
if(i == 5) BJDebugMsg("u is null");
BJDebugMsg(testNames<i>);
BJDebugMsg("AVG time::" +R2S(results<i>/10));
}
}
private function onInit()
{
dynam = Hugearray.create();
ht = InitHashtable();
someunit = CreateUnit(Player(0),039;hfoo039;,0,0,0);
}
}
//! endzinc
//===========================================================================
function InitTrig_Text takes nothing returns nothing
set gg_trg_Text = CreateTrigger( )
call TriggerRegisterPlayerChatEvent( gg_trg_Text, Player(0), "", false )
call TriggerAddAction( gg_trg_Text, function Trig_Text_Actions )
endfunction
</i></i></i></i></i></i>
I am slightly out of standard approach, as I like the ability to enter the number of iterations. 500 is what I tested with though.
If you really want I can change the trigger so that it uses Escape.