Jump to content
Bl44a

Script performance

Recommended Posts

I will do some testing next week with the built in timers, but I wanted to see if someone with more detailed knowledge of the functions had input, or if anyone had a better solution they could propose.

 

So a summary of what I am doing is; cycling through players (80 players max); checking their distance from client running the script; checking their side; checking their conscious state.

My initial method which I am looking to optimize does the following:

 

{

if _x distance player < 1000 then

if side _x == side player then

if !unconscious then

<all conditions met, do something>

}   

forEach playableUnit

 

 

 

My idea to improve the script is to do something like this:

 

 

{

if side _x == side player then

if !unconscious then

<all conditions met, do something>

}

forEach (player nearEntities 1000)

 

 

My initial assumption without testing or doing a proof is that for a low number of players, the first method would be better, but as the number of players increases, the second would be quicker.

 

Is there a much better solution that I am missing?

 

Thanks.

Share this post


Link to post
Share on other sites

Your heaviest evaluation will be the distance, but the real question is how often this is evaluated?

Share this post


Link to post
Share on other sites

Your heaviest evaluation will be the distance, but the real question is how often this is evaluated?

 

Right, which is why my intuition is that for a large number of players, the first method (checking the distance of each player) is not good.

This is added to a missioneventhandler (draw3d), so each frame.

 

now that I think about, I could just rearrange my conditionals.. 

I could do:

if on team then

if distance < 1000 then

if !incapacitated

 

that way the beefy distance check will only run on players on the same team. Which is at most n/2.

 

In this case it might be better than nearentities. I'm not at my gaming PC so I can't go look at the function in functionviewer do any testing.

That's why I was curious if anyone had an idea of how nearentities specifically worked. For all I know it could get every entity on the map, check its distance, and then return an array of only those entities that are closer than the provided distance... In which case it would run horribly. This is an exaggeration and likely not the case but just an example. 

Share this post


Link to post
Share on other sites

As far as I remember sqf will evaluate all conditions no matter the order or outcome of one conditional compared to another. You will need to explicitly evaluate lazily:

if (cond1 && {cond2} && {cond3}) then {stuff};
Where if cond1 is false, it implictly falsifies the entire condition, whereas your original would evaluate all of them anyhow, but anyways, seems you understand that concept, just wanted to say sqf doesn't do that inherently.

And never mind to it all, your're nesting the if statements, same damn thing as what I just put lol, long day.

As far as nearEntites goes, I don't know what's under the hood there, but it's safe to assume it would probably iterate through all entities (AI or players) not just players. So I would recommend iterating through "allPlayers" command, doing the distance check last, and the other conditions in whatever order.

And for another optimization that could help some, use count instead of forEach, no difference between the two other than forEach provides an indexing variable that increments for each element iterated through, which you aren't using.

I would recommend coding it on the basis that the number of players could inherently be infinite, because when your're dealing with only a handful the performance requirement would be negilgible.

Again, small but could help, the side of the player is unlikely to change, so instead of re-evaling every iteration, save that value outside the loop.

Share this post


Link to post
Share on other sites

What about something like this?

{
//Your if stuff
false;
}
count ({player distance _x < 1000} select playableUnits)

Edit: Fixed example, thanks to jshock

Share this post


Link to post
Share on other sites

What about something like this?

{
//Your if stuff
false;
}
count ({player distance _x } count playableUnits)

Count returns a number not an array for use, did you mean select?

Share this post


Link to post
Share on other sites

No, he's good, but it's debatable.  If you're needing to jam in a bool return to satisfy the bool/nothing return of count, then you could use forEach without additional line of (albeit simple) code per iteration.

 

Nobody's really bothered posting up some relative timings from their PC and a lot of people on this forum say things like "works like a charm" for everything, so without testing code personally, I wouldn't believe anyone on this forum.

  • Like 1

Share this post


Link to post
Share on other sites

 

And for another optimization that could help some, use count instead of forEach, no difference between the two other than forEach provides an indexing variable that increments for each element iterated through, which you aren't using.

 

 

ahh, I never looked into count. I didn't know it was an alternative to forEach (minus the indexing variable).

 

Thanks, for the input everyone

Share this post


Link to post
Share on other sites

Testing code performance can be a fun thing to do if you're in the single player editor.

The debug console basically has everything you need for it, monitoring variables, an "Execute" input and the dedicated code performance button.

Usually I'll test performance on the VR map since there's nothing much going on that could influence script performance.

My rig is a 4770k at 4.5 and a GTX 770 4gb classified from evga.

 

I used this to spawn and equally distribute 79 other units:

for "_i" from 1 to 79 do {

    _pos = [random 10000,random 10000,0];
    _unit = createGroup side player createUnit [typeOf player,_pos,[],0,"NONE"];

};

Now we have 80 units on the map, including the player unit.

The side of all units stays west to provide comparable results.

 

Using your first approach:

{

    if (_x distance player < 1000) then {

        if (side _x == side player) then {

            if !(lifeState _x == "INCAPACITATED") then {

                true

            }
        }
    }

} forEach allUnits;

Runs for 0.179888 ms average.

That's already great, considering one frame at 60 fps takes ~16.6667ms to run.

 

I replaced "==" with "isEqualTo" and tried to alterate the order of checks.

 

Side check first:

{

    if (side _x isEqualTo side player) then {

        if (_x distance player < 1000) then {

            if !(lifeState _x isEqualTo "INCAPACITATED") then {

                true

            }
        }
    }

} forEach allUnits;

0.287687 ms. Ouch.

That's worse than before, hence lifeState check, then distance, then side:

{

    if !(lifeState _x isEqualTo "INCAPACITATED") then {

        if (_x distance player < 1000) then {

            if (side _x isEqualTo side player) then {

                true

            }
        }
    }

} forEach allUnits;

0.271813 ms

A bit better, still worse than the original attempt.

 

Last try, first distance check, then lifeState, then side:

{

    if (_x distance player < 1000) then {

        if !(lifeState _x isEqualTo "INCAPACITATED") then {

            if (side _x isEqualTo side player) then {

                true

            }
        }
    }

} forEach allUnits;

0.179953 ms.

And there we are.

Seems that the distance check at first safes the most performance, since most units would be further away from the player than 1000m.

 

 

 

 

 

 

This brings the question how these 3 versions will perform if ALL units are within 1000m of the player.

Using this to spawn 79 units within 1000m:

for "_i" from 1 to 79 do {
 
    _unit = createGroup side player createUnit [typeOf player,getposatl player,[],1000,"NONE"];
 
};

Now back to the first example above, distance, side, lifeState:

{
 
    if (_x distance player < 1000) then {
 
        if (side _x == side player) then {
 
            if !(lifeState _x == "INCAPACITATED") then {
 
                true
 
            }
        }
    }
 
} forEach allUnits;

0.437445 ms.

 

 

Second example from above, side, distance, lifeState:

{
 
    if (side _x isEqualTo side player) then {
 
        if (_x distance player < 1000) then {
 
            if !(lifeState _x isEqualTo "INCAPACITATED") then {
 
                true
 
            }
        }
    }
 
} forEach allUnits;

0.409836 ms, a tad better than the first example.

 

 

Third example, distance, lifeState, side:

{
 
    if (_x distance player < 1000) then {
 
        if !(lifeState _x isEqualTo "INCAPACITATED") then {
 
            if (side _x isEqualTo side player) then {
 
                true
 
            }
        }
    }
 
} forEach allUnits;

0.403714 ms.

 

 

Coming to the conclusion that depending on the type of mission it's best to set the first condition to something that would return false for most units, so the forEach loop can continue and check the next unit.

 

A mission that has lots of players spread out all over the map (probably most likely for wasteland or similar kind of missions) benefits most from checking for distance first.

On missions with players distributed amongst three sides (west, east, independent) the biggest benefit would be doing the sidecheck first, then check for the distance.

 

All in all, the best performant snippet eats up roughly 1% runtime of a frame, the worst eats 2.7%.

No snippet comes even close to 3ms runtime, so it's safe to say using this on every frame should give no issues, unless there's other more heavy stuff happening.

 

Cheers

  • Like 1

Share this post


Link to post
Share on other sites
{
 
        if (side _x == side player) then {
 
            if !(lifeState _x == "INCAPACITATED") then {
 
                true
 
            };
        };
false;
}
count (allUnits select {player distance _x < 1000 })

I used the code above to spawn 79 units and here's the result:

 

0.175654 ms

Share this post


Link to post
Share on other sites

Hi! My study on this + a question.

 

Of course order of questions is important, you have to put them in order to ask first the one which will have more chances of having a NO. But in dynamic environments thats very unpredictable.

 

I usually aim for some balance with the commands you are using, for example if the first question is using findEmptyPosition probably you should leave it for the last.

 

First easy ones that require a quick check for the engine, such as alive, isPlayer, later math calculations, like distance, later checks on strings or arrays, like in array, or lifeState and later the most heavy demand like findEmptyPosition.

 

Ok, now comes my question. I am supposing array select {whatever} is better tan iterating  asking through the array.

 

In the example from above:

 

_side = side player;// so we dont have to check the side of the player everytime.

{dostuff} forEach (playableUnits select {(_x distance player < 1000) and (side _x == _side) and (!lifeState _x != "INCAPACITATED")});

 

Is not that better than checking the conditions "by yourself"?

Share this post


Link to post
Share on other sites

Only one way to test there barbolani :thumb:. Optimizations threads are always exciting to me, learning new stuff and all, only reason I'm not throwing up timings is it's 4am and I'm on my phone trying to beat insomnia (at least to some extent).

I would like to see the timings on something like barbolani posted but more like the following, just to fully test out some different script commands and possibly some future optimizations (doubtful on the following, just want to see the results :p):

(playableUnits select {allConditions}) apply {_x call fnc_doStuff};
Will test this and others after college shit tomorrow :).
  • Like 1

Share this post


Link to post
Share on other sites

I would do it but I am on the office doing nothing :)

 

All those things come to my head during coding, but in the end I am messed up with some bug and allways say "ok, will make some tests as soon as I solve this" :)

Share this post


Link to post
Share on other sites

_side = side player;// so we dont have to check the side of the player everytime.

{dostuff} forEach (playableUnits select {(_x distance player < 1000) and (side _x == _side) and (!lifeState _x != "INCAPACITATED")});

 

Is not that better than checking the conditions "by yourself"?

Having all other things being equal, it should not be better.

At first, select creates (returns) an array which is not there in initial variant. Here we have some slight memory and execution overhead.

At second, additional iteration loop is having place because of forEach; in case of apply there will also be overhead caused by calls and storing their results back into array.

At third, there will be no gains in terms of reducing initial operations count: no new corners to be cut, etc.  select is still O(N) where N is number of elements in input (playableUnits).

Having O(N) both ways, what matters is amount of operations performed on every iteration, e.g. you can win a (very tiny) bit in some places: like not creating _forEachIndex in select; and lose using extra calls, and extra memory.

 

Consider measuring for different orders of magnitude of input's size to have a full and clear picture (esp. if not sure algorithm complexity is linear).

Share this post


Link to post
Share on other sites

Having all other things being equal, it should not be better.

At first, select creates (returns) an array which is not there in initial variant. Here we have some slight memory and execution overhead.

At second, additional iteration loop is having place because of forEach; in case of apply there will also be overhead caused by calls and storing their results back into array.

At third, there will be no gains in terms of reducing initial operations count: no new corners to be cut, etc.  select is still O(N) where N is number of elements in input (playableUnits).

Having O(N) both ways, what matters is amount of operations performed on every iteration, e.g. you can win a (very tiny) bit in some places: like not creating _forEachIndex in select; and lose using extra calls, and extra memory.

 

Consider measuring for different orders of magnitude of input's size to have a full and clear picture (esp. if not sure algorithm complexity is linear).

This I do understand, I just wanted to see what we could get away with in sqf, cause I know with some things I've been surprised ;).

Share this post


Link to post
Share on other sites

So, if I understood well (don't think so), doing this:

 

_side = side player;// so we dont have to check the side of the player everytime.

{dostuff} forEach (playableUnits select {(_x distance player < 1000) and (side _x == _side) and (!lifeState _x != "INCAPACITATED")});

 

Is almost the same tan doing this:

 

_side = sidePlayer;

_arr = [];

{

if (condition) then {if (condition) then {if (condition) then {_arr pushBack _x}}};

} forEach playableUnits;

 

{doStuff} forEach _arr;

 

Correct?

Share this post


Link to post
Share on other sites

What about something like this?

{
//Your if stuff
false;
}
count ({player distance _x < 1000} select playableUnits)

Edit: Fixed example, thanks to jshock

 

Why is there a false at the end of the count loop?

Share this post


Link to post
Share on other sites

Why is there a false at the end of the count loop?

Because count expects bool as return, would throw an error otherwise.

 

Cheers

  • Like 1

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×