Code Optimisation: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
(Undo revision 89233 by Waffle SS. (talk) reverted strange edit)
m (Fix minimenu)
 
(155 intermediate revisions by 16 users not shown)
Line 1: Line 1:
==  Make it work. ==
{{TOC|side|0.9}}
{{Feature|informative|This page is about [[Code Optimisation]]. For ''conception'' optimisation, see [[Mission Optimisation]].}}


''"Premature optimization is the root of all evil."<br />Donald Knuth''
This article will try to be a general guide about improving your code '''and''' its performance.
* The first part ([[#Rules|Rules]]) will focus on having a clean, readable and maintainable code.
* The second part ([[#Code optimisation|Code optimisation]]) is about '''improving performance''', sometimes trading it against code readability.
* The third part ([[#Equivalent commands performance|Equivalent commands performance]]) mentions commands that in appearance have identical effects but may differ in terms of performance according to the use you may have of them.
* The fourth part ([[#Conversion from earlier versions|Conversion from earlier versions]]) is a hopefully helpful, short guide about useful new commands or syntaxes to replace the old ways.


No need to worry about making it work at light speed if it doesn't even do what it is supposed to. Focus on getting a working product first.


== Make it fast. ==
== Rules ==


Optimisation is everything when running lots of instances, with low delays. However, there is such thing as premature optimisation. Also, avoid excessive cleverness.
In the domain of development, any rule is a rule of thumb. If a rule states for example that it is ''better'' that a line of code doesn't go over 80 characters, it doesn't mean that any line '''''must not''''' go over 80 characters; sometimes, the situation needs it. If you have a good structure, '''do not''' change your code to enforce a single arbitrary rule. If you break many of them, you may have to change something. Again, this is according to your judgement.


''"Excessive cleverness is doing something in a really clever way when actually you could have done it in a much more straightforward but slightly less optimal manner.  You've probably seen examples of people who construct amazing chains of macros (in C) or bizarre overloading patterns (in C++) which work fine but which you look at an go "wtf"?  EC is a variation of premature-optimisation.  It's also an act of hubris - programmers doing things because they want to show how clever they are rather than getting the job done." - sbsmac
With that being said, here are the three basic rules to get yourself in the clear:
''


====Written it twice? Put it in a function====
# {{Link|#Make It Work|Make it work}}
Pre-compilation by the game engine can save up 20x the amount of time processing, even if the initial time is slightly lengthened. If you've written it twice, or if there is a kind of loop consistently being compiled (perhaps a script run by execVM), make it into a function (FUNCVAR =compile preprocessfilelinenumbers "filename.sqf")
# {{Link|#Make It Readable|Make it readable}}
# {{Link|#Optimise Then|Optimise then}}


==== Preprocessfilelinenumbers ====
=== Make It Work ===
The [[preprocessFileLineNumbers]] command remembers what it has done, so loading a file once will load it into memory, therefore if wanted to refrain from using global variables for example, but wanted a function precompiled, but not saved, you could simply use:
<code><nowiki>call compile preprocessfilelinenumbers "file"</nowiki></code>


Remembering the only loss of performance will be the [[compile]] time of the string returned and then the [[call]] of the code itself.
{{Feature|quote|Premature optimization is the root of all evil.|{{Link|https://en.wikipedia.org/wiki/Donald_Knuth|Donald Knuth}}}}
Your first goal when coding is to make your code do what you want it does. A good way to reach this objective is to read and getting inspired by other people's code. If you understand it by reading it once, it is probably a good source of inspiration.


====Length====
* When starting from scratch if you know what you want but miss the specific steps to get to your point, it is a good practice to write down in your native language what you want to do. E.g ''Get [[allUnits|all the units]] [[distance|near]] [[locationPosition|the city]], and [[forEach|for each]] [[side|west]] soldier in them, [[setDamage|add 30% damage]]''.
If any script or function is longer than around 200-300 lines, then perhaps (not true in all cases by all means) you may need to rethink the structure of the script itself, and whether it is all within scope of the functionality required, and if you could do something cleaner, faster and better.
* Use [[Arma_3_Startup_Parameters#Developer_Options|-showScriptErrors]] startup parameter and '''make sure your code doesn't throw errors.''' Not only will your code run slower but it may also ''not work at all''. Be sure to read the error, isolate the issue and sort it out [[:Category:Scripting Commands|thanks to this Wiki]].
* Read your [[Crash_Files|Arma RPT]] (report) to read more details about the error that happened in your code.


====Fewer statements => faster code====
=== Make It Readable ===
This may sound too obvious, but... optimise the code by removing redundant statements. The following code examples do the same thing, but the latter is 1.5 times faster:


<code>_arr = [1,2];
Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:
_one = _arr [[select]] 0;
_two = _arr [[select]] 1;
_three = _one + _two;
</code>


<code>_arr = [1,2];
* While [[SQF Syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. Variables like '''_u''' instead of '''_uniform''' should not be present.
_three  = (_arr [[select]] 0) + (_arr [[select]] 1);
* ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
</code>
* Indentation is important for the human mind, and space is too. Space is free, use it.
* Same goes for line return; it helps to see a code block wrapping multiple common instructions instead of having to guess where it starts and stops.
* Do you see the same code multiple times, only with different parameters? Now is the time to write a function!
* If you have a lot of [[if]]..[[else]], you may want to look at a [[switch]] condition, or again break your code in smaller functions.
* Is your function code far too long? Break it in understandable-sized bites for your own sanity.
* Finally, camel-casing (namingLikeThis) your variables will naturally make the code more readable, especially for long names.


====Conditions====
{{Feature|informative|See '''[[Code Best Practices]]''' for more information.}}


<code><nowiki>if (_group knowsAbout vehicle _object > 0 && alive _object && canMove _object && count magazines _object > 0) then {
See the following code:
//custom code
_w=[]; {_w pushbackunique primaryweapon _x} foreach((allunits+alldeadmen) select{_x call BIS_fnc_objectside==east});
};</nowiki></code>


You may expect the engine to stop reading the condition after the group has no knowledge about the object but that's false.
The same example is far more readable with proper spacing, good variable names and intermediate results:
The engine will continue evaluating the condition until the end even if any of the previous conditions evaluated false.
_weaponNames = [];
_allUnitsAliveAndDead = allUnits + allDeadMen;
_allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east };
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;
<!--


<code><nowiki>if (_group knowsAbout vehicle _object > 0) then {
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
      if (alive _object && canMove _object && count magazines _object > 0) then {
            //custom code
      };
};</nowiki></code>


Now the engine will only continue reading the condition after the group has some knowledge about the object. Alternatively you can use lazy evaluation syntax. If normal evaluation syntax is (bool1 .. bool2 .. bool3 .. ...), lazy evaluation syntax is (bool1 .. {bool2} .. {bool3} .. ...). Now let's look at the above example using lazy evaluation:
-->


<code><nowiki>if (_group knowsAbout _vehicle object > 0 && {alive _object} && {canMove _object} && {count magazines _object > 0}) then {
==== Constants ====
            //custom code
Using a hard-coded constant more than once? Use preprocessor directives rather than storing it in memory or cluttering your code with numbers. Such as:
};</nowiki></code>
<sqf>
a = _x + 1.053;
b = _y + 1.053;
</sqf>
And
<sqf>
_buffer = 1.053;
a = _x + _buffer;
b = _y + _buffer;
</sqf>
Becomes
<sqf>
#define BUFFER 1.053 // note: no semicolon
_a = _x + BUFFER;
_b = _y + BUFFER;
</sqf>
{{Feature|informative|Using the <sqf inline>#define</sqf> macro only works within the current [[SQF Syntax|SQF]] ''file''. Such definition will not propagate anywhere else.}}
'''Global''' "constants" can be defined ''via'' a [[Description.ext]] declaration, though, and accessed using [[getMissionConfigValue]] command:
 
Declaration in [[Description.ext]]:
<syntaxhighlight lang="cpp">
var1 = 123;
var2 = "123";
var3[] = { 1, 2, 3 };
rand = __EVAL(random 999);
</syntaxhighlight>
 
Usage in code:
<sqf>
hint str getMissionConfigValue "var1"; // 123 // 0.0007 ms
hint str getMissionConfigValue "var2"; // "123" // 0.0008 ms
hint str getMissionConfigValue "var3"; // [1,2,3] // 0.0017 ms
hint str getMissionConfigValue "rand"; // constant random, for example 935.038 // 0.0007 ms
</sqf>


Using lazy evaluation is not always the best way as it could speed up the code as well as slow it down, depending on the current condition being evaluated:
The [[getMissionConfigValue]] command searching [[Description.ext]] from top to bottom,
it is better for a matter of performance to put all your definitions at the top of the file.


<code>['[[true]] || {[[false]]} || {[[false]]}'] [[call]] [[BIS_fnc_codePerformance]]; //fastest
=== Optimise Then ===
['[[true]] || [[false]] || [[false]]'] [[call]] [[BIS_fnc_codePerformance]]; //normal
['[[false]] || [[false]] || [[false]]'] [[call]] [[BIS_fnc_codePerformance]]; //same as above
['[[false]] || {[[false]]} || {[[false]]}'] [[call]] [[BIS_fnc_codePerformance]]; //slowest
</code>


== Make it pretty. ==
Once you know what is what, you can understand your code better.
* Use [[Variables#Scopes|private variables]] instead of global variables (preceded by an underscore) as much as possible.
* You were iterating multiple times on the same array?
** You should be able to spot your issue now.
* Are you using [[execVM]] on the same file, many times?
** Store your function in memory to avoid file reading every call ( e.g <sqf inline>_myFunction = compile preprocessFileLineNumbers "myFile.sqf";</sqf>).
* Is your variable name far too long?
** Find a smaller name, according to the variable scope;<!--
-->e.g:<sqf>{ _opforUnitUniform = uniform _x; systemChat _opforUnitUniform; } forEach _allOpforUnits;</sqf><!--
-->becomes<sqf>{ _uniform = uniform _x; systemChat _uniform; } forEach _allOpforUnits;</sqf>


Documentation, readability, and all that jazz. Clean code is good code.


====If Else If Else If Else ...====
== Code Optimisation ==
If you can't escape this using a [[switch]] control structure, then try and rethink the functionality. Especially if only one option is needed to match.


On the other hand [[switch]] is slower than [[if]] [[then]] [[else]]. To keep tidiness of the [[switch]] and speed of [[if]], use [[if]] [[exitWith]] combined with [[call]]:
{{Feature|important|
<code>[[call]] {
'''Please note:''' Tests and benchmarks were done with the latest {{arma3}} version at the time {{GVI|arma3|1.82}} with '''Tank DLC'''. Game engine performance may have changed since.<br>Benchmark result in milliseconds (ms) is an average for '''10000''' iterations.
[[if]] (cond1) [[exitWith]] {//code 1};
}}
[[if]] (cond2) [[exitWith]] {//code 2};
[[if]] (cond3) [[exitWith]] {//code 3};
//default code
};</code>


{{Feature|informative|
{{Colorball|red|1.125}} means you '''must''' change your ways today, ''or with us you will ride…''<br>
{{Colorball|orange|1.125}} means you may want to look at it if you are targeting pure performance<br>
{{Colorball|green|1.125}} means the gain is little to insignificant. Going through your code for this replacement is not worth it. You ''may'' only consider it for future code.
}}


[[if]] () [[then]] {} <br/> is faster than <br/> [[if]] () [[exitWith]] {} <br/> is faster than <br/> [[if]] () [[then]] {} [[else]] {} <br/> or <br/> [[if]] () [[then]] [{},{}]
=== Scheduled and Unscheduled Environment ===


However there is no noticeable difference in speed in the following:
There are two code environment types, [[Scheduler#Scheduled_Environment|scheduled]] and [[Scheduler#Unscheduled_Environment|unscheduled]].
* A '''scheduled''' script has an execution time limit of '''3 ms''' before being suspended to the benefit of another script until its turn comes back. It is a bit slower than unscheduled, but [[canSuspend|suspending]] ([[sleep]], [[waitUntil]]) is allowed.
* An '''unscheduled''' script is not watched and will run without limitations. It is recommended for time-critical scripts, but [[canSuspend|suspending]] ([[sleep]], [[waitUntil]]) is '''not''' allowed!
{{Feature|informative|See [[Scheduler]] for more information.}}


<code>_a = 0; [[if]] ([[true]]) [[then]] {_a = 1};
=== {{Colorball|orange|0.9}} Variable Assignment ===
_a = [[if]] ([[true]]) [[then]] [{1},{0}];
_a = [[if]] ([[true]]) [[then]] {1} [[else]] {0};
</code>


==Constants==
<sqf>
private _myVar = [33, 66] select false; // 0.0013 ms
private _myVar = if (false) then { 33; } else { 66; }; // 0.0020 ms
private "_myVar"; if (false) then { _myVar = 33; } else { _myVar = 66; }; // 0.0025 ms
</sqf>


Using a hard coded constant more than once? Use preprocessor directives rather than storing it in memory or cluttering your code with numbers. Such as:
=== {{Colorball|orange|0.9}} Lazy Evaluation ===
<code><nowiki>a = _x + 1.053;
b = _y + 1.053;</nowiki></code>


And
In [[SQF Syntax|SQF]] the following code will evaluate every single condition, even if one fails:
<sqf>if (a && b && c) then {};</sqf>
Even if <var>a</var> returns [[false]] (and thus the entire [[Boolean]] expression can no longer become [[true]]), <var>b</var> and <var>c</var> will still be executed and evaluated regardless.
 
To avoid this behaviour, one can either imbricate [[if]] statements or use '''lazy evaluation'''. The latter is done like so:
<sqf>if (a && { b && { c } }) then {};</sqf>
In the example above, condition evaluation stops once any condition evaluates to [[false]].
 
==== Influence on Semantics ====
Depending on the arrangement of the curly brackets, lazy evaluation can change the precedence (and therefore the semantics) of a condition. Consider this example:
 
{| class="wikitable align-center" style="margin: auto"
|-
! rowspan="2" | Expression !! colspan="3" | Condition Value !! rowspan="2" | Evaluated Conditions !! rowspan="2" | Result
|-
! <var>a</var> !! <var>b</var> !! <var>c</var>
|-
| style="text-align: left" | <sqf>a &&  b  ||  c</sqf> || rowspan="3" | [[false]] || rowspan="3" | any [[Boolean]] || rowspan="3" | [[true]] || <var>a</var>, <var>b</var>, <var>c</var> || [[true]]
|-
| style="text-align: left" | <sqf>a && {b} || {c}</sqf> || <var>a</var>, <var>c</var> || [[true]]
|-
| style="text-align: left" | <sqf>a && {b  || {c}}</sqf> || <var>a</var> || [[false]]
|}
 
==== Performance ====
Using lazy evaluation is not always the best way as it can both speed up and slow down the code, depending on the current condition being evaluated:
<sqf>
["true  || { false  || {false}}", nil, 100000] call BIS_fnc_codePerformance; // 0.00080 ms
["true  ||  {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00105 ms
["false ||  false  ||  false  ", nil, 100000] call BIS_fnc_codePerformance; // 0.00123 ms
["true  ||  false  ||  false  ", nil, 100000] call BIS_fnc_codePerformance; // 0.00128 ms
["false ||  {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00200 ms
</sqf>
 
=== {{Colorball|red|0.9}} Concatenating Strings ===
 
<sqf inline>myString = myString + otherString</sqf> works fine for small strings, however the bigger the string gets the slower the operation becomes:
<sqf>myString = ""; for "_i" from 1 to 10000 do { myString = myString + "123" }; // 290 ms</sqf>
 
The solution is to use a string array that you will concatenate later:
<sqf>
strings = [];
for "_i" from 1 to 10000 do { strings pushBack "123" };
strings = strings joinString ""; // 30 ms
</sqf>
 
=== {{Colorball|red|0.9}} Array Manipulation ===
 
==== Add Elements ====
New commands [[append]] and [[pushBack]] hold the best score.
<sqf>
_array = [0,1,2,3]; _array append [4,5,6]; // 0.0020 ms
_array = [0,1,2,3]; _array = _array + [4,5,6]; // 0.0023 ms
_array = [0,1,2,3]; { _array set [count _array, _x]; } forEach [4,5,6]; // 0.0080 ms
</sqf>
 
<sqf>
_array = [0,1,2,3]; _array pushBack 4; // 0.0016 ms
_array = [0,1,2,3]; _array = _array + [4]; // 0.0021 ms
_array = [0,1,2,3]; _array set [count _array, _x]; // 0.0022 ms
</sqf>
 
==== Iterate Elements ====
[[for]] is twice as fast as [[forEach]] and is recommended '''if [[Magic_Variables#x|_x]] is not required'''.
 
<sqf>
private _array = allUnits; // 64 units 256 units
 
// if the amount of loops is known
for "_i" from 0 to 63 do {}; // 0.0120 ms 0.0460 ms
for "_i" from 0 to count _array -1 do {}; // 0.0170 ms 0.0480 ms
for "_i" from 0 to count _array -1 do { private _x = _array select _i }; // 0.0500 ms 0.1896 ms <
for "_i" from 0 to count _array -1 do { private _x = array select _i; _x setDamage 0 }; // 0.107 ms 0.39 ms
 
{} forEach _array; // 0.0250 ms 0.098 ms
{ _x setDamage 0 } forEach _array; // 0.0770 ms 0.312 ms
 
_array apply {}; // 0.0250 ms 0.098 ms
_array apply { _x setDamage 0 }; // 0.0770 ms 0.312 ms
 
private _i = 0;
while { _i < 64 } do { _i = _i + 1; }; // 0.048 ms 0.200 ms
 
// counts array every loop
private _i = 0;
while { _i < count _array } do { _i = _i + 1; }; // 0.062 ms 0.247 ms
 
private _i = 0;
while { _i < 64 } do { private _x = _array select _i; _i = _i + 1; }; // 0.086 ms 0.42 ms
</sqf>
 
==== Remove Elements ====
<sqf>
_array = [0,1,2,3]; _array deleteAt 0; // 0.0015 ms
_array = [0,1,2,3]; _array set [0, objNull]; _array = _array - [objNull]; // 0.0038 ms
</sqf>


<code><nowiki>_buffer = 1.053;
<sqf>
a = _x + _buffer;
_array = [0,1,2,3]; _array deleteRange [1, 2]; // 0.0018 ms
b = _y + _buffer;</nowiki></code>
_array = [0,1,2,3]; { _array set [_x, objNull] } forEach [1,2]; _array = _array - [objNull]; // 0.0078 ms
</sqf>


Becomes:
=== {{Colorball|red|0.9}} Multiplayer Recommendations ===
<code><nowiki>#define BUFFER 1.053


_a = _x + BUFFER;
* Do not saturate the network with information: [[publicVariable]] or public [[setVariable]] shouldn't be used at high frequency, else '''everyone's performance experience''' is at risk!
_b = _y + BUFFER;
* The server is supposed to have a good CPU and a lot of memory, use it: store functions, run them from it, send only the result to the clients
</nowiki></code>
* [[publicVariable]] and [[setVariable]] variable name length impacts network, be sure to send well-named, understandable variables<br><span style="font-size: 0.9em;">(''and not '''<span style="word-break: break-word">playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction</span>''''')</span>
This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant is it.
* Use, use and use [[remoteExec]] &amp; [[remoteExecCall]]. Ditch [[BIS_fnc_MP]] for good!
{{Feature|informative|See [[Multiplayer Scripting]] for more information.}}


==Loops==


These first two loop types are identical in speed (+/- 10%), and are more than 3x as fast the proceeding two loop types.
== Equivalent Commands Performance ==


*for "_y" from # to # step # do { ... };
=== {{Colorball|orange|0.9}} call ===
*{ ... } foreach [ ... ];


Where as these two loops are much slower, and for maximum performance, avoided.
[[call]] without arguments is faster than call with arguments:
<sqf>
call {}; // 0.0007 ms
123 call {}; // 0.0013 ms
</sqf>


*while { expression } do { code };
Since the variables defined in the parent scope will be available in the [[call]]ed child scope, it could be possible to speed up the code by avoiding passing arguments all together, for example writing:
*for [{ ... },{ ... },{ ... }] do { ... }
<sqf>player addEventHandler ["HandleDamage", { call my_fnc_damage }];</sqf>
instead of:
<sqf>player addEventHandler ["HandleDamage", { _this call my_fnc_damage }];</sqf>


Waituntil can be used when you want something to only run once per frame, which can be handy for limiting scripts that may be resource heavy.
=== {{Colorball|red|0.9}} execVM and call ===


*[[waitUntil]] {expression};
{{Feature|important|
Using [[execVM]] multiple times make the game read the file and recompile it every time.<br>
If you use the script more than once, store its code in a variable or better, make it a [[Arma 3: Functions Library|Function]]!
}}
<sqf>
// myFile.sqf is an EMPTY file
private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once
call _myFunction; // 0.0009 ms
execVM "myFile.sqf"; // 0.275 ms


As requested, the method to gain this information was via the CBA_fnc_benchmarkFunction, using around 10,000 iterations. It was not tested across different stations, and *may* be subject to change between them (ArmA2 is special remember :P):
// myFile.sqf is BIS_fnc_showRespawnMenu
private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once
["close"] call _myFunction; // 0.0056 ms
["close"] execVM "myFile.sqf"; // 0.506 ms
</sqf>


<code><nowiki>fA = {
=== {{Colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers ===
private "_i";
_i = 0;
while {_i < 1000} do {
_i = _i + 1;
private "_t";
_t = "0";
};
};</nowiki></code>


<code><nowiki>fB = {
<sqf>
for "_i" from 0 to 1000 do {
// myFile.sqf is an empty file
private "_t";
loadFile "myFile.sqf"; // 0.219 ms
_t = "0";
preprocessFile "myFile.sqf"; // 0.353 ms
};
preprocessFileLineNumbers "myFile.sqf"; // 0.355 ms
};</nowiki></code>
</sqf>


This code then performs 10,000 tests and returns average time taken for the function, measured via diag_ticktime.
<sqf>
<code><nowiki>
// myFile.sqf is BIS_fnc_showRespawnMenu
[fA,[],10000] call CBA_fnc_benchmarkFunction;
loadFile "myFile.sqf"; // 0.3516 ms
[fB,[],10000] call CBA_fnc_benchmarkFunction;
preprocessFile "myFile.sqf"; // 2.75 ms
</nowiki></code>
preprocessFileLineNumbers "myFile.sqf"; // 2.73 ms
</sqf>


====10,000 Iterations Limit in Loops====
<sqf>
// myFile.sqf is a missing file
loadFile "myFile.sqf"; // 0.692 ms
preprocessFile "myFile.sqf"; // 0.6225 ms
preprocessFileLineNumbers "myFile.sqf"; // 0.6225 ms
</sqf>


A [[while]] [[do]] loop will be limited to 10,000 iteration in non-scheduled environment. In scheduled environment such limit does not apply.
{{Feature|important|The comparison of [[loadFile]] with preprocessFile* is not exactly fair as [[loadFile]] doesn't preprocess the file's content.<br><!--
-->On the other hand, the loaded file cannot contain any {{cc|}} or {{codecomment|/* */}} comments nor any [[PreProcessor Commands|preprocessor instructions]] (including debug information like line numbers or file information).}}


==Threads==
=== {{Colorball|green|0.9}} if ===
The game runs in a scheduled environment, and there are two ways you can run your code. Scheduled and non scheduled.


Depending on where the scope originates, determines how the code is executed. Scheduled code is subject to delays between reading the script across the engine, and execution times can depend on the load on the system at the time.
<sqf>
if (condition) then { /* thenCode */ }; // 0.0011 ms
if (condition) exitWith { /* exitCode */ }; // 0.0014 ms
if (condition) then { /* thenCode */ } else { /* elseCode */ }; // 0.0015 ms
if (condition) then [{ /* thenCode */ }, { /* elseCode */ }]; // 0.0016 ms
</sqf>


Some basic examples:
=== {{Colorball|green|0.9}} if and select ===


*Triggers are inside what we call the 'non-scheduled' environment;
Use <sqf inline>[array] select boolean</sqf> instead of the lazy-evaluated [[if]].
*All pre-init code executions are without scheduling;
<sqf>
*FSM conditions are without scheduling;
_result = ["false result", "true result"] select true; // 0.0011 ms
*Event handlers (on units and in GUI) are without scheduling;
_result = if (true) then { "true result"; } else { "false result"; }; // 0.0017 ms
*Sqf code which called from sqs-code are without scheduling.
</sqf>


====The 3ms run time====
=== {{Colorball|orange|0.9}} if and switch ===
A scheduled script runs for exactly 3ms before it is put in suspension to be resumed on the next frame, again for another 3ms and so on until the script is finished. The amount of suspension depends on FPS. At 20 FPS the duration of suspension for example is 50ms.


This means that if scheduled script cannot be completed under 3ms, the execution can stretch for undefined amount of time, subject to engine load, FPS and other non scheduled scripts running at the same time. A while true loop with sleep started in scheduled environment therefore has little chance to follow with exact interval.
<sqf>
_result = call {
if (false) exitWith {};
if (false) exitWith {};
if (true)  exitWith {};
if (false) exitWith {};
if (false) exitWith {};
}; // 0.0032 ms
</sqf>


Scheduled scripts always start with slight delay, subject to engine load.
<sqf>
_result = switch (true) do {
case (false): {};
case (false): {};
case (true) : {};
case (false): {};
case (false): {};
}; // 0.0047 ms
</sqf>


====When am I creating new threads?====
=== {{Colorball|orange|0.9}} in vs find ===


Using the [[spawn]]/[[execVM]]/[[exec]] commands are creating small threads within the scheduler for ArmA2 (verification from a BIS DEV for specifics is needed here), and as the scheduler works through each one individually, the delay between returning to the start of the schedule to proceed to the next line of your code can be very high (in high load situations, delays of up to a minute can be experienced!).
<sqf>
// String search
"bar" in "foobar" // 0.0008 ms
"foobar" find "bar" > -1 // 0.0012 ms
</sqf>


Obviously this problem is only an issue when your instances are lasting for longer than their execution time, ie spawned loops with sleeps that never end, or last a long time.
<sqf>
// array search - case-sensitive
"bar" in ["foo", "Bar", "bar", "BAR"]; // 0.0012 ms
["foo", "Bar", "bar", "BAR"] find "bar" > -1; // 0.0016 ms
</sqf>


==Avoid O(n^2)!!==
=== {{Colorball|orange|0.9}} for ===


Commonly you may set up foreach foreach's.
The {{hl|[[for]]..[[from]]..[[to]]..[[do]]}} is twice as fast as its alternative syntax, {{hl|[[for]]..[[do]]}}.
'For' example:
<sqf>
for "_i" from 0 to 10 do { /* forCode */ }; // 0.015 ms
for [{ _i = 0 }, { _i < 100 }, { _i = _i + 1 }] do { /* forCode */ }; // 0.030 ms
</sqf>


<code><nowiki>{
=== {{Colorball|green|0.9}} forEach vs count vs findIf ===
{ ...} foreach [0,0,0];
} foreach [0,0,0];</nowiki></code>


This example is of the order (n^2) (3^2 = 9 iterations). For arrays that are twice as big, you will run 4 times slower, and for arrays that are 3 times as big you will run 9 times slower! Of course, you don't always have a choice, and if one (or both) of the arrays is guaranteed to be small it's not really as big of a deal.
Both [[forEach]] and [[count]] commands will step through ''all'' the array elements and both commands will contain reference to current element with the [[Magic Variables#x|_x]] variable.
However, [[count]] loop is a little faster than [[forEach]] loop, but it does not benefit from the [[Magic Variables#forEachIndex|_forEachIndex]] variable.<br>
Also, there is a limitation as the code inside [[count]] expects [[Boolean]] or [[Nothing]] while the command itself returns [[Number]].
This limitation is very important if you try to replace your [[forEach]] by [[count]]. If you have to add an extra [[true]]/[[false]]/[[nil]] at the end to make [[count]] work, it will be slower than the [[forEach]] equivalent.


==Deprecated/Slow Commands==
<sqf>
{ diag_log _x } count  [1,2,3,4,5]; // 0.082 ms
{ diag_log _x } forEach [1,2,3,4,5]; // 0.083 ms
</sqf>


====Adding elements to an [[Array|array]]====
<sqf>
* [[pushBack]] was added in ARMA3 1.26 and is currently the fastest command to push an element into an array, as of 1.29 it will also return the index of the element. Quick tests shows it's around 2x faster than the below method, set. Not to mention it is also easier to read.
// with an empty array
<code><nowiki>
_someoneIsNear = (allUnits findIf { _x  distance [0,0,0] < 1000 }) != -1; // 0.0046 ms
_a pushBack _v
_someoneIsNear = { _x distance [0,0,0] < 1000 } count allUnits > 0; // 0.0047 ms <
</nowiki></code>
_someoneIsNear = {
if (_x distance [0,0,0] < 1000) exitWith { true };
false
} forEach allUnits; // 0.0060 ms
</sqf>


* [[set]] is around 2x faster than [[plus_a|binary addition]]  
<sqf>
<code><nowiki>
// with a 30 items array
_a set [count _a,_v]
_someoneIsNear = (allUnits findIf { _x  distance [0,0,0] < 1000 }) != -1; // 0.0275 ms
</nowiki></code>
_someoneIsNear = { _x distance [0,0,0] < 1000 } count allUnits > 0; // 0.0645 ms <
_someoneIsNear = {
if (_x distance [0,0,0] < 1000) exitWith { true };
false
} forEach allUnits; // 0.0390 ms
</sqf>


Instead of:
=== {{Colorball|red|0.9}} findIf ===
<code><nowiki>
_a = _a + [_v]
</nowiki></code>


====Removing elements from an [[Array|array]]====
[[findIf]] stops array iteration as soon as the condition is met.
When FIFO removing elements from an array, the set removal method works best, even if it makes a copy of the new array.
<sqf>
[0,1,2,3,4,5,6,7,8,9] findIf { _x == 2 }; // 0.0050 ms
{ if (_x == 2) exitWith { _forEachIndex; }; } forEach [0,1,2,3,4,5,6,7,8,9]; // 0.0078 ms
_quantity = { _x == 2 } count [0,1,2,3,4,5,6,7,8,9]; // 0.0114 ms
</sqf>


<code><nowiki>
=== {{Colorball|green|0.9}} format vs str ===
ARRAYX set [0, objnull];
ARRAYX = ARRAYX - [objnull];
</nowiki></code>


====Combining [[Array|array]]s====
<sqf>
<code>_array1 [[append]] _array2</code>
str 33; // 0.0016 ms
is faster than
format ["%1", 33]; // 0.0022 ms
<code>_array1 <nowiki>=</nowiki> _array1 + _array2</code>
</sqf>


The larger the arrays to append, the faster [[append]] is as it does not create a new array, which happens with array addition.
=== {{Colorball|green|0.9}} + vs format vs joinString ===


====Comparing [[Array|array]]s====
non-[[String]] data:
<sqf>
[33, 45, 78] joinString ""; // 0.0052 ms - no length limit
format ["%1%2%3", 33, 45, 78]; // 0.0054 ms - limited to ~8Kb
str 33 + str 45 + str 78; // 0.0059 ms - no length limit
</sqf>


To compare arrays use the following function:
[[String]] data:
<sqf>
["str1", "str2", "str3"] joinString ""; // 0.0015 ms - no length limit
format ["%1%2%3", "str1", "str2", "str3"]; // 0.0015 ms - limited to ~8Kb
"str1" + "str2" + "str3"; // 0.0012 ms - no length limit
</sqf>


<syntaxhighlight lang=javascript>KK_fnc_isEqual = {
=== {{Colorball|orange|0.9}} private ===
    switch (_this select 0) do {
        case (_this select 1) : {true};
        default {false};
    };
};</syntaxhighlight>


Examples:
Direct declaration (<sqf inline>private _var = value</sqf>) is faster than declaring ''then'' assigning the variable.
<sqf>
private _a = 1;
private _b = 2;
private _c = 3;
private _d = 4;
// 0.0023 ms
</sqf>


<syntaxhighlight lang=javascript>hint str ([[1,2,3], [1,2,3]] call KK_fnc_isEqual); //true
<sqf>
hint str ([[1,[2,[3]]], [1,[2,[3]]]] call KK_fnc_isEqual); //true
private ["_a", "_b", "_c", "_d"];
hint str ([[1,[2,[3]]], [1,[2,[4]]]] call KK_fnc_isEqual); //false</syntaxhighlight>
_a = 1;
_b = 2;
_c = 3;
_d = 4;
// 0.0040 ms
</sqf>


In Arma 3 use [[isEqualTo]] command to compare arrays.
However, if you have to reuse the same variable in a loop, external declaration is faster.<br>
The reason behind this is that a declaration in the loop will create, assign and delete the variable in each loop.<br>
An external declaration creates the variable only once and the loop only assigns the value.
<sqf>
private ["_a", "_b", "_c", "_d"];
for "_i" from 1 to 10 do
{
_a = 1; _b = 2; _c = 3; _d = 4;
};
// 0.0195 ms
</sqf>


====Checking if array is []====
<sqf>
for "_i" from 1 to 10 do
{
private _a = 1; private _b = 2; private _c = 3; private _d = 4;
};
// 0.0235 ms
</sqf>


Traditional ([[count]] _arr == 0) is pretty fast, but direct comparison with new comparison command is a little faster: (_arr [[isEqualTo]] [])
=== {{Colorball|orange|0.9}} isNil ===


====Position World is the fastest====
<sqf>
isNil "varName"; // 0.0007 ms
isNil { varName }; // 0.0012 ms
</sqf>


[[getPosASL]] and [[visiblePositionASL]] are almost 2x faster than [[getPos]], [[position]] and [[visiblePosition]].
=== {{Colorball|red|0.9}} isEqualType and typeName ===
Even


<code>[[ASLtoATL]] [[getPosASL]] _obj;</code>
[[isEqualType]] is much faster than [[typeName]]
<sqf>
"string" isEqualType 33; // 0.0006 ms
typeName "string" == typeName 33; // 0.0018 ms
</sqf>


is almost 2x faster than
=== {{Colorball|green|0.9}} isEqualTo and count ===


<code>[[getPos]] _obj;</code>
<sqf>
Note that [[getPosATL]] and [[getPosASL]] are equally as fast in ArmA 3 (tested in v1.22).
// with a items array
allUnits isEqualTo []; // 0.0040 ms
count allUnits == 0; // 0.0043 ms
</sqf>


You can use this function where _this is the object you want to find the position of.
=== {{Colorball|green|0.9}} select and param ===
<code>FNC_GetPos = {
private "_pos";
_pos = getPosASL _this;
if !(surfaceIsWater _pos) then {
_pos = ASLToATL _pos;
};
_pos
};</code>


But new to Arma 3 command [[getPosWorld]] is the fastest  of them all.
<sqf>
[1,2,3] select 0; // 0.0008 ms
[1,2,3] param [0]; // 0.0011 ms
</sqf>


====Config path delimiter====
=== {{Colorball|red|0.9}} createSimpleObject vs createVehicle ===
''>>'' is slightly faster than ''/'' when used in config path with [[configFile]] or [[missionConfigFile]], i.e.
<code>[[getText]] ([[configFile]] >> "CfgVehicles" >> [[typeOf]] _veh >> ...)
// is faster than
[[getText]] ([[configFile]]/"CfgVehicles"/[[typeOf]] _veh/...)</code>


====nearEntities vs nearestObjects====
<sqf>
* [[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of object(s) which are within the given range.
// createSimpleObject is over 43× faster than createVehicle!
If a range was set to more thean 100 meters it is highly recommend to use [[nearEntities]] instead of [[nearestObjects]].
deleteVehicle createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d", [0,0,0]]; // ~0.08 ms
deleteVehicle createSimpleObject ["Land_VR_Shape_01_cube_1m_F", [0,0,0]]; // ~3.2 ms
deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "CAN_COLLIDE"]; // ~2.7 ms
deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "NONE"]; // ~78 ms
</sqf>


Note: [[nearEntities]] only searches for objects which are alive.
=== {{Colorball|orange|0.9}} objectParent and vehicle ===
Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command.


====forEach vs count====
<sqf>
* Both commands will step through supplied array of elements one by one and both commands will contain reference to current element in ''_x'' variable. However, [[count]] loop is a little faster than [[forEach]] loop, but it does not have ''_forEachIndex'' variable and the code inside [[count]] expects [[Boolean]] or [[Nothing]] while it returns [[Number]].
isNull objectParent player; // 0.0013 ms
vehicle player == player; // 0.0022 ms
</sqf>


<code>{[[diag_log]] _x} [[count]] [1,2,3,4,5,6,7,8,9];
[[File:nearEntities vs nearestObjects.png|thumb|150px|right|nearEntities vs nearestObjects]]
//is faster than
=== {{Colorball|red|0.9}} nearEntities and nearestObjects ===
{[[diag_log]] _x} [[forEach]] [1,2,3,4,5,6,7,8,9];</code>


<code>_someoneIsNear = {_x [[distance]] [0,0,0] < 1000} [[count]] [[allUnits]] > 0;
[[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of objects within the given range.
//is still faster than
If range is over 100 meters it is highly recommended to use [[nearEntities]] over [[nearestObjects]].
_someoneIsNear = {
<sqf>
if (_x [[distance]] [0,0,0] < 1000) [[exitWith]] {[[true]]};
// tested with a NATO rifle squad amongst solar power plant panels on Altis at coordinates [20762,15837]
[[false]]
getPosATL player nearEntities [["Man"], 50]; // 0.0075 ms
} [[forEach]] [[allUnits]];
nearestObjects [getPosATL player, ["Man"], 50]; // 0.0145 ms
</code>
</sqf>


====format vs +====
{{Feature|important|[[nearEntities]] only searches for [[alive]] objects and on-foot soldiers.<br>
* when adding more than two strings, [[format]] is faster than +.
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.}}
Adding 3 strings:
<code>a = [[format]] ["Hi, my name is %1?","bob, what's yours"]
//0.00371094 ms
a =  "Hi, my name is " + "bob, what's yours" + "?"
//0.00429688 ms</code>
Adding 2 strings:
<code>a = format ["Hi, my name is %1","bob, what's yours?"]
//0.00380859 ms
a =  "Hi, my name is " + "bob, what's yours?"
//0.00351562 ms</code>


====select vs if====
{{Clear}}
* when selecting between two variables, [[select]] is faster than using an [[if]] statement.
=== {{Colorball|orange|0.9}} Global Variables vs Local Variables ===
<code>a = "Your " + (["a loser","awesome!"] [[select]] [[true]])
//0.00458984 ms


a = "Your " + ([[if]] [[true]] [[then]] [{"awesome!"},{"a loser"}])
If you need to use global variable repeatedly in a loop, copy its value to local variable and use local variable instead:
//0.00537109 ms</code>


==How to test and gain this information yourself?==
<sqf>
SomeGlobalVariable = [123];
for "_i" from 1 to 100 do
{
SomeGlobalVariable select 0;
};
// 0.13 ms
</sqf>


There is a few ways to measure the information and run time durations inside ArmA2, mostly using differencing of the time itself. The CBA package includes a function for you to test yourself, however if you are remaining addon free or cannot use this, the following code setup is as effective; and allows different ways to retrieve the information (chat text, rpt file, clipboard)
is noticeably slower than


<code><nowiki>
<sqf>
_fnc_dump = {
SomeGlobalVariable = [123];
player globalchat str _this;
private _var = SomeGlobalVariable;
diag_log str _this;
for "_i" from 1 to 100 do
//copytoclipboard str _this;
{
_var select 0;
};
};
// 0.08 ms
</sqf>
=== {{Colorball|green|0.9}} Config path delimiter ===
{{hl|[[config greater greater name|>>]]}} is slightly faster than {{hl|[[a / b|/]]}} when used in config path with [[configFile]] or [[missionConfigFile]].
<sqf>
configFile >> "CfgVehicles"; // 0.0019 ms
configFile  / "CfgVehicles"; // 0.0023 ms
</sqf>
{{Feature|informative|A config path can be stored in a variable for later use, saving CPU time: <sqf inline>_cfgVehicles = configFile >> "CfgVehicles"</sqf>.}}
=== {{Colorball|orange|0.9}} getPos* and setPos* ===
<sqf>
getPosWorld // 0.0015 ms
getPosASL // 0.0016 ms
getPosATL // 0.0016 ms
getPosASLW // 0.0023 ms
getPos // 0.0030-0.0300 ms; performance depends on where this command is used - see its documentation
position // same as getPos
getPosVisual // same as getPos
visiblePosition // same as getPos
</sqf>
<sqf>
setPosWorld // 0.0060 ms
setPosASL // 0.0060 ms
setPosATL // 0.0060 ms
setPos // 0.0063 ms
setPosASLW // 0.0068 ms
setVehiclePosition // 0.0077 ms with "CAN_COLLIDE"
// 0.0390 ms with "NONE"
</sqf>
=== {{Colorball|orange|0.9}} toLower/toUpper vs toLowerANSI/toUpperANSI ===
<sqf>
// _myString is a 100 chars "aAaAaA(…)" string
toLowerANSI _myString; // 0.0006 ms
toLower _myString; // 0.0016 ms
toUpperANSI _myString; // 0.0006 ms
toUpper _myString; // 0.0016 ms
</sqf>
== Equivalent Data Structures Performance ==
=== Key-Value Data Structures ===
<sqf>
private _hashMap = createHashMapFromArray [["id", 123], ["name", "player name"], ["unit", player]]; // since Arma 3 v2.02
private _goodFormat = [["id", "name", "unit"], [123, "player name", player]];
private _slowFormat = [["id", 123], ["name", "player name"], ["unit", player]];
</sqf>
<sqf>
private _name = _hashMap get "name"; // 0.0018ms
// this takes 0.0038ms:
private _index = _goodFormat select 0 find "name"; // loop in engine
private _name = _goodFormat select 1 select _index;
// this takes 0.0116ms:
private _index = _slowFormat findIf { _x select 0 == "name" }; // loop in script
private _name = _slowFormat select _index select 1;
</sqf>
{{Feature|informative|[[:Category:Function Group: Database|Database Functions]] use a slow format.}}
== Conversion From Earlier Versions ==
Each iteration of Bohemia games ({{ofp}}, {{arma1}}, {{arma2}}, {{tkoh}}, {{arma3}}) brought their own new commands, especially {{arma2}} and {{arma3}}.<br>
For that, if you are converting scripts from older versions of the engine, the following aspects should be reviewed.
=== Loops ===
* [[forEach]] loops, depending on the situation, can be replaced by:
** [[apply]]
** [[count]]
** [[findIf]]
** [[select]]
=== Array Operations ===
* '''Adding an item:''' <sqf inline>myArray + [element]</sqf> and <sqf inline>myArray set [count myArray, element]</sqf> have been replaced by [[pushBack]]
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]]
* '''Removing items:''' <sqf inline>myArray set [1, objNull]; myArray - [objNull]</sqf> has been replaced by [[deleteAt]] and [[deleteRange]]
* '''Concatenating:''' <sqf inline>myArray = myArray + [element]</sqf> has been ''reinforced'' with [[append]]: if you don't need the original array to be modified, use [[+]]
* '''Comparing:''' use [[isEqualTo]] instead of [[BIS_fnc_areEqual]]
* '''Finding common items:''' [[in]] [[forEach]] loop has been replaced by [[arrayIntersect]]
* '''Condition filtering:''' [[forEach]] can be replaced by [[select]] (alternative syntax)
<sqf>result = (arrayOfNumbers select { _x % 2 == 0 }); // 1.55 ms</sqf>
<sqf>
result = [];
{
if (_x % 2 == 0) then { result pushBack _x; };
} forEach arrayOfNumbers; // 2.57 ms
</sqf>
=== Vector Operations ===
* [[BIS_fnc_vectorMultiply]] has been replaced by [[vectorMultiply]] (at least 6x faster)
* [[BIS_fnc_vectorDivide]] too, to an extent (see [[BIS_fnc_vectorDivide|its page]] for more information)
<sqf>
private _vector = [102, 687, 1543];
private _factor = 53;
_vector vectorMultiply _factor; // 0.0028 ms
[_vector, _factor] call BIS_fnc_vectorMultiply; // 0.0145 ms
_vector vectorMultiply (1 / _factor); // 0.003 ms - but beware of 0 divisor
[_vector, _factor] call BIS_fnc_vectorDivide; // 0.017 ms
</sqf>
=== String Operations ===
[[String]] manipulation has been simplified with the following commands:
* alternative syntax for [[select]]: <sqf inline>string select index</sqf>
* [[toArray]] and [[toString]] have been ''reinforced'' with [[splitString]] and [[joinString]]
=== Number Operations ===
* [[BIS_fnc_linearConversion]] has been replaced by [[linearConversion]]. The command is '''9 times faster'''.
* [[BIS_fnc_selectRandomWeighted]] has been replaced by [[selectRandomWeighted]]. The command is '''7 times faster'''.
=== Type Comparison ===
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]].
=== Multiplayer ===
* [[BIS_fnc_MP]] has been replaced by [[remoteExec]] and [[remoteExecCall]] and internally uses them. Use the engine commands from now on!
{{Feature|informative|See also [[Multiplayer Scripting]].}}
=== Parameters ===


_t1 = diag_tickTime;
* [[BIS_fnc_param]] has been replaced by [[param]] and [[params]]. The commands are approximately '''14 times''' faster. Use them!
// ... code to test
(diag_tickTime - _t1) call _fnc_dump;
</nowiki></code>


In ArmA 3 you can simply use in-built library function [[BIS_fnc_codePerformance]], '''now integrated into the debug console''' as the speedometer button.


[[Category: Scripting_Topics ]]
[[Category:Arma Scripting Tutorials]]

Latest revision as of 02:48, 6 September 2023

This page is about Code Optimisation. For conception optimisation, see Mission Optimisation.

This article will try to be a general guide about improving your code and its performance.

  • The first part (Rules) will focus on having a clean, readable and maintainable code.
  • The second part (Code optimisation) is about improving performance, sometimes trading it against code readability.
  • The third part (Equivalent commands performance) mentions commands that in appearance have identical effects but may differ in terms of performance according to the use you may have of them.
  • The fourth part (Conversion from earlier versions) is a hopefully helpful, short guide about useful new commands or syntaxes to replace the old ways.


Rules

In the domain of development, any rule is a rule of thumb. If a rule states for example that it is better that a line of code doesn't go over 80 characters, it doesn't mean that any line must not go over 80 characters; sometimes, the situation needs it. If you have a good structure, do not change your code to enforce a single arbitrary rule. If you break many of them, you may have to change something. Again, this is according to your judgement.

With that being said, here are the three basic rules to get yourself in the clear:

  1. Make it work
  2. Make it readable
  3. Optimise then

Make It Work

«
« Premature optimization is the root of all evil. » – Donald Knuth

Your first goal when coding is to make your code do what you want it does. A good way to reach this objective is to read and getting inspired by other people's code. If you understand it by reading it once, it is probably a good source of inspiration.

  • When starting from scratch if you know what you want but miss the specific steps to get to your point, it is a good practice to write down in your native language what you want to do. E.g Get all the units near the city, and for each west soldier in them, add 30% damage.
  • Use -showScriptErrors startup parameter and make sure your code doesn't throw errors. Not only will your code run slower but it may also not work at all. Be sure to read the error, isolate the issue and sort it out thanks to this Wiki.
  • Read your Arma RPT (report) to read more details about the error that happened in your code.

Make It Readable

Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:

  • While SQF is (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. Variables like _u instead of _uniform should not be present.
  • One-lining (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
  • Indentation is important for the human mind, and space is too. Space is free, use it.
  • Same goes for line return; it helps to see a code block wrapping multiple common instructions instead of having to guess where it starts and stops.
  • Do you see the same code multiple times, only with different parameters? Now is the time to write a function!
  • If you have a lot of if..else, you may want to look at a switch condition, or again break your code in smaller functions.
  • Is your function code far too long? Break it in understandable-sized bites for your own sanity.
  • Finally, camel-casing (namingLikeThis) your variables will naturally make the code more readable, especially for long names.
See Code Best Practices for more information.

See the following code:

_w=[]; {_w pushbackunique primaryweapon _x} foreach((allunits+alldeadmen) select{_x call BIS_fnc_objectside==east});

The same example is far more readable with proper spacing, good variable names and intermediate results:

_weaponNames = [];
_allUnitsAliveAndDead = allUnits + allDeadMen;
_allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east };
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;

Constants

Using a hard-coded constant more than once? Use preprocessor directives rather than storing it in memory or cluttering your code with numbers. Such as:

a = _x + 1.053; b = _y + 1.053;

And

_buffer = 1.053; a = _x + _buffer; b = _y + _buffer;

Becomes

#define BUFFER 1.053 // note: no semicolon _a = _x + BUFFER; _b = _y + BUFFER;

Using the #define macro only works within the current SQF file. Such definition will not propagate anywhere else.

Global "constants" can be defined via a Description.ext declaration, though, and accessed using getMissionConfigValue command:

Declaration in Description.ext:

var1 = 123;
var2 = "123";
var3[] = { 1, 2, 3 };
rand = __EVAL(random 999);

Usage in code:

hint str getMissionConfigValue "var1"; // 123 // 0.0007 ms hint str getMissionConfigValue "var2"; // "123" // 0.0008 ms hint str getMissionConfigValue "var3"; // [1,2,3] // 0.0017 ms hint str getMissionConfigValue "rand"; // constant random, for example 935.038 // 0.0007 ms

The getMissionConfigValue command searching Description.ext from top to bottom, it is better for a matter of performance to put all your definitions at the top of the file.

Optimise Then

Once you know what is what, you can understand your code better.

  • Use private variables instead of global variables (preceded by an underscore) as much as possible.
  • You were iterating multiple times on the same array?
    • You should be able to spot your issue now.
  • Are you using execVM on the same file, many times?
  • Is your variable name far too long?


Code Optimisation

Please note: Tests and benchmarks were done with the latest Arma 3 version at the time Arma 3 logo black.png1.82 with Tank DLC. Game engine performance may have changed since.
Benchmark result in milliseconds (ms) is an average for 10000 iterations.
means you must change your ways today, or with us you will ride…
means you may want to look at it if you are targeting pure performance
means the gain is little to insignificant. Going through your code for this replacement is not worth it. You may only consider it for future code.

Scheduled and Unscheduled Environment

There are two code environment types, scheduled and unscheduled.

  • A scheduled script has an execution time limit of 3 ms before being suspended to the benefit of another script until its turn comes back. It is a bit slower than unscheduled, but suspending (sleep, waitUntil) is allowed.
  • An unscheduled script is not watched and will run without limitations. It is recommended for time-critical scripts, but suspending (sleep, waitUntil) is not allowed!
See Scheduler for more information.

Variable Assignment

private _myVar = [33, 66] select false; // 0.0013 ms private _myVar = if (false) then { 33; } else { 66; }; // 0.0020 ms private "_myVar"; if (false) then { _myVar = 33; } else { _myVar = 66; }; // 0.0025 ms

Lazy Evaluation

In SQF the following code will evaluate every single condition, even if one fails:

if (a && b && c) then {};

Even if a returns false (and thus the entire Boolean expression can no longer become true), b and c will still be executed and evaluated regardless.

To avoid this behaviour, one can either imbricate if statements or use lazy evaluation. The latter is done like so:

if (a && { b && { c } }) then {};

In the example above, condition evaluation stops once any condition evaluates to false.

Influence on Semantics

Depending on the arrangement of the curly brackets, lazy evaluation can change the precedence (and therefore the semantics) of a condition. Consider this example:

Expression Condition Value Evaluated Conditions Result
a b c
a && b || c
false any Boolean true a, b, c true
a && {b} || {c}
a, c true
a && {b || {c}}
a false

Performance

Using lazy evaluation is not always the best way as it can both speed up and slow down the code, depending on the current condition being evaluated:

["true || { false || {false}}", nil, 100000] call BIS_fnc_codePerformance; // 0.00080 ms ["true || {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00105 ms ["false || false || false ", nil, 100000] call BIS_fnc_codePerformance; // 0.00123 ms ["true || false || false ", nil, 100000] call BIS_fnc_codePerformance; // 0.00128 ms ["false || {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00200 ms

Concatenating Strings

myString = myString + otherString works fine for small strings, however the bigger the string gets the slower the operation becomes:

myString = ""; for "_i" from 1 to 10000 do { myString = myString + "123" }; // 290 ms

The solution is to use a string array that you will concatenate later:

strings = []; for "_i" from 1 to 10000 do { strings pushBack "123" }; strings = strings joinString ""; // 30 ms

Array Manipulation

Add Elements

New commands append and pushBack hold the best score.

_array = [0,1,2,3]; _array append [4,5,6]; // 0.0020 ms _array = [0,1,2,3]; _array = _array + [4,5,6]; // 0.0023 ms _array = [0,1,2,3]; { _array set [count _array, _x]; } forEach [4,5,6]; // 0.0080 ms

_array = [0,1,2,3]; _array pushBack 4; // 0.0016 ms _array = [0,1,2,3]; _array = _array + [4]; // 0.0021 ms _array = [0,1,2,3]; _array set [count _array, _x]; // 0.0022 ms

Iterate Elements

for is twice as fast as forEach and is recommended if _x is not required.

private _array = allUnits; // 64 units 256 units // if the amount of loops is known for "_i" from 0 to 63 do {}; // 0.0120 ms 0.0460 ms for "_i" from 0 to count _array -1 do {}; // 0.0170 ms 0.0480 ms for "_i" from 0 to count _array -1 do { private _x = _array select _i }; // 0.0500 ms 0.1896 ms < for "_i" from 0 to count _array -1 do { private _x = array select _i; _x setDamage 0 }; // 0.107 ms 0.39 ms {} forEach _array; // 0.0250 ms 0.098 ms { _x setDamage 0 } forEach _array; // 0.0770 ms 0.312 ms _array apply {}; // 0.0250 ms 0.098 ms _array apply { _x setDamage 0 }; // 0.0770 ms 0.312 ms private _i = 0; while { _i < 64 } do { _i = _i + 1; }; // 0.048 ms 0.200 ms // counts array every loop private _i = 0; while { _i < count _array } do { _i = _i + 1; }; // 0.062 ms 0.247 ms private _i = 0; while { _i < 64 } do { private _x = _array select _i; _i = _i + 1; }; // 0.086 ms 0.42 ms

Remove Elements

_array = [0,1,2,3]; _array deleteAt 0; // 0.0015 ms _array = [0,1,2,3]; _array set [0, objNull]; _array = _array - [objNull]; // 0.0038 ms

_array = [0,1,2,3]; _array deleteRange [1, 2]; // 0.0018 ms _array = [0,1,2,3]; { _array set [_x, objNull] } forEach [1,2]; _array = _array - [objNull]; // 0.0078 ms

Multiplayer Recommendations

  • Do not saturate the network with information: publicVariable or public setVariable shouldn't be used at high frequency, else everyone's performance experience is at risk!
  • The server is supposed to have a good CPU and a lot of memory, use it: store functions, run them from it, send only the result to the clients
  • publicVariable and setVariable variable name length impacts network, be sure to send well-named, understandable variables
    (and not playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction)
  • Use, use and use remoteExec & remoteExecCall. Ditch BIS_fnc_MP for good!
See Multiplayer Scripting for more information.


Equivalent Commands Performance

call

call without arguments is faster than call with arguments:

call {}; // 0.0007 ms 123 call {}; // 0.0013 ms

Since the variables defined in the parent scope will be available in the called child scope, it could be possible to speed up the code by avoiding passing arguments all together, for example writing:

player addEventHandler ["HandleDamage", { call my_fnc_damage }];

instead of:

player addEventHandler ["HandleDamage", { _this call my_fnc_damage }];

execVM and call

Using execVM multiple times make the game read the file and recompile it every time.
If you use the script more than once, store its code in a variable or better, make it a Function!

// myFile.sqf is an EMPTY file private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once call _myFunction; // 0.0009 ms execVM "myFile.sqf"; // 0.275 ms // myFile.sqf is BIS_fnc_showRespawnMenu private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once ["close"] call _myFunction; // 0.0056 ms ["close"] execVM "myFile.sqf"; // 0.506 ms

loadFile, preprocessFile and preprocessFileLineNumbers

// myFile.sqf is an empty file loadFile "myFile.sqf"; // 0.219 ms preprocessFile "myFile.sqf"; // 0.353 ms preprocessFileLineNumbers "myFile.sqf"; // 0.355 ms

// myFile.sqf is BIS_fnc_showRespawnMenu loadFile "myFile.sqf"; // 0.3516 ms preprocessFile "myFile.sqf"; // 2.75 ms preprocessFileLineNumbers "myFile.sqf"; // 2.73 ms

// myFile.sqf is a missing file loadFile "myFile.sqf"; // 0.692 ms preprocessFile "myFile.sqf"; // 0.6225 ms preprocessFileLineNumbers "myFile.sqf"; // 0.6225 ms

The comparison of loadFile with preprocessFile* is not exactly fair as loadFile doesn't preprocess the file's content.
On the other hand, the loaded file cannot contain any //  or /* */ comments nor any preprocessor instructions (including debug information like line numbers or file information).

if

if (condition) then { /* thenCode */ }; // 0.0011 ms if (condition) exitWith { /* exitCode */ }; // 0.0014 ms if (condition) then { /* thenCode */ } else { /* elseCode */ }; // 0.0015 ms if (condition) then [{ /* thenCode */ }, { /* elseCode */ }]; // 0.0016 ms

if and select

Use [array] select boolean instead of the lazy-evaluated if.

_result = ["false result", "true result"] select true; // 0.0011 ms _result = if (true) then { "true result"; } else { "false result"; }; // 0.0017 ms

if and switch

_result = call { if (false) exitWith {}; if (false) exitWith {}; if (true) exitWith {}; if (false) exitWith {}; if (false) exitWith {}; }; // 0.0032 ms

_result = switch (true) do { case (false): {}; case (false): {}; case (true) : {}; case (false): {}; case (false): {}; }; // 0.0047 ms

in vs find

// String search "bar" in "foobar" // 0.0008 ms "foobar" find "bar" > -1 // 0.0012 ms

// array search - case-sensitive "bar" in ["foo", "Bar", "bar", "BAR"]; // 0.0012 ms ["foo", "Bar", "bar", "BAR"] find "bar" > -1; // 0.0016 ms

for

The for..from..to..do is twice as fast as its alternative syntax, for..do.

for "_i" from 0 to 10 do { /* forCode */ }; // 0.015 ms for [{ _i = 0 }, { _i < 100 }, { _i = _i + 1 }] do { /* forCode */ }; // 0.030 ms

forEach vs count vs findIf

Both forEach and count commands will step through all the array elements and both commands will contain reference to current element with the _x variable. However, count loop is a little faster than forEach loop, but it does not benefit from the _forEachIndex variable.
Also, there is a limitation as the code inside count expects Boolean or Nothing while the command itself returns Number. This limitation is very important if you try to replace your forEach by count. If you have to add an extra true/false/nil at the end to make count work, it will be slower than the forEach equivalent.

{ diag_log _x } count [1,2,3,4,5]; // 0.082 ms { diag_log _x } forEach [1,2,3,4,5]; // 0.083 ms

// with an empty array _someoneIsNear = (allUnits findIf { _x distance [0,0,0] < 1000 }) != -1; // 0.0046 ms _someoneIsNear = { _x distance [0,0,0] < 1000 } count allUnits > 0; // 0.0047 ms < _someoneIsNear = { if (_x distance [0,0,0] < 1000) exitWith { true }; false } forEach allUnits; // 0.0060 ms

// with a 30 items array _someoneIsNear = (allUnits findIf { _x distance [0,0,0] < 1000 }) != -1; // 0.0275 ms _someoneIsNear = { _x distance [0,0,0] < 1000 } count allUnits > 0; // 0.0645 ms < _someoneIsNear = { if (_x distance [0,0,0] < 1000) exitWith { true }; false } forEach allUnits; // 0.0390 ms

findIf

findIf stops array iteration as soon as the condition is met.

[0,1,2,3,4,5,6,7,8,9] findIf { _x == 2 }; // 0.0050 ms { if (_x == 2) exitWith { _forEachIndex; }; } forEach [0,1,2,3,4,5,6,7,8,9]; // 0.0078 ms _quantity = { _x == 2 } count [0,1,2,3,4,5,6,7,8,9]; // 0.0114 ms

format vs str

str 33; // 0.0016 ms format ["%1", 33]; // 0.0022 ms

+ vs format vs joinString

non-String data:

[33, 45, 78] joinString ""; // 0.0052 ms - no length limit format ["%1%2%3", 33, 45, 78]; // 0.0054 ms - limited to ~8Kb str 33 + str 45 + str 78; // 0.0059 ms - no length limit

String data:

["str1", "str2", "str3"] joinString ""; // 0.0015 ms - no length limit format ["%1%2%3", "str1", "str2", "str3"]; // 0.0015 ms - limited to ~8Kb "str1" + "str2" + "str3"; // 0.0012 ms - no length limit

private

Direct declaration (private _var = value) is faster than declaring then assigning the variable.

private _a = 1; private _b = 2; private _c = 3; private _d = 4; // 0.0023 ms

private ["_a", "_b", "_c", "_d"]; _a = 1; _b = 2; _c = 3; _d = 4; // 0.0040 ms

However, if you have to reuse the same variable in a loop, external declaration is faster.
The reason behind this is that a declaration in the loop will create, assign and delete the variable in each loop.
An external declaration creates the variable only once and the loop only assigns the value.

private ["_a", "_b", "_c", "_d"]; for "_i" from 1 to 10 do { _a = 1; _b = 2; _c = 3; _d = 4; }; // 0.0195 ms

for "_i" from 1 to 10 do { private _a = 1; private _b = 2; private _c = 3; private _d = 4; }; // 0.0235 ms

isNil

isNil "varName"; // 0.0007 ms isNil { varName }; // 0.0012 ms

isEqualType and typeName

isEqualType is much faster than typeName

"string" isEqualType 33; // 0.0006 ms typeName "string" == typeName 33; // 0.0018 ms

isEqualTo and count

// with a items array allUnits isEqualTo []; // 0.0040 ms count allUnits == 0; // 0.0043 ms

select and param

[1,2,3] select 0; // 0.0008 ms [1,2,3] param [0]; // 0.0011 ms

createSimpleObject vs createVehicle

// createSimpleObject is over 43× faster than createVehicle! deleteVehicle createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d", [0,0,0]]; // ~0.08 ms deleteVehicle createSimpleObject ["Land_VR_Shape_01_cube_1m_F", [0,0,0]]; // ~3.2 ms deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "CAN_COLLIDE"]; // ~2.7 ms deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "NONE"]; // ~78 ms

objectParent and vehicle

isNull objectParent player; // 0.0013 ms vehicle player == player; // 0.0022 ms

nearEntities vs nearestObjects

nearEntities and nearestObjects

nearEntities is much faster than nearestObjects given on range and amount of objects within the given range. If range is over 100 meters it is highly recommended to use nearEntities over nearestObjects.

// tested with a NATO rifle squad amongst solar power plant panels on Altis at coordinates [20762,15837] getPosATL player nearEntities [["Man"], 50]; // 0.0075 ms nearestObjects [getPosATL player, ["Man"], 50]; // 0.0145 ms

nearEntities only searches for alive objects and on-foot soldiers.
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.

Global Variables vs Local Variables

If you need to use global variable repeatedly in a loop, copy its value to local variable and use local variable instead:

SomeGlobalVariable = [123]; for "_i" from 1 to 100 do { SomeGlobalVariable select 0; }; // 0.13 ms

is noticeably slower than

SomeGlobalVariable = [123]; private _var = SomeGlobalVariable; for "_i" from 1 to 100 do { _var select 0; }; // 0.08 ms

Config path delimiter

>> is slightly faster than / when used in config path with configFile or missionConfigFile.

configFile >> "CfgVehicles"; // 0.0019 ms configFile / "CfgVehicles"; // 0.0023 ms

A config path can be stored in a variable for later use, saving CPU time: _cfgVehicles = configFile >> "CfgVehicles".

getPos* and setPos*

getPosWorld // 0.0015 ms getPosASL // 0.0016 ms getPosATL // 0.0016 ms getPosASLW // 0.0023 ms getPos // 0.0030-0.0300 ms; performance depends on where this command is used - see its documentation position // same as getPos getPosVisual // same as getPos visiblePosition // same as getPos

setPosWorld // 0.0060 ms setPosASL // 0.0060 ms setPosATL // 0.0060 ms setPos // 0.0063 ms setPosASLW // 0.0068 ms setVehiclePosition // 0.0077 ms with "CAN_COLLIDE" // 0.0390 ms with "NONE"

toLower/toUpper vs toLowerANSI/toUpperANSI

// _myString is a 100 chars "aAaAaA(…)" string toLowerANSI _myString; // 0.0006 ms toLower _myString; // 0.0016 ms toUpperANSI _myString; // 0.0006 ms toUpper _myString; // 0.0016 ms


Equivalent Data Structures Performance

Key-Value Data Structures

private _hashMap = createHashMapFromArray [["id", 123], ["name", "player name"], ["unit", player]]; // since Arma 3 v2.02 private _goodFormat = [["id", "name", "unit"], [123, "player name", player]]; private _slowFormat = [["id", 123], ["name", "player name"], ["unit", player]];

private _name = _hashMap get "name"; // 0.0018ms // this takes 0.0038ms: private _index = _goodFormat select 0 find "name"; // loop in engine private _name = _goodFormat select 1 select _index; // this takes 0.0116ms: private _index = _slowFormat findIf { _x select 0 == "name" }; // loop in script private _name = _slowFormat select _index select 1;

Database Functions use a slow format.


Conversion From Earlier Versions

Each iteration of Bohemia games (Operation Flashpoint, Armed Assault, Arma 2, Take On Helicopters, Arma 3) brought their own new commands, especially Arma 2 and Arma 3.
For that, if you are converting scripts from older versions of the engine, the following aspects should be reviewed.

Loops

Array Operations

result = (arrayOfNumbers select { _x % 2 == 0 }); // 1.55 ms
result = []; { if (_x % 2 == 0) then { result pushBack _x; }; } forEach arrayOfNumbers; // 2.57 ms

Vector Operations

private _vector = [102, 687, 1543]; private _factor = 53; _vector vectorMultiply _factor; // 0.0028 ms [_vector, _factor] call BIS_fnc_vectorMultiply; // 0.0145 ms _vector vectorMultiply (1 / _factor); // 0.003 ms - but beware of 0 divisor [_vector, _factor] call BIS_fnc_vectorDivide; // 0.017 ms

String Operations

String manipulation has been simplified with the following commands:

Number Operations

Type Comparison

Multiplayer

Parameters