Code Optimisation: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
No edit summary
m (Fix Successive condition check (lazy evaluation) example)
(39 intermediate revisions by 9 users not shown)
Line 1: Line 1:
== Make it work. ==
{{SideTOC}}
== 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 ==
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.


== Make it fast. ==
With that being said, here are the three basic rules to get yourself in the clear:


Optimisation is everything when running lots of instances, with low delays. However, there is such thing as premature optimisation. Also, avoid excessive cleverness.
# [[#Make it work|Make it work]]
# [[#Make it readable|Make it readable]]
# [[#Optimise then|Optimise then]]


''"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 ===
''
{{note|"Premature optimization is the root of all evil." – ''[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.


====Written it twice? Put it in a function====
* 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]]''.
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")
* 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.


==== Preprocessfilelinenumbers ====
=== Make it readable ===
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:
Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:
<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.
* 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-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 and commands will naturally make the code more readable, especially for long names.


====Length====
{{Informative| '''_i''' is an accepted variable standard for a [[for]]..[[do]] iteration}}
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====
See the following code:
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:
_w=[]; {_w pushbackunique primaryweapon _x} foreach((allunits+alldeadmen) select{_x call bis_fnc_objectside==east});


<code>_arr = [1,2];
The same example is far more readable with proper spacing, good variable names and intermediate results:
_one = _arr [[select]] 0;
_weaponNames = [];
_two = _arr [[select]] 1;
_allUnitsAliveAndDead = allUnits + allDeadMen;
_three = _one + _two;
  _allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east };
</code>
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;
 
<!--
<code>_arr = [1,2];
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
_three = (_arr [[select]] 0) + (_arr [[select]] 1);
-->
</code>
 
====Variable Names====
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.
<code>_pN = "John Smith"; //this line executes in half the time of the line below
_playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction = "John Smith";</code>
 
====Conditions====
 
<code><nowiki>if (_group knowsAbout vehicle _object > 0 && alive _object && canMove _object && count magazines _object > 0) then {
//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 {
      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 {
            //custom code
};</nowiki></code>
 
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:
 
<code>['[[true]] || {[[false]]} || {[[false]]}'] [[call]] [[BIS_fnc_codePerformance]]; //fastest
['[[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====
 
[[isNil]] [[String]] is quite a bit faster than [[isNil]] [[Code]]
 
<code>var = 123;
[[isNil]] "var";
// is faster than
[[isNil]] {var};</code>
 
== Make it pretty. ==
 
Documentation, readability, and all that jazz. Clean code is good code.
 
====If Else If Else If Else ...====
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]]:
<code>[[call]] {
[[if]] (cond1) [[exitWith]] {//code 1};
[[if]] (cond2) [[exitWith]] {//code 2};
[[if]] (cond3) [[exitWith]] {//code 3};
//default code
};</code>
 
 
[[if]] () [[then]] {} <br/> is faster than <br/> [[if]] () [[exitWith]] {} <br/> is faster than <br/> [[if]] () [[then]] {} [[else]] {} <br/> or <br/> [[if]] () [[then]] [{},{}]
 
However there is no noticeable difference in speed in the following:
 
<code>_a = 0; [[if]] ([[true]]) [[then]] {_a = 1};
_a = [[if]] ([[true]]) [[then]] [{1},{0}];
_a = [[if]] ([[true]]) [[then]] {1} [[else]] {0};
</code>
 
==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:
<code><nowiki>a = _x + 1.053;
b = _y + 1.053;</nowiki></code>


==== 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
And
_buffer = 1.053;
a = _x + _buffer;
b = _y + _buffer;
Becomes
<span style="color: purple; font-weight: bold;">#define BUFFER 1.053</span> {{codecomment|// note: no semicolon}}
_a = _x + BUFFER;
_b = _y + BUFFER;
{{note|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:


<code><nowiki>_buffer = 1.053;
Declaration in [[Description.ext]]:
a = _x + _buffer;
<syntaxhighlight lang="cpp">
b = _y + _buffer;</nowiki></code>
var1 = 123;
var2 = "123";
var3[] = {1,2,3};
rand = __EVAL(random 999);
</syntaxhighlight>


Becomes:
Usage in code:
<code><nowiki>#define BUFFER 1.053
[[hint]] [[str]] [[getMissionConfigValue]] "var1"; // 123 {{codecomment|// 0.0007 ms}}
[[hint]] [[str]] [[getMissionConfigValue]] "var2"; // "123" {{codecomment|// 0.0008 ms}}
[[hint]] [[str]] [[getMissionConfigValue]] "var3"; // [1,2,3] {{codecomment|// 0.0017 ms}}
[[hint]] [[str]] [[getMissionConfigValue]] "rand"; // constant random, for example 935.038 {{codecomment|// 0.0007 ms}}


_a = _x + BUFFER;
The [[getMissionConfigValue]] command searching [[Description.ext]] from top to bottom,
_b = _y + BUFFER;
it is better for a matter of performance to put all your definitions at the top of the file.
</nowiki></code>
This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant is it.


==Loops==
=== Optimise then ===
Once you know what is what, you can understand your code better.
* Use private variables instead of global variables (preceded with 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 with {{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
{ _opforUnitUniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _opforUnitUniform; } [[forEach]] _allOpforUnits;
becomes
{ _uniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _uniform; } [[forEach]] _allOpforUnits;


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


*for "_y" from # to # step # do { ... };
== Code optimisation ==
*{ ... } foreach [ ... ];
{{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.}}


Where as these two loops are much slower, and for maximum performance, avoided.
{{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.
}}


*while { expression } do { code };
=== Scheduled and unscheduled environment ===
*for [{ ... },{ ... },{ ... }] do { ... }
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!
{{note|See [[Scheduler]] full article for more information.}}


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|orange|0.9}} Lazy evaluation ===
[[private]] _myVar = [33, 66] [[select]] ([[false]]); {{codecomment|// 0.0013 ms}}
[[private]] _myVar = [[if]] ([[false]]) [[then]] { 33; } [[else]] { 66; }; {{codecomment|// 0.0020 ms}}
[[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{codecomment|// 0.0025 ms}}


*[[waitUntil]] {expression};
=== {{colorball|orange|0.9}} Successive condition check ===
In [[SQF syntax|SQF]] the following code will check '''all and every''' conditions, even if one fails:
[[if]] (condition1 && condition2 && condition3) [[then]] { {{codecomment|/* 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 [[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.


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


<code><nowiki>fA = {
=== {{colorball|red|0.9}} Concatenating multiple small strings together ===
private "_i";
{{Inline code|myString {{=}} myString + otherString}} works fine for small strings, however the bigger the string gets the slower the operation becomes:
_i = 0;
myString = ""; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] { myString = myString + "123" }; {{codecomment|// 290 ms}}
while {_i < 1000} do {
_i = _i + 1;
private "_t";
_t = "0";
};
};</nowiki></code>


<code><nowiki>fB = {
The solution is to use a string array that you will concatenate later:
for "_i" from 0 to 1000 do {
strings = [];
private "_t";
[[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {strings [[pushBack]] "123"};
_t = "0";
strings = strings [[joinString]] ""; {{codecomment|// 30 ms}}
};
};</nowiki></code>


This code then performs 10,000 tests and returns average time taken for the function, measured via diag_ticktime.
=== {{colorball|red|0.9}} Manipulating arrays ===
<code><nowiki>
[fA,[],10000] call CBA_fnc_benchmarkFunction;
[fB,[],10000] call CBA_fnc_benchmarkFunction;
</nowiki></code>


====10,000 Iterations Limit in Loops====
==== Adding elements ====
New commands [[append]] and [[pushBack]] hold the best score.
_array = [0,1,2,3]; _array [[append]] [4,5,6]; {{codecomment|// 0.0020 ms}}
_array = [0,1,2,3]; _array = _array + [4,5,6]; {{codecomment|// 0.0023 ms}}
_array = [0,1,2,3]; { _array [[set]] <nowiki>[</nowiki>[[count]] _array, [[_x]]]; } [[forEach]] [4,5,6]; {{codecomment|// 0.0080 ms}}


A [[while]] [[do]] loop will be limited to 10,000 iteration in non-scheduled environment. In scheduled environment such limit does not apply.
_array = [0,1,2,3]; _array [[pushBack]] 4; {{codecomment|// 0.0016 ms}}
_array = [0,1,2,3]; _array = _array + [4]; {{codecomment|// 0.0021 ms}}
_array = [0,1,2,3]; _array [[set]] <nowiki>[</nowiki>[[count]] _array, [[_x]]]; {{codecomment|// 0.0022 ms}}


==Threads==
==== Removing elements ====
The game runs in a scheduled environment, and there are two ways you can run your code. Scheduled and non scheduled.
_array = [0,1,2,3]; _array [[deleteAt]] 0; {{codecomment|// 0.0015 ms}}
_array = [0,1,2,3]; _array [[set]] [0, [[objNull]]]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{codecomment|// 0.0038 ms}}


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.
_array = [0,1,2,3]; _array [[deleteRange]] [1, 2]; {{codecomment|// 0.0018 ms}}
_array = [0,1,2,3]; { _array [[set]] <nowiki>[</nowiki>[[_x]], [[objNull]]] } [[forEach]] [1,2]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{codecomment|// 0.0078 ms}}


Some basic examples:
=== {{colorball|red|0.9}} 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<br /><span style="font-size: 0.9em;">(''and not '''playerNameBecauseThePlayerIsImportantAndWeNeedToKnowWhoTheyAreAllTheTimeEspeciallyInsideThisImpressiveFunction''''')</span>
* Use, use and use [[remoteExec]] &amp; [[remoteExecCall]]. Ditch [[BIS_fnc_MP]] for good!
<!--
=== {{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"?'')
-->


*Triggers are inside what we call the 'non-scheduled' environment;
== Equivalent commands performance ==
*All pre-init code executions are without scheduling;
*FSM conditions are without scheduling;
*Event handlers (on units and in GUI) are without scheduling;
*Sqf code which called from sqs-code are without scheduling.


====The 3ms run time====
=== {{colorball|green|0.9}} if ===
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.  
[[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{codecomment|// 0.0011 ms}}
[[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{codecomment|// 0.0014 ms}}
[[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{codecomment|// 0.0015 ms}}
[[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{codecomment|// 0.0016 ms}}


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.
=== {{colorball|green|0.9}} if and select ===
Use {{Inline code|[array] [[select]] [[Boolean]]}} instead of the lazy-evaluated [[if]].
_result = ["false result", "true result"] [[select]] [[true]]; {{codecomment|// 0.0011 ms}}
_result = [[if]] ([[true]]) [[then]] { "true result"; } [[else]] { "false result"; }; {{codecomment|// 0.0017 ms}}


Scheduled scripts always start with slight delay, subject to engine load.
=== {{colorball|orange|0.9}} if and switch ===
_result = [[call]] {
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[true]])  [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
[[if]] ([[false]]) [[exitWith]] {};
}; {{codecomment|// 0.0032 ms}}


====When am I creating new threads?====
_result = [[switch]] ([[true]]) [[do]] {
[[case]] ([[false]]): {};
[[case]] ([[false]]): {};
[[case]] ([[true]]) : {};
[[case]] ([[false]]): {};
[[case]] ([[false]]): {};
}; {{codecomment|// 0.0047 ms}}


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!).
=== {{colorball|orange|0.9}} for ===
The {{Inline code|[[for]]..[[from]]..[[to]]..[[do]]}} is twice as fast as its alternative syntax, {{Inline code|[[for]]..[[do]]}}.
[[for]] "_i" [[from]] 0 [[to]] 10 [[do]] { {{codecomment|/* forCode */}} }; {{codecomment|// 0.015 ms}}
[[for]] [{_i = 0}, {_i < 100}, {_i = _i + 1}] [[do]] { {{codecomment|/* forCode */}} }; {{codecomment|// 0.030 ms}}


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.
=== {{colorball|green|0.9}} 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.<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.


==Avoid O(n^2)!!==
{ [[diag_log]] [[_x]] } [[count]]  [1,2,3,4,5]; {{codecomment|// 0.082 ms}}
{ [[diag_log]] [[_x]] } [[forEach]] [1,2,3,4,5]; {{codecomment|// 0.083 ms}}


Commonly you may set up foreach foreach's.
{{codecomment|// with an empty array}}
'For' example:
_someoneIsNear = ([[allUnits]] [[findIf]] { [[_x]]  [[distance]] [0,0,0] < 1000 }) != -1; {{codecomment|// 0.0046 ms}}
_someoneIsNear = { [[_x]] [[distance]] [0,0,0] < 1000 } [[count]] [[allUnits]] > 0; {{codecomment|// '''0.0047 ms'''}}
_someoneIsNear = {
[[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] };
[[false]]
} [[forEach]] [[allUnits]]; {{codecomment|// 0.0060 ms}}


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


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.
=== {{colorball|red|0.9}} 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 }; {{codecomment|// 0.0050 ms}}
{ [[if]] ([[_x]] == 2) [[exitWith]] { [[_forEachIndex]]; }; } [[forEach]] [0,1,2,3,4,5,6,7,8,9]; {{codecomment|// 0.0078 ms}}
_quantity = { [[_x]] == 2 } [[count]] [0,1,2,3,4,5,6,7,8,9]; {{codecomment|// 0.0114 ms}}


==Deprecated/Slow Commands==
=== {{colorball|green|0.9}} + and format ===
When concatenating more than two strings, [[format]] is faster than [[valuea_plus_valueb|+]].
[[format]] ["%1%2%3%4%5", "string1", "string2", "string3", "string4", "string5"]; {{codecomment|// 0.0019 ms}}
"string1" + "string2" + "string3" + "string4" + "string5"; {{codecomment|// 0.0021 ms}}


====Adding elements to an [[Array|array]]====
=== {{colorball|green|0.9}} format and str ===
* [[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.
[[str]] 33; {{codecomment|// 0.0016 ms}}
<code><nowiki>
[[format]] ["%1", 33]; {{codecomment|// 0.0022 ms}}
_a pushBack _v
</nowiki></code>


* [[set]] is around 2x faster than [[plus_a|binary addition]]  
=== {{colorball|orange|0.9}} private ===
<code><nowiki>
Direct declaration ({{Inline code|[[private]] _var {{=}} value}}) is faster than declaring ''then'' assigning the variable.
_a set [count _a,_v]
[[private]] _a = 1;
</nowiki></code>
[[private]] _b = 2;
[[private]] _c = 3;
[[private]] _d = 4;
{{codecomment|// 0.0023 ms}}


Instead of:
[[private]] ["_a", "_b", "_c", "_d"];
<code><nowiki>
_a = 1;
_a = _a + [_v]
_b = 2;
</nowiki></code>
_c = 3;
_d = 4;
{{codecomment|// 0.0040 ms}}


====Removing elements from an [[Array|array]]====
However, if you have to reuse the same variable in a loop, external declaration is faster.<br />
[[deleteAt]] - Removes array element at the given index and returns removed element (modifies the original array, just like [[resize]] or [[set]])
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;
};
{{codecomment|// 0.0195 ms}}


<code><nowiki>_array = [1,2,3]
[[for]] "_i" [[from]] 1 [[to]] 10 [[do]]
_array deleteAt 1;
{
systemChat str _array; // -> [1,3]
[[private]] _a = 1; [[private]] _b = 2; [[private]] _c = 3; [[private]] _d = 4;
</nowiki></code>
};
{{codecomment|// 0.0235 ms}}


Faster than...
=== {{colorball|orange|0.9}} isNil ===
[[isNil]] "varName"; {{codecomment|// 0.0007 ms}}
[[isNil]] {varName}; {{codecomment|// 0.0012 ms}}


When FIFO removing elements from an array, the set removal method works best, even if it makes a copy of the new array.
=== {{colorball|red|0.9}} isEqualType and typeName ===
[[isEqualType]] is much faster than [[typeName]]
"string" [[isEqualType]] 33; {{codecomment|// 0.0006 ms}}
[[typeName]] "string" == [[typeName]] 33; {{codecomment|// 0.0018 ms}}


<code><nowiki>ARRAYX set [0, objnull];
=== {{colorball|green|0.9}} isEqualTo and count ===
ARRAYX = ARRAYX - [objnull];
{{codecomment|// with a items array}}
</nowiki></code>
[[allUnits]] [[isEqualTo]] []; {{codecomment|// 0.0040 ms}}
[[count]] [[allUnits]] == 0; {{codecomment|// 0.0043 ms}}


====Combining [[Array|array]]s====
=== {{colorball|green|0.9}} select and param ===
*When adding an array to an existing array variable, [[append]] is fastest
  [1,2,3] [[select]] 0; {{codecomment|// 0.0008 ms}}
<code>arr1 = [1,2,3,4,5,6,7,8,9,0]; arr2 = arr1; arr1 append arr2;
  [1,2,3] [[param]] [0]; {{codecomment|// 0.0011 ms}}
//0.015 ms


arr1 = [1,2,3,4,5,6,7,8,9,0]; arr2 = arr1; arr1 + arr2;
=== {{colorball|orange|0.9}} objectParent and vehicle ===
//0.016 ms (Arma 3 after optimisation)</code>
[[isNull]] [[objectParent]] [[player]]; {{codecomment|// 0.0013 ms}}
[[append]] modifies existing array while "+" produces a copy, hence a little bit slower.  
[[vehicle]] [[player]] == [[player]]; {{codecomment|// 0.0022 ms}}


* When not saving the array to a variable, use +.
=== {{colorball|red|0.9}} nearEntities and nearestObjects ===
<code>([veh1] + _array2) call BIS_fnc_setPitchBank
[[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of objects within the given range.
//0.004 ms
If range is over 100 meters it is highly recommended to use [[nearEntities]] over [[nearestObjects]].
{{codecomment|// tested with a NATO rifle squad amongst solar power plant panels on [[Altis]] at coordinates [20762,15837]}}
[[getPosATL]] [[player]] [[nearEntities]] [["Man"], 50]; {{codecomment|// 0.0075ms}}
[[nearestObjects]] <nowiki>[</nowiki>[[getPosATL]] [[player]], ["Man"], 50]; {{codecomment|// 0.0145ms}}


_array1 = [veh1];
{{Important|[[nearEntities]] only searches for [[alive]] objects and on-foot soldiers.<br />
_array1 append _array2;
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.}}
_array1 call BIS_fnc_setPitchBank
//0.0054 ms</code>


====Comparing [[Array|array]]s====
=== {{colorball|orange|0.9}} 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:


To compare arrays prior Arma 3, use the following function:
<code>SomeGlobalVariable = [123];
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]]
{
SomeGlobalVariable [[select]] 0;
};
{{codecomment|// 0.13 ms}}</code>


<syntaxhighlight lang=javascript>KK_fnc_arraysAreEqual = {str (_this select 0) in [str (_this select 1)]};</syntaxhighlight>
is noticeably slower than


Example:
<code>SomeGlobalVariable = [123];
 
[[private]] _var = SomeGlobalVariable;
<syntaxhighlight lang=javascript>hint str ([[1,2,[3]], [1,2,[3]]] call KK_fnc_arraysAreEqual); //true</syntaxhighlight>
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]]
 
{
In Arma 3 use [[isEqualTo]] command.
_var [[select]] 0;
 
};
====Comparing values by type====
{{codecomment|// 0.08 ms}}</code>
 
<code>"a" [[isEqualType]] 0
//0.0009 ms</code>
 
Is much faster than
 
<code>[[typeName]] "a" == [[typeName]] 0
//0.0032 ms</code>
 
====Checking if array is []====
 
Traditional ([[count]] _arr == 0) is pretty fast, but direct comparison with new comparison command is a little faster: (_arr [[isEqualTo]] [])
 
====Position World is the fastest====
 
[[getPosASL]], [[getPosATL]] and [[visiblePositionASL]] are  faster than [[getPos]], [[position]] and [[visiblePosition]]. But new to Arma 3 command [[getPosWorld]] is the fastest  of them all.
<code>[[getPosWorld]] [[player]]
//0.0014 ms</code>
 
<code>[[getPosASL]] [[player]]
//0.0014 ms</code>
 
<code>[[getPosATL]] [[player]]
//0.0015 ms</code>
 
<code>[[visiblePositionASL]] [[player]]
//0.0014 ms</code>
 
<code>[[visiblePosition]] [[player]]
//0.0048 ms</code>
 
<code>[[getPos]] [[player]]
//or
[[position]] [[player]]
//0.005 ms</code>
 
====Config path delimiter====
''>>'' is slightly faster than ''/'' when used in config path with [[configFile]] or [[missionConfigFile]], i.e.
<code>[[configFile]] >> "CfgVehicles"
//0.0019 ms</code>
is faster than
<code>[[configFile]]/"CfgVehicles"
//0.0023 ms</code>
 
====nearEntities vs nearestObjects====
* [[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.
Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command.
 
====forEach vs count====
* 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];
//is faster than
{[[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;
//is still faster than
_someoneIsNear = {
if (_x [[distance]] [0,0,0] < 1000) [[exitWith]] {[[true]]};
[[false]]
} [[forEach]] [[allUnits]];
</code>
 
====for [] vs for "_i" ====
* 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 +====
* when adding more than two strings, [[format]] is faster than +.
Adding 3 strings:
<code>a = [[format]] ["Hi, my name is %1%2","bob, what's yours","?"]
//0.004 ms
a =  "Hi, my name is " + "bob, what's yours" + "?"
//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====
 
For small strings a = a + b works fine, however the bigger the string gets the slower this becomes:
 
<code>s = ""; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {s = s + "123"}; //30000 chars @ 290ms</code>
 
The solution is to use array to make string and then convert array to string:
 
<code>s = []; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {s [[pushBack]] "123"}; s = s [[joinString]] ""; //30000 chars @ 30ms</code>
 
====select vs if====
* when selecting between two variables, [[select]] is faster than using an [[if]] statement.
<code>a = "You're " + (["a loser","awesome!"] [[select]] [[true]])
//0.0046 ms
 
a = "You're " + ([[if]] [[true]] [[then]] [{"awesome!"},{"a loser"}])
//0.0054 ms</code>
 
====Checking if unit is on foot====
 
<code>[[isNull]] [[objectParent]] [[player]]
//0.0013 ms</code>
 
is a little faster than traditional
 
<code>[[vehicle]] [[player]] == [[player]]
//0.0022 ms</code>
 
====createVehicle(Local)====
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
_obj setPos (getPos player); //0,03ms (100 testcycles)</code>


is 200 times faster than...
=== {{colorball|green|0.9}} Config path delimiter ===
{{Inline code|[[config_greater_greater_name|>>]]}} is slightly faster than {{Inline code|[[config_/_name|/]]}} when used in config path with [[configFile]] or [[missionConfigFile]].
[[configFile]] >> "CfgVehicles"; {{codecomment|// 0.0019 ms}}
[[configFile]]  / "CfgVehicles"; {{codecomment|// 0.0023 ms}}
{{note|A config path can be stored in a variable for later use, saving CPU time: ''{{Inline code|_cfgVehicles {{=}} [[configFile]] >> "CfgVehicles"}}'' }}


<code>_obj = 'Land_Stone_4m_F' createVehicle (getPos player); //also createVehicleLocal
=== {{colorball|orange|0.9}} getPos* and setPos* ===
_obj setPos (getPos player); //5,9ms (100 testcycles)</code>
[[getPosWorld]] {{codecomment|// 0.0015 ms}}
[[getPosASL]] {{codecomment|// 0.0016 ms}}
[[getPosATL]] {{codecomment|// 0.0016 ms}}
[[getPos]] {{codecomment|// 0.0020 ms}}
[[position]] {{codecomment|// 0.0020 ms}}
[[getPosVisual]] {{codecomment|// 0.0021 ms}}
[[visiblePosition]] {{codecomment|// 0.0021 ms}}
[[getPosASLW]] {{codecomment|// 0.0023 ms}}


====createSimpleObject vs createVehicle====
[[setPosWorld]] {{codecomment|// 0.0060 ms}}
[[createSimpleObject]] is over 43x faster than [[createVehicle]]!
[[setPosASL]] {{codecomment|// 0.0060 ms}}
<code>createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"];// ~3.5 ms</code>
[[setPosATL]] {{codecomment|// 0.0060 ms}}
<code>createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]];// ~0.08 ms</code>
[[setPos]] {{codecomment|// 0.0063 ms}}
[[setPosASLW]] {{codecomment|// 0.0068 ms}}
[[setVehiclePosition]] {{codecomment|// 0.0077 ms with "CAN_COLLIDE"}}
{{codecomment|// 0.0390 ms with "NONE"}}


==== 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+):
== Conversion from earlier versions ==
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>private _a = 1; private _b = 2; private _c = 3; private _d = 4;
=== Loops ===
// 0.0023 ms</code>
* [[forEach]] loops, depending on the situation, can be replaced by:
** [[apply]]
** [[count]]
** [[findIf]]
** [[select]]


== Resolve any script errors ==
=== Array operations ===
* '''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]]
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]]
* '''Removing items:''' {{Inline code|myArray [[set]] [1, objNull]; myArray [[a_-_b|-]] [objNull]}} has been replaced by [[deleteAt]] and [[deleteRange]]
* '''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 }); {{codecomment|// 1.55 ms}}


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:
result = [];
<code>[[systemChat]] "123"; // execution time ~0.00271ms
{
[[systemChat]] 123; // obvious type error, execution time ~0.172206ms, 63 times slower!</code>
[[if]] ([[_x]] % 2 == 0) [[then]] { result [[pushBack]] [[_x]]; };
} [[forEach]] arrayOfNumbers; {{codecomment|// 2.57 ms}}


==How to test and gain this information yourself?==
=== String operations ===
[[String]] manipulation has been simplified with the following commands:
* alternative syntax for [[select]]: {{Inline code|string [[select]] index}}
* [[toArray]] and [[toString]] have been ''reinforced'' with [[splitString]] and [[joinString]]


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)
=== 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'''.


<code><nowiki>
=== Type comparison ===
_fnc_dump = {
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]].
player globalchat str _this;
diag_log str _this;
//copytoclipboard str _this;
};


_t1 = diag_tickTime;
=== Multiplayer ===
// ... code to test
* [[BIS_fnc_MP]] has been replaced by [[remoteExec]] and [[remoteExecCall]] and internally uses them. Use the engine commands from now on!
(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.
=== Parameters ===
* [[BIS_fnc_param]] has been replaced by [[param]] and [[params]]. The commands are approximately '''14 times''' faster. Use them!


[[Category: Scripting_Topics ]]
[[Category: Scripting Topics]]

Revision as of 11:17, 19 June 2019

Template:SideTOC

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:note 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 and commands will naturally make the code more readable, especially for long names.
_i is an accepted variable standard for a for..do iteration

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;

Template:note 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 with 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?
    • Find a smaller name, according to the variable scope:

e.g

{ _opforUnitUniform = uniform _x; systemChat _opforUnitUniform; } forEach _allOpforUnits;

becomes

{ _uniform = uniform _x; systemChat _uniform; } forEach _allOpforUnits;


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!

Template:note

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!

Equivalent commands performance

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 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 + and format

When concatenating more than two strings, format is faster than +.

format ["%1%2%3%4%5", "string1", "string2", "string3", "string4", "string5"];	// 0.0019 ms
"string1" + "string2" + "string3" + "string4" + "string5";						// 0.0021 ms

Template:colorball format and str

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

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.0075ms
nearestObjects [getPosATL player, ["Man"], 50];	// 0.0145ms
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

Template:note

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"


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