Code Optimisation: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (codecomment → cc template)
(48 intermediate revisions by 6 users not shown)
Line 1: Line 1:
== Make it work. ==
{{SideTOC|0.9}}
{{ Informative | This page is about [[Code Optimisation]]. For ''conception'' optimisation, see [[Mission Optimisation]]. }}
== Introduction ==
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.


''"Premature optimization is the root of all evil."<br />Donald Knuth''


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.
== Rules ==


== Make it fast. ==
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.


Optimisation is everything when running lots of instances, with low delays. However, there is such thing as premature optimisation. Also, avoid excessive cleverness.
With that being said, here are the three basic rules to get yourself in the clear:


''"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
# [[#Make it work|Make it work]]
''
# [[#Make it readable|Make it readable]]
# [[#Optimise then|Optimise then]]


====Written it twice? Put it in a function====
=== 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");


==== Preprocessfilelinenumbers ====
{{quote|Premature optimization is the root of all evil.|[https://en.wikipedia.org/wiki/Donald_Knuth Donald Knuth]}}
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:
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.
<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.
* 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]]''.
* 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.


====Length====
=== Make it readable ===
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.


====Fewer statements => faster code====
Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:
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];
* 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.
_one = _arr [[select]] 0;
* ''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.
_two = _arr [[select]] 1;
* Indentation is important for the human mind, and space is too. Space is free, use it.
_three = _one + _two;
* 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.
</code>
* 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.


<code>_arr = [1,2];
{{Informative | See '''[[Code Best Practices]]''' for more information.}}
_three  = (_arr [[select]] 0) + (_arr [[select]] 1);
</code>
NOTE: "fewer statements" refers to fewer statements that the engine needs to execute. NOT to confuse with: "fewer statements to write".


====Variable Names====
See the following code:
Scripts and functions that use long [[Variables|variable]] names will run more slowly than those with short names. Using cryptically short variable names is not recommended without explanatory comments.
_w=[]; {_w pushbackunique primaryweapon _x} foreach((allunits+alldeadmen) select{_x call bis_fnc_objectside==east});
<code>_pN = "John Smith"; //this line executes in half the time of the line below
_playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction = "John Smith";</code>


====Conditions====
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;
<!--


<code><nowiki>if (_group knowsAbout vehicle _object > 0 && alive _object && canMove _object && count magazines _object > 0) then {
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
//custom code
};</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 engine will continue evaluating the condition until the end even if any of the previous conditions evaluated false.


<code><nowiki>if (_group knowsAbout vehicle _object > 0) then {
==== Constants ====
      if (alive _object && canMove _object && count magazines _object > 0) then {
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:
            //custom code
a = _x + 1.053;
      };
b = _y + 1.053;
};</nowiki></code>
And
_buffer = 1.053;
a = _x + _buffer;
b = _y + _buffer;
Becomes
<span style="color: purple; font-weight: bold;">#define BUFFER 1.053</span> {{cc|note: no semicolon}}
_a = _x + BUFFER;
_b = _y + BUFFER;
{{Informative | 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:


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:
Declaration in [[Description.ext]]:
<syntaxhighlight lang="cpp">
var1 = 123;
var2 = "123";
var3[] = {1,2,3};
rand = __EVAL(random 999);
</syntaxhighlight>


<code><nowiki>if (_group knowsAbout _vehicle object > 0 && {alive _object} && {canMove _object} && {count magazines _object > 0}) then {
Usage in code:
            //custom code
[[hint]] [[str]] [[getMissionConfigValue]] "var1"; // 123 {{cc|0.0007 ms}}
};</nowiki></code>
[[hint]] [[str]] [[getMissionConfigValue]] "var2"; // "123" {{cc|0.0008 ms}}
[[hint]] [[str]] [[getMissionConfigValue]] "var3"; // [1,2,3] {{cc|0.0017 ms}}
[[hint]] [[str]] [[getMissionConfigValue]] "rand"; // constant random, for example 935.038 {{cc|0.0007 ms}}


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>


====isNil====
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 {{Inline code|_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";}}).
* Is your variable name far too long?
** Find a smaller name, according to the variable scope; e.g:<br><!--
--><code>{ _opforUnitUniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _opforUnitUniform; } [[forEach]] _allOpforUnits;</code><!--
-->becomes <code>{ _uniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _uniform; } [[forEach]] _allOpforUnits;</code>


[[isNil]] [[String]] is quite a bit faster than [[isNil]] [[Code]]


<code>var = 123;
== Code optimisation ==
[[isNil]] "var";
// is faster than
[[isNil]] {var};</code>


== Make it pretty. ==
{{Important|
'''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.}}


Documentation, readability, and all that jazz. Clean code is good code.
{{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 Else If Else If Else ...====
=== Scheduled and unscheduled environment ===
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]]:
There are two code environment types, [[Scheduler#Scheduled_Environment|scheduled]] and [[Scheduler#Unscheduled_Environment|unscheduled]].
<code>[[call]] {
* 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.
[[if]] (cond1) [[exitWith]] {/*code 1*/};
* 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!
[[if]] (cond2) [[exitWith]] {/*code 2*/};
{{Informative | See [[Scheduler]] full article for more information.}}
[[if]] (cond3) [[exitWith]] {/*code 3*/};
//default code
};</code>


=== {{colorball|orange|0.9}} Lazy evaluation ===


[[if]] () [[then]] {} <br/> is faster than <br/> [[if]] () [[exitWith]] {} <br/> is faster than <br/> [[if]] () [[then]] {} [[else]] {} <br/> or <br/> [[if]] () [[then]] [{},{}]
[[private]] _myVar = [33, 66] [[select]] ([[false]]); {{cc|0.0013 ms}}
[[private]] _myVar = [[if]] ([[false]]) [[then]] { 33; } [[else]] { 66; }; {{cc|0.0020 ms}}
[[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{cc|0.0025 ms}}


However there is no noticeable difference in speed in the following:
=== {{colorball|orange|0.9}} Successive condition check ===


<code>_a = 0; [[if]] ([[true]]) [[then]] {_a = 1};
In [[SQF syntax|SQF]] the following code will check '''all and every''' conditions, even if one fails:
_a = [[if]] ([[true]]) [[then]] [{1},{0}];
[[if]] (condition1 && condition2 && condition3) [[then]] { {{codecomment|/* thenCode */}} };
_a = [[if]] ([[true]]) [[then]] {1} [[else]] {0};
This code will check ''condition1'', and if it is not [[true]] ''condition2'' and ''condition3'' will execute anyway.
</code>
To avoid this behaviour, you can either imbricate [[if|ifs]] or use '''lazy evaluation''' such as the following:
[[if]] (condition1 && { condition2 } && { condition3 }) [[then]] { {{codecomment|/* thenCode */}} };
This method will stop condition evaluation on the first [[false]] statement.


==Constants==
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:
["[[true]] &nbsp;|| {&nbsp;[[false]]&nbsp; || {[[false]]}}", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00080 ms}}
["[[true]] &nbsp;|| &nbsp;{[[false]]} || {[[false]]}&nbsp;", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00105 ms}}
["[[false]] || &nbsp;&nbsp;[[false]] &nbsp;|| &nbsp;[[false]]&nbsp;&nbsp;", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00123 ms}}
["[[true]] &nbsp;|| &nbsp;&nbsp;[[false]] &nbsp;|| &nbsp;[[false]]&nbsp;&nbsp;", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00128 ms}}
["[[false]] || &nbsp;{[[false]]} || {[[false]]}&nbsp;", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00200 ms}}


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|red|0.9}} Concatenating multiple small strings together ===
<code><nowiki>a = _x + 1.053;
b = _y + 1.053;</nowiki></code>


And
{{Inline code|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" }; {{cc|290 ms}}


<code><nowiki>_buffer = 1.053;
The solution is to use a string array that you will concatenate later:
a = _x + _buffer;
strings = [];
b = _y + _buffer;</nowiki></code>
[[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {strings [[pushBack]] "123"};
strings = strings [[joinString]] ""; {{cc|30 ms}}


Becomes:
=== {{colorball|red|0.9}} Manipulating arrays ===
<code><nowiki>#define BUFFER 1.053


_a = _x + BUFFER;
==== Adding elements ====
_b = _y + BUFFER;
New commands [[append]] and [[pushBack]] hold the best score.
</nowiki></code>
_array = [0,1,2,3]; _array [[append]] [4,5,6]; {{cc|0.0020 ms}}
This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant is it.
_array = [0,1,2,3]; _array = _array + [4,5,6]; {{cc|0.0023 ms}}
_array = [0,1,2,3]; { _array [[set]] <nowiki>[</nowiki>[[count]] _array, [[_x]]]; } [[forEach]] [4,5,6]; {{cc|0.0080 ms}}


==Loops==
_array = [0,1,2,3]; _array [[pushBack]] 4; {{cc|0.0016 ms}}
_array = [0,1,2,3]; _array = _array + [4]; {{cc|0.0021 ms}}
_array = [0,1,2,3]; _array [[set]] <nowiki>[</nowiki>[[count]] _array, [[_x]]]; {{cc|0.0022 ms}}


These first two loop types are identical in speed (+/- 10%), and are more than 3x as fast the proceeding two loop types.
==== Removing elements ====
_array = [0,1,2,3]; _array [[deleteAt]] 0; {{cc|0.0015 ms}}
_array = [0,1,2,3]; _array [[set]] [0, [[objNull]]]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{cc|0.0038 ms}}


*for "_y" from # to # step # do { ... };
_array = [0,1,2,3]; _array [[deleteRange]] [1, 2]; {{cc|0.0018 ms}}
*{ ... } foreach [ ... ];
_array = [0,1,2,3]; { _array [[set]] <nowiki>[</nowiki>[[_x]], [[objNull]]] } [[forEach]] [1,2]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{cc|0.0078 ms}}


Where as these two loops are much slower, and for maximum performance, avoided.
=== {{colorball|red|0.9}} Multiplayer recommendations ===


*while { expression } do { code };
* 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!
*for [{ ... },{ ... },{ ... }] do { ... }
* 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<br /><span style="font-size: 0.9em;">(''and not '''<span style="word-break: break-word">playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction</span>''''')</span>
* Use, use and use [[remoteExec]] &amp; [[remoteExecCall]]. Ditch [[BIS_fnc_MP]] for good!
{{Informative | See [[Multiplayer Scripting]] for more information.}}
<!--
=== {{colorball|red|0.9}} createSimpleObject vs createVehicle ===
''TODO''
* [[createSimpleObject]] is over '''43x''' faster than createVehicle!
createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"];// ~3.5 ms
createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]];// ~0.08 ms
private _cube = createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"]; deleteVehicle _cube; // 244 cycles, 4.11066 ms
private _cube = createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]]; deleteVehicle _cube; // freezes the game for seconds, but 9748 cycles / 0.102585 ms?? Cannot confirm.
* Creating an object at [0,0,0] ''then'' [[setPos]] it will be faster than creating it at the wanted pos (''if spawning obstacles are present? What about "CAN_COLLIDE"?'')
-->


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.
== Equivalent commands performance ==


*[[waitUntil]] {expression};
=== {{colorball|orange|0.9}} call ===


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):
[[call]] without arguments is faster than call with arguments:
[[call]] {}; {{cc|0.0007 ms}}
123 [[call]] {}; {{cc|0.0013 ms}}


<code><nowiki>fA = {
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:
private "_i";
[[player]] [[addEventHandler]] ["HandleDamage", { [[call]] my_fnc_damage }];
_i = 0;
instead of:
while {_i < 1000} do {
[[player]] [[addEventHandler]] ["HandleDamage", { _this [[call]] my_fnc_damage }];
_i = _i + 1;
private "_t";
_t = "0";
};
};</nowiki></code>


<code><nowiki>fB = {
<!-- TODO: compare execVM and spawn, and/or waitUntil scriptDone / call
for "_i" from 0 to 1000 do {
=== {{colorball|red|0.9}} execVM and call ===
private "_t";
_t = "0";
};
};</nowiki></code>


This code then performs 10,000 tests and returns average time taken for the function, measured via diag_ticktime.
Using [[execVM]] multiple times make the game read the file and recompile it every time.<br>
<code><nowiki>
If you will use the script more than once, store its code in a variable:
[fA,[],10000] call CBA_fnc_benchmarkFunction;
[fB,[],10000] call CBA_fnc_benchmarkFunction;
</nowiki></code>


====10,000 Iterations Limit in Loops====
{{cc|myFile.sqf is an '''empty''' file}}
_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|compile time is done only once}}
[[call]] _myFunction; {{cc|0.0009 ms}}
[[execVM]] "myFile.sqf"; {{cc|0.275 ms}}


A [[while]] [[do]] loop will be limited to 10,000 iteration in [[Scheduler#Unscheduled_Environment|non-scheduled environment]]. In [[Scheduler#Scheduled_Environment|scheduled environment]] such limit does not apply.
{{cc|myFile.sqf is [[BIS_fnc_showRespawnMenu]]}}
_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";
["close"] [[call]] _myFunction; {{cc|0.0056 ms}}
["close"] [[execVM]] "myFile.sqf"; {{cc|0.506 ms}}


==Threads==
{{cc|myFile.sqf is a '''missing''' file}}
The game runs in a [[Scheduler#Scheduled_Environment|scheduled environment]], and there are two ways you can run your code. [[Scheduler#Scheduled_Environment|Scheduled]] and [[Scheduler#Unscheduled_Environment|non scheduled]].
_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";
[[call]] _myFunction; {{cc|0.0009 ms}}
[[execVM]] "myFile.sqf"; {{cc|0.275 ms}}
-->


Depending on where the scope originates, determines how the code is executed. [[Scheduler#Scheduled_Environment|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.
=== {{colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers ===


Some basic examples:
{{cc|myFile.sqf is an '''empty''' file}}
[[loadFile]] "myFile.sqf"; {{cc|0.219 ms}}
[[preprocessFile]] "myFile.sqf"; {{cc|0.353 ms}}
[[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|0.355 ms}}


*Triggers are inside what we call the '[[Scheduler#Unscheduled_Environment|non-scheduled]]' environment;
{{cc|myFile.sqf is [[BIS_fnc_showRespawnMenu]]}}
*All pre-init code executions are [[Scheduler#Unscheduled_Environment|without scheduling]];
[[loadFile]] "myFile.sqf"; {{cc|0.3516 ms}}
*FSM conditions are [[Scheduler#Unscheduled_Environment|without scheduling]];
[[preprocessFile]] "myFile.sqf"; {{cc|2.75 ms}}
*Event handlers (on units and in GUI) are [[Scheduler#Unscheduled_Environment|without scheduling]];
[[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|2.73 ms}}
*Sqf code which called from sqs-code are [[Scheduler#Unscheduled_Environment|without scheduling]].


====The 3ms run time====
{{cc|myFile.sqf is a '''missing''' file}}
A [[Scheduler#Scheduled_Environment|scheduled]] script can only run for maximum 3ms per frame before it is put in suspension to be resumed on the next frame or even later.
[[loadFile]] "myFile.sqf"; {{cc|0.692 ms}}
For more information on that see [[Scheduler#Scheduled_Environment|Scheduler]].
[[preprocessFile]] "myFile.sqf"; {{cc|0.6225 ms}}
[[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|0.6225 ms}}


====When am I creating new threads?====
{{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 informations like line numbers or file informations).}}


Using the [[spawn]]/[[execVM]]/[[exec]] commands are creating small entries within the script [[Scheduler|scheduler]], and as the [[Scheduler|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!).
=== {{colorball|green|0.9}} if ===


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.
[[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{cc|0.0011 ms}}
[[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{cc|0.0014 ms}}
[[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{cc|0.0015 ms}}
[[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{cc|0.0016 ms}}


==Avoid O(n^2)!!==
=== {{colorball|green|0.9}} if and select ===


Commonly you may set up foreach foreach's.
Use {{Inline code|[array] [[select]] [[Boolean]]}} instead of the lazy-evaluated [[if]].
'For' example:
_result = ["false result", "true result"] [[select]] [[true]]; {{cc|0.0011 ms}}
_result = [[if]] ([[true]]) [[then]] { "true result"; } [[else]] { "false result"; }; {{cc|0.0017 ms}}


<code><nowiki>{
=== {{colorball|orange|0.9}} if and switch ===
{ ...} 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.
_result = [[call]] {
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[true]])  [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
}; {{cc|0.0032 ms}}


==Deprecated/Slow Commands==
_result = [[switch]] ([[true]]) [[do]] {
[[case]] ([[false]]): {};
[[case]] ([[false]]): {};
[[case]] ([[true]]) : {};
[[case]] ([[false]]): {};
[[case]] ([[false]]): {};
}; {{cc|0.0047 ms}}


====Adding elements to an [[Array|array]]====
=== {{colorball|orange|0.9}} in vs find ===
* [[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.
<code><nowiki>
_a pushBack _v
</nowiki></code>


* [[set]] is around 2x faster than [[plus_a|binary addition]]  
{{cc|[[String]] search}}
<code><nowiki>
"bar" [[in]] "foobar" {{cc|0.0008 ms}}
_a set [count _a,_v]
"foobar" [[find]] "bar" > -1 {{cc|0.0012 ms}}
</nowiki></code>


Instead of:
{{cc|[[Array]] search - '''case-sensitive'''}}
<code><nowiki>
"bar" [[in]] ["foo", "Bar", "bar", "BAR"]; {{cc|? ms}}
_a = _a + [_v]
"bar" [[find]] ["foo", "Bar", "bar", "BAR"] > -1; {{cc|? ms}}
</nowiki></code>


====Removing elements from an [[Array|array]]====
=== {{colorball|orange|0.9}} for ===
[[deleteAt]] - Removes array element at the given index and returns removed element (modifies the original array, just like [[resize]] or [[set]])


<code><nowiki>_array = [1,2,3]
The {{Inline code|[[for]]..[[from]]..[[to]]..[[do]]}} is twice as fast as its alternative syntax, {{Inline code|[[for]]..[[do]]}}.
_array deleteAt 1;
[[for]] "_i" [[from]] 0 [[to]] 10 [[do]] { {{codecomment|/* forCode */}} }; {{cc|0.015 ms}}
systemChat str _array; // -> [1,3]
[[for]] [{_i = 0}, {_i < 100}, {_i = _i + 1}] [[do]] { {{codecomment|/* forCode */}} }; {{cc|0.030 ms}}
</nowiki></code>


Faster than...
=== {{colorball|green|0.9}} forEach vs count vs findIf ===


When FIFO removing elements from an array, the set removal method works best, even if it makes a copy of the new array.
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.<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 a extra true/false/nil at the end to make count work, it will be slower than the forEach equivalent.


<code><nowiki>ARRAYX set [0, objnull];
{ [[diag_log]] [[_x]] } [[count]]  [1,2,3,4,5]; {{cc|0.082 ms}}
ARRAYX = ARRAYX - [objnull];
{ [[diag_log]] [[_x]] } [[forEach]] [1,2,3,4,5]; {{cc|0.083 ms}}
</nowiki></code>


====Combining [[Array|array]]s====
{{cc|with an empty array}}
*When adding an array to an existing array variable, [[append]] is fastest
_someoneIsNear = ([[allUnits]] [[findIf]] { [[_x]]  [[distance]] [0,0,0] < 1000 }) != -1; {{cc|0.0046 ms}}
<code>arr1 = [1,2,3,4,5,6,7,8,9,0]; arr2 = arr1; arr1 append arr2;
_someoneIsNear = { [[_x]] [[distance]] [0,0,0] < 1000 } [[count]] [[allUnits]] > 0; {{cc|'''0.0047 ms'''}}
//0.015 ms
_someoneIsNear = {
[[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] };
[[false]]
} [[forEach]] [[allUnits]]; {{cc|0.0060 ms}}


arr1 = [1,2,3,4,5,6,7,8,9,0]; arr2 = arr1; arr1 + arr2;
{{cc|with a 30 items array}}
//0.016 ms (Arma 3 after optimisation)</code>
_someoneIsNear = ([[allUnits]] [[findIf]] { [[_x]]  [[distance]] [0,0,0] < 1000 }) != -1; {{cc|0.0275 ms}}
[[append]] modifies existing array while "+" produces a copy, hence a little bit slower.  
_someoneIsNear = { [[_x]] [[distance]] [0,0,0] < 1000 } [[count]] [[allUnits]] > 0; {{cc|'''0.0645 ms'''}}
_someoneIsNear = {
[[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] };
[[false]]
} [[forEach]] [[allUnits]]; {{cc|0.0390 ms}}


* When not saving the array to a variable, use +.
=== {{colorball|red|0.9}} findIf ===
<code>([veh1] + _array2) call BIS_fnc_setPitchBank
//0.004 ms


_array1 = [veh1];
[[findIf]] stops array iteration as soon as the condition is met.
_array1 append _array2;
[0,1,2,3,4,5,6,7,8,9] [[findIf]] { [[_x]] == 2 }; {{cc|0.0050 ms}}
_array1 call BIS_fnc_setPitchBank
{ [[if]] ([[_x]] == 2) [[exitWith]] { [[_forEachIndex]]; }; } [[forEach]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0078 ms}}
//0.0054 ms</code>
_quantity = { [[_x]] == 2 } [[count]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0114 ms}}


====Comparing [[Array|array]]s====
=== {{colorball|green|0.9}} format vs str ===


To compare arrays prior Arma 3, use the following function:
[[str]] 33; {{cc|0.0016 ms}}
[[format]] ["%1", 33]; {{cc|0.0022 ms}}


<syntaxhighlight lang=javascript>KK_fnc_arraysAreEqual = {str (_this select 0) in [str (_this select 1)]};</syntaxhighlight>
=== {{colorball|green|0.9}} + vs format vs joinString ===


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


<syntaxhighlight lang=javascript>hint str ([[1,2,[3]], [1,2,[3]]] call KK_fnc_arraysAreEqual); //true</syntaxhighlight>
=== {{colorball|orange|0.9}} private ===


In Arma 3 use [[isEqualTo]] command.
Direct declaration ({{Inline code|[[private]] _var {{=}} value}}) is faster than declaring ''then'' assigning the variable.
[[private]] _a = 1;
[[private]] _b = 2;
[[private]] _c = 3;
[[private]] _d = 4;
{{cc|0.0023 ms}}


====Comparing values by type====
[[private]] ["_a", "_b", "_c", "_d"];
_a = 1;
_b = 2;
_c = 3;
_d = 4;
{{cc|0.0040 ms}}


<code>"a" [[isEqualType]] 0
However, if you have to reuse the same variable in a loop, external declaration is faster.<br />
//0.0009 ms</code>
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.
[[private]] ["_a", "_b", "_c", "_d"];
[[for]] "_i" [[from]] 1 [[to]] 10 [[do]]
{
_a = 1; _b = 2; _c = 3; _d = 4;
};
{{cc|0.0195 ms}}


Is much faster than
[[for]] "_i" [[from]] 1 [[to]] 10 [[do]]
{
[[private]] _a = 1; [[private]] _b = 2; [[private]] _c = 3; [[private]] _d = 4;
};
{{cc|0.0235 ms}}


<code>[[typeName]] "a" == [[typeName]] 0
=== {{colorball|orange|0.9}} isNil ===
//0.0032 ms</code>


====Checking if array is []====
[[isNil]] "varName"; {{cc|0.0007 ms}}
[[isNil]] {varName}; {{cc|0.0012 ms}}


Traditional ([[count]] _arr == 0) is pretty fast, but direct comparison with new comparison command is a little faster: (_arr [[isEqualTo]] [])
=== {{colorball|red|0.9}} isEqualType and typeName ===


<code>[[count]] _arr == 0
[[isEqualType]] is much faster than [[typeName]]
// 0.0014 ms</code>
"string" [[isEqualType]] 33; {{cc|0.0006 ms}}
[[typeName]] "string" == [[typeName]] 33; {{cc|0.0018 ms}}


<code>_arr [[isEqualTo]] []
=== {{colorball|green|0.9}} isEqualTo and count ===
// 0.0013 ms</code>


====Position World is the fastest====
{{cc|with a items array}}
[[allUnits]] [[isEqualTo]] []; {{cc|0.0040 ms}}
[[count]] [[allUnits]] == 0; {{cc|0.0043 ms}}


[[getPosASL]], [[getPosATL]] and [[visiblePositionASL]] are  faster than [[getPos]], [[position]] and [[visiblePosition]]. But new to Arma 3 command [[getPosWorld]] is the fastest  of them all.
=== {{colorball|green|0.9}} select and param ===
<code>[[getPosWorld]] [[player]]
//0.0014 ms</code>


<code>[[getPosASL]] [[player]]
  [1,2,3] [[select]] 0; {{cc|0.0008 ms}}
//0.0014 ms</code>
  [1,2,3] [[param]] [0]; {{cc|0.0011 ms}}


<code>[[getPosATL]] [[player]]
=== {{colorball|orange|0.9}} objectParent and vehicle ===
//0.0015 ms</code>


<code>[[visiblePositionASL]] [[player]]
[[isNull]] [[objectParent]] [[player]]; {{cc|0.0013 ms}}
//0.0014 ms</code>
[[vehicle]] [[player]] == [[player]]; {{cc|0.0022 ms}}


<code>[[visiblePosition]] [[player]]
=== {{colorball|red|0.9}} nearEntities and nearestObjects ===
//0.0048 ms</code>


<code>[[getPos]] [[player]]
[[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of objects within the given range.
//or
If range is over 100 meters it is highly recommended to use [[nearEntities]] over [[nearestObjects]].
[[position]] [[player]]
{{cc|tested with a NATO rifle squad amongst solar power plant panels on [[Altis]] at coordinates [20762,15837]}}
//0.005 ms</code>
[[getPosATL]] [[player]] [[nearEntities]] [["Man"], 50]; {{cc|0.0075 ms}}
[[nearestObjects]] <nowiki>[</nowiki>[[getPosATL]] [[player]], ["Man"], 50]; {{cc|0.0145 ms}}


====Config path delimiter====
{{Important|[[nearEntities]] only searches for [[alive]] objects and on-foot soldiers.<br />
''>>'' is slightly faster than ''/'' when used in config path with [[configFile]] or [[missionConfigFile]], i.e.
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.}}
<code>[[configFile]] >> "CfgVehicles"
//0.0019 ms</code>
is faster than
<code>[[configFile]]/"CfgVehicles"
//0.0023 ms</code>
====Reusing configs====


The delimiter [[config_greater_greater_name|>>]] (or /) is just a script command with 2 arguments <tt>arg1 >> arg2</tt>, so when one constructs config path, he just chains several commands so that the result of one command becomes argument for another. Therefore when repeated request to config is required, it makes sense to store the closest [[Config]] result in a variable for performance.
=== {{colorball|orange|0.9}} Global variables vs local variables ===
<code>_cfgCar = [[configFile]] >> "CfgVehicles" >> "Car";
_access = _cfgCar >> "access";
_type = _cfgCar >> "type";
....</code>


====nearEntities vs nearestObjects====
If you need to use global variable repeatedly in a loop, copy its value to local variable and use local variable instead:
* [[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of object(s) which are within the given range.
If a range was set to more thean 100 meters it is highly recommend to use [[nearEntities]] instead of [[nearestObjects]].


Note: [[nearEntities]] only searches for objects which are alive.
<code>SomeGlobalVariable = [123];
Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command.
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]]
{
SomeGlobalVariable [[select]] 0;
};
{{cc|0.13 ms}}</code>


====forEach vs count====
is noticeably slower than
* 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]].


<code>{[[diag_log]] _x} [[count]] [1,2,3,4,5,6,7,8,9];
<code>SomeGlobalVariable = [123];
//is faster than
[[private]] _var = SomeGlobalVariable;
{[[diag_log]] _x} [[forEach]] [1,2,3,4,5,6,7,8,9];</code>
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]]
 
{
<code>_someoneIsNear = {_x [[distance]] [0,0,0] < 1000} [[count]] [[allUnits]] > 0;
_var [[select]] 0;
//is still faster than
};
_someoneIsNear = {
{{cc|0.08 ms}}</code>
if (_x [[distance]] [0,0,0] < 1000) [[exitWith]] {[[true]]};
[[false]]
} [[forEach]] [[allUnits]];
</code>
 
====Filtering array with select {}====
If you want to filter an array, you can loop trough it with forEach/Count and use If (condition) inside OR use select {}.


In this example,the testArray is filled with numbers from 0 to 1000 and in this our filter condition is to be an even number.
=== {{colorball|green|0.9}} Config path delimiter ===
<code>
result = [];
{
[[if]] (_x % 2 [[==]] 0) [[then]]
{
result [[pushBack]] _x;
};
} [[forEach]] testArray;
'''//2.57 ms'''


result = (testArray [[select]] {_x % 2 [[==]] 0});
{{Inline code|[[config_greater_greater_name|>>]]}} is slightly faster than {{Inline code|[[config_/_name|/]]}} when used in config path with [[configFile]] or [[missionConfigFile]].
'''//1.55 ms'''
[[configFile]] >> "CfgVehicles"; {{cc|0.0019 ms}}
</code>
[[configFile]]  / "CfgVehicles"; {{cc|0.0023 ms}}
{{Informative | A config path can be stored in a variable for later use, saving CPU time: ''{{Inline code|_cfgVehicles {{=}} [[configFile]] >> "CfgVehicles"}}'' }}


So if you would like - for example - add these even numbers up:
=== {{colorball|orange|0.9}} getPos* and setPos* ===
<code>
result = 0;
{
[[if]] (_x % 2 [[==]] 0) [[then]]
{
result = result + _x;
};
} [[forEach]] testArray;
'''//2.79 ms'''


result = 0;
[[getPosWorld]] {{cc|0.0015 ms}}
{  
[[getPosASL]] {{cc|0.0016 ms}}
result = result + _x;
[[getPosATL]] {{cc|0.0016 ms}}
} [[forEach]] (testArray [[select]] {_x % 2 [[==]] 0});
[[getPos]] {{cc|0.0020 ms}}
'''//2.44 ms'''
[[position]] {{cc|0.0020 ms}}
</code>
[[getPosVisual]] {{cc|0.0021 ms}}
[[visiblePosition]] {{cc|0.0021 ms}}
[[getPosASLW]] {{cc|0.0023 ms}}


Filtering your base array with select {} will be faster.
[[setPosWorld]] {{cc|0.0060 ms}}
[[setPosASL]] {{cc|0.0060 ms}}
[[setPosATL]] {{cc|0.0060 ms}}
[[setPos]] {{cc|0.0063 ms}}
[[setPosASLW]] {{cc|0.0068 ms}}
[[setVehiclePosition]] {{cc|0.0077 ms with "CAN_COLLIDE"}}
{{cc|0.0390 ms with "NONE"}}


====for [] vs for "_i" ====
=== {{colorball|orange|0.9}} toLower/toUpper vs toLowerANSI/toUpperANSI ===
* One may think that "for [] do" is faster than "for "_i" from 1 to 10 step 1 do" because less keywords are used.
But the opposite is true.
<code>for [{_x= 1},{_x <= 10},{_x = _x + 1}] do {true};
//0.0532 ms
for "_i" from 1 to 10 step 1 do {true};
//0.015 ms</code>


====format vs +====
{{cc|_myString is a 100 chars "aAaAaA(…)" string}}
* when adding more than two strings, [[format]] is faster than +.
[[toLowerANSI]] _myString; {{cc|0.0006 ms}}
Adding 3 strings:
[[toLower]] _myString; {{cc|0.0016 ms}}
<code>a = [[format]] ["Hi, my name is %1%2","bob, what's yours","?"]
   
//0.004 ms
[[toLowerANSI]] _myString; {{cc|0.0006 ms}}
a = "Hi, my name is " + "bob, what's yours" + "?"
  [[toLower]] _myString; {{cc|0.0016 ms}}
//0.0043 ms</code>
Adding 2 strings:
<code>a = format ["Hi, my name is %1","bob, what's yours?"]
//0.0038 ms
a = "Hi, my name is " + "bob, what's yours?"
//0.0035 ms</code>


====Adding large strings together====
== Conversion from earlier versions ==


For small strings a = a + b works fine, however the bigger the string gets the slower this becomes:
Each iteration of Bohemia games ({{ofp}}, {{arma}}, {{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.


<code>s = ""; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {s = s + "123"}; //30000 chars @ 290ms</code>
=== Loops ===


The solution is to use array to make string and then convert array to string:
* [[forEach]] loops, depending on the situation, can be replaced by:
** [[apply]]
** [[count]]
** [[findIf]]
** [[select]]


<code>s = []; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {s [[pushBack]] "123"}; s = s [[joinString]] ""; //30000 chars @ 30ms</code>
=== Array operations ===


====select vs if====
* '''Adding an item:''' {{Inline code|myArray [[valuea_plus_valueb|+]] [element]}} and {{Inline code|myArray [[set]] <nowiki>[</nowiki>[[count]] myArray, element]}} have been replaced by [[pushBack]]
* when selecting between two variables, [[select]] is faster than using an [[if]] statement.
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]]
<code>a = "You're " + (["a loser","awesome!"] [[select]] [[true]])
* '''Removing items:''' {{Inline code|myArray [[set]] [1, objNull]; myArray [[a_-_b|-]] [objNull]}} has been replaced by [[deleteAt]] and [[deleteRange]]
//0.0046 ms
* '''Concatenating:''' {{Inline code|myArray {{=}} myArray [[valuea_plus_valueb|+]] [element]}} 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)
result = (arrayOfNumbers [[select]] { [[_x]] % 2 == 0 }); {{cc|1.55 ms}}


a = "You're " + ([[if]] [[true]] [[then]] [{"awesome!"},{"a loser"}])
result = [];
//0.0054 ms</code>
{
[[if]] ([[_x]] % 2 == 0) [[then]] { result [[pushBack]] [[_x]]; };
} [[forEach]] arrayOfNumbers; {{cc|2.57 ms}}


====Checking if unit is on foot====
=== String operations ===


<code>[[isNull]] [[objectParent]] [[player]]
[[String]] manipulation has been simplified with the following commands:
//0.0013 ms</code>
* alternative syntax for [[select]]: {{Inline code|string [[select]] index}}
* [[toArray]] and [[toString]] have been ''reinforced'' with [[splitString]] and [[joinString]]


is a little faster than traditional
=== Number operations ===


<code>[[vehicle]] [[player]] == [[player]]
* [[BIS_fnc_linearConversion]] has been replaced by [[linearConversion]]. The command is '''9 times faster'''.
//0.0022 ms</code>
* [[BIS_fnc_selectRandomWeighted]] has been replaced by [[selectRandomWeighted]]. The command is '''7 times faster'''.


====createVehicle(Local)====
=== Type comparison ===
createVehicle(Local) position is not exact so you must use setPos but this is very slow, to create the object on [0,0,0] and then set the position is faster.


<code>_obj = 'Land_Stone_4m_F' createVehicle [0,0,0]; //also createVehicleLocal
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]].
_obj setPos (getPos player); //0,03ms (100 testcycles)</code>


is 200 times faster than...
=== Multiplayer ===


<code>_obj = 'Land_Stone_4m_F' createVehicle (getPos player); //also createVehicleLocal
* [[BIS_fnc_MP]] has been replaced by [[remoteExec]] and [[remoteExecCall]] and internally uses them. Use the engine commands from now on!
_obj setPos (getPos player); //5,9ms (100 testcycles)</code>


====createSimpleObject vs createVehicle====
=== Parameters ===
[[createSimpleObject]] is over 43x faster than [[createVehicle]]!
<code>createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"];// ~3.5 ms</code>
<code>createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]];// ~0.08 ms</code>
 
==== private ["_var"] vs private _var ====
<code>private ["_a", "_b", "_c", "_d"];
_a = 1; _b = 2; _c = 3; _d = 4;
// 0.0040 ms</code>
 
is slower than the new "[[private]] _var = value" syntax (Arma 3 v1.53+):
 
<code>private _a = 1; private _b = 2; private _c = 3; private _d = 4;
// 0.0023 ms</code>
 
However,
 
<code>private ["_a", "_b", "_c", "_d"];
for "_i" from 1 to 100 do
{
_a = 1; _b = 2; _c = 3; _d = 4;
};
// 0.146327 ms</code>
 
is usually faster than
 
<code>for "_i" from 1 to 100 do
{
private _a = 1; private _b = 2; private _c = 3; private _d = 4;
};
// 0.186776 ms</code>
 
== Resolve any script errors ==
 
If command is throwing an error because of incorrect or illegal input, it will write this into .rpt file regardless of whether or not ''-showScriptErrors'' is enabled. Many mission makers choose to disable onscreen errors, however this may degrade game performance significantly if errors are not dealt with. Compare the following:
<code>[[systemChat]] "123"; // execution time ~0.00271ms
[[systemChat]] 123; // obvious type error, execution time ~0.172206ms, 63 times slower!</code>
 
==How to test and gain this information yourself?==
 
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)
 
<code><nowiki>
_fnc_dump = {
player globalchat str _this;
diag_log str _this;
//copytoclipboard str _this;
};


_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: Scripting Topics]]

Revision as of 19:02, 13 September 2019

Template:SideTOC

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

Introduction

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

Template:quote 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.
Template:colorball means you must change your ways today, or with us you will ride…

Template:colorball means you may want to look at it if you are targeting pure performance

Template:colorball 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 full article for more information.

Template:colorball Lazy evaluation

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

Template:colorball Successive condition check

In SQF the following code will check all and every conditions, even if one fails:

if (condition1 && condition2 && condition3) then { /* thenCode */ };

This code will check condition1, and if it is not true condition2 and condition3 will execute anyway. To avoid this behaviour, you can either imbricate ifs or use lazy evaluation such as the following:

if (condition1 && { condition2 } && { condition3 }) then { /* thenCode */ };

This method will stop condition evaluation on the first false statement.

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:

["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

Template:colorball Concatenating multiple small strings together

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

Template:colorball Manipulating arrays

Adding 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

Removing 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

Template:colorball 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

Template:colorball 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 }];


Template:colorball 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 informations like line numbers or file informations).

Template:colorball if

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

Template:colorball 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

Template:colorball 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

Template:colorball 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"];				// ? ms
"bar" find ["foo", "Bar", "bar", "BAR"] > -1;		// ? ms

Template:colorball 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

Template:colorball 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 a 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

Template:colorball 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

Template:colorball format vs str

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

Template:colorball + vs format vs joinString

[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

Template:colorball 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

Template:colorball isNil

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

Template:colorball isEqualType and typeName

isEqualType is much faster than typeName

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

Template:colorball isEqualTo and count

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

Template:colorball select and param

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

Template:colorball objectParent and vehicle

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

Template:colorball 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.

Template:colorball 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

Template:colorball 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"

Template:colorball getPos* and setPos*

getPosWorld							// 0.0015 ms
getPosASL							// 0.0016 ms
getPosATL							// 0.0016 ms
getPos								// 0.0020 ms
position							// 0.0020 ms
getPosVisual						// 0.0021 ms
visiblePosition						// 0.0021 ms
getPosASLW							// 0.0023 ms
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"

Template:colorball toLower/toUpper vs toLowerANSI/toUpperANSI

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

toLowerANSI _myString;	// 0.0006 ms
toLower _myString;		// 0.0016 ms 

Conversion from earlier versions

Each iteration of Bohemia games (Operation Flashpoint, Arma, 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

String operations

String manipulation has been simplified with the following commands:

Number operations

Type comparison

Multiplayer

Parameters