Code Optimisation: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Text replacement - "<br />" to "<br>")
(43 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<div style="float: right; margin-left: 1.5em; margin-bottom: 1em; max-width: 25%;">__TOC__</div>
{{SideTOC|0.9}}
{{ Informative | This page is about [[Code Optimisation]]. For ''conception'' optimisation, see [[Mission Optimisation]]. }}
== Introduction ==
== Introduction ==
This article will try to be a general guide about improving your code '''and''' its performance.
This article will try to be a general guide about improving your code '''and''' its performance.
Line 9: Line 10:


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


Line 18: Line 20:


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


Line 26: Line 29:


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


Line 35: Line 39:
* 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.
* 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.
* 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.
* Finally, camel-casing (namingLikeThis) your variables will naturally make the code more readable, especially for long names.


{{Informative| '''_i''' is an accepted variable standard for a [[for]]..[[do]] iteration}}
{{Informative | See '''[[Code Best Practices]]''' for more information.}}


See the following code:
See the following code:
Line 48: Line 52:
  { _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;
  { _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;
<!--
<!--
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes.
 
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
 
-->
-->


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


This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant anymore.
Declaration in [[Description.ext]]:
<syntaxhighlight lang="cpp">
var1 = 123;
var2 = "123";
var3[] = {1,2,3};
rand = __EVAL(random 999);
</syntaxhighlight>
 
Usage in code:
[[hint]] [[str]] [[getMissionConfigValue]] "var1"; // 123 {{cc|0.0007 ms}}
[[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}}
 
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 ===
=== Optimise then ===
Once you know what is what, you can understand your code better.
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
* 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 were iterating multiple times on the same array?
** You should be able to spot your issue now.
** You should be able to spot your issue now.
* Are you using [[execVM]] on the same file, many times?
* 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";}}
** 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?
* Is your variable name far too long?
** Find a smaller name, according to the variable scope:
** Find a smaller name, according to the variable scope; e.g:<br><!--
e.g
--><code>{ _opforUnitUniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _opforUnitUniform; } [[forEach]] _allOpforUnits;</code><!--
{ _opforUnitUniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _opforUnitUniform; } [[forEach]] _allOpforUnits;
-->becomes <code>{ _uniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _uniform; } [[forEach]] _allOpforUnits;</code>
becomes
{ _uniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _uniform; } [[forEach]] _allopforUnits;




== Code optimisation ==
== Code optimisation ==
{{Important|
{{Important|
'''Please note:''' tests and benchmarks were done with the latest {{arma3}} version at the time (v1.82, Tanks DLC). Game engine performance may have changed since.<br />Benchmark result in milliseconds (ms) is an average for '''10000''' iterations.}}
'''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.}}


{{Informative|
{{Informative|
{{colorball|red|1.125}}    means you '''must''' change your ways today, ''or with us you will ride…''<br />
{{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|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.
{{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.
}}
}}


=== Scheduled and unscheduled environment ===
=== Scheduled and unscheduled environment ===
There are two code environment types, [[Scheduler#Scheduled_Environment|scheduled]] and [[Scheduler#Unscheduled_Environment|unscheduled]].
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.
* 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!
* 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.}}
{{Informative | See [[Scheduler]] full article for more information.}}


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


=== {{colorball|orange|0.9}} Successive condition check ===
=== {{colorball|orange|0.9}} Successive condition check ===
In [[SQF syntax|SQF]] the following code will check '''all and every''' condition, even if one fail:
 
In [[SQF syntax|SQF]] the following code will check '''all and every''' conditions, even if one fails:
  [[if]] (condition1 && condition2 && condition3) [[then]] { {{codecomment|/* thenCode */}} };
  [[if]] (condition1 && condition2 && condition3) [[then]] { {{codecomment|/* thenCode */}} };
This code will check ''condition1'', and if it is not [[true]] ''condition2'' and ''condition3'' will execute anyway.
This code will check ''condition1'', and if it is not [[true]] ''condition2'' and ''condition3'' will execute anyway.
Line 111: Line 137:


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


=== {{colorball|red|0.9}} Concatenating multiple small strings together ===
=== {{colorball|red|0.9}} Concatenating multiple small strings together ===
{{Inline code|myString {{=}} myString + otherString}} works fine for small strings, however the bigger the string gets the slower the operation becomes:
{{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" }; {{codecomment|// 290 ms}}
  myString = ""; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] { myString = myString + "123" }; {{cc|290 ms}}


The solution is to use a string array that you will concatenate later:
The solution is to use a string array that you will concatenate later:
  strings = [];
  strings = [];
  [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {strings [[pushBack]] "123"};
  [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {strings [[pushBack]] "123"};
  strings = strings [[joinString]] ""; {{codecomment|// 30 ms}}
  strings = strings [[joinString]] ""; {{cc|30 ms}}


=== {{colorball|red|0.9}} Manipulating arrays ===
=== {{colorball|red|0.9}} Manipulating arrays ===
Line 130: Line 157:
==== Adding elements ====
==== Adding elements ====
New commands [[append]] and [[pushBack]] hold the best score.
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 [[append]] [4,5,6]; {{cc|0.0020 ms}}
  _array = [0,1,2,3]; _array = _array + [4,5,6]; {{codecomment|// 0.0023 ms}}
  _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]; {{codecomment|// 0.0080 ms}}
  _array = [0,1,2,3]; { _array [[set]] [<nowiki/>[[count]] _array, [[_x]]]; } [[forEach]] [4,5,6]; {{cc|0.0080 ms}}


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


==== Removing elements ====
==== Removing elements ====
  _array = [0,1,2,3]; _array [[deleteAt]] 0; {{codecomment|// 0.0015 ms}}
  _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]]]; {{codecomment|// 0.0038 ms}}
  _array = [0,1,2,3]; _array [[set]] [0, [[objNull]]]; _array = _array - [<nowiki/>[[objNull]]]; {{cc|0.0038 ms}}


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


=== {{colorball|red|0.9}} Multiplayer recommendations ===
=== {{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!
* 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
* 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>
* [[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!
* 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 ===
=== {{colorball|red|0.9}} createSimpleObject vs createVehicle ===
Line 162: Line 191:


== Equivalent commands performance ==
== Equivalent commands performance ==
=== {{colorball|orange|0.9}} call ===
[[call]] without arguments is faster than call with arguments:
[[call]] {}; {{cc|0.0007 ms}}
123 [[call]] {}; {{cc|0.0013 ms}}
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:
[[player]] [[addEventHandler]] ["HandleDamage", { [[call]] my_fnc_damage }];
instead of:
[[player]] [[addEventHandler]] ["HandleDamage", { _this [[call]] my_fnc_damage }];
<!-- TODO: compare execVM and spawn, and/or waitUntil scriptDone / call
=== {{colorball|red|0.9}} execVM and call ===
Using [[execVM]] multiple times make the game read the file and recompile it every time.<br>
If you will use the script more than once, store its code in a variable:
{{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}}
{{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}}
{{cc|myFile.sqf is a '''missing''' file}}
_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";
[[call]] _myFunction; {{cc|0.0009 ms}}
[[execVM]] "myFile.sqf"; {{cc|0.275 ms}}
-->
=== {{colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers ===
{{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}}
{{cc|myFile.sqf is [[BIS_fnc_showRespawnMenu]]}}
[[loadFile]] "myFile.sqf"; {{cc|0.3516 ms}}
[[preprocessFile]] "myFile.sqf"; {{cc|2.75 ms}}
[[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|2.73 ms}}
{{cc|myFile.sqf is a '''missing''' file}}
[[loadFile]] "myFile.sqf"; {{cc|0.692 ms}}
[[preprocessFile]] "myFile.sqf"; {{cc|0.6225 ms}}
[[preprocessFileLineNumbers]] "myFile.sqf"; {{cc|0.6225 ms}}
{{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).}}


=== {{colorball|green|0.9}} if ===
=== {{colorball|green|0.9}} if ===
  [[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{codecomment|// 0.0011 ms}}
 
  [[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{codecomment|// 0.0014 ms}}
  [[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{cc|0.0011 ms}}
  [[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{codecomment|// 0.0015 ms}}
  [[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{cc|0.0014 ms}}
  [[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{codecomment|// 0.0016 ms}}
  [[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{cc|0.0015 ms}}
  [[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{cc|0.0016 ms}}


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


=== {{colorball|orange|0.9}} if and switch ===
=== {{colorball|orange|0.9}} if and switch ===
  _result = [[call]] {
  _result = [[call]] {
  [[if]] ([[false]]) [[exitWith]] {};
  [[if]] ([[false]]) [[exitWith]] {};
Line 181: Line 270:
  [[if]] ([[false]]) [[exitWith]] {};
  [[if]] ([[false]]) [[exitWith]] {};
  [[if]] ([[false]]) [[exitWith]] {};
  [[if]] ([[false]]) [[exitWith]] {};
  }; {{codecomment|// 0.0032 ms}}
  }; {{cc|0.0032 ms}}


  _result = [[switch]] ([[true]]) [[do]] {
  _result = [[switch]] ([[true]]) [[do]] {
Line 189: Line 278:
  [[case]] ([[false]]): {};
  [[case]] ([[false]]): {};
  [[case]] ([[false]]): {};
  [[case]] ([[false]]): {};
  }; {{codecomment|// 0.0047 ms}}
  }; {{cc|0.0047 ms}}
 
=== {{colorball|orange|0.9}} in vs find ===
 
{{cc|[[String]] search}}
"bar" [[in]] "foobar" {{cc|0.0008 ms}}
"foobar" [[find]] "bar" > -1 {{cc|0.0012 ms}}
 
{{cc|[[Array]] search - '''case-sensitive'''}}
"bar" [[in]] ["foo", "Bar", "bar", "BAR"]; {{cc|? ms}}
"bar" [[find]] ["foo", "Bar", "bar", "BAR"] > -1; {{cc|? ms}}


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


=== {{colorball|green|0.9}} forEach vs count vs findIf ===
=== {{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.
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 />
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]].
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]; {{codecomment|// 0.082 ms}}
  { [[diag_log]] [[_x]] } [[count]]  [1,2,3,4,5]; {{cc|0.082 ms}}
  { [[diag_log]] [[_x]] } [[forEach]] [1,2,3,4,5]; {{codecomment|// 0.083 ms}}
  { [[diag_log]] [[_x]] } [[forEach]] [1,2,3,4,5]; {{cc|0.083 ms}}


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


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


{{colorball|red|1.125}} [[findIf]] (since {{arma3}} v1.82) stops array iteration as soon as the condition is met.
=== {{colorball|red|0.9}} findIf ===
[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}}


=== {{colorball|green|0.9}} + and format ===
[[findIf]] stops array iteration as soon as the condition is met.
When concatenating more than two strings, [[format]] is faster than [[valuea_plus_valueb|+]].
[0,1,2,3,4,5,6,7,8,9] [[findIf]] { [[_x]] == 2 }; {{cc|0.0050 ms}}
[[format]] ["%1%2%3%4%5", "string1", "string2", "string3", "string4", "string5"]; {{codecomment|// 0.0019 ms}}
{ [[if]] ([[_x]] == 2) [[exitWith]] { [[_forEachIndex]]; }; } [[forEach]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0078 ms}}
  "string1" + "string2" + "string3" + "string4" + "string5"; {{codecomment|// 0.0021 ms}}
  _quantity = { [[_x]] == 2 } [[count]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0114 ms}}


=== {{colorball|green|0.9}} format and str ===
=== {{colorball|green|0.9}} format vs str ===
  [[str]] 33; {{codecomment|// 0.0016 ms}}
 
  [[format]] ["%1", 33]; {{codecomment|// 0.0022 ms}}
  [[str]] 33; {{cc|0.0016 ms}}
  [[format]] ["%1", 33]; {{cc|0.0022 ms}}
 
=== {{colorball|green|0.9}} + vs format vs joinString ===
 
[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}}


=== {{colorball|orange|0.9}} private ===
=== {{colorball|orange|0.9}} private ===
Direct declaration ({{Inline code|[[private]] _var {{=}} value}} since {{arma3}} v1.53) is faster than declaring ''then'' assigning the variable.
 
Direct declaration ({{Inline code|[[private]] _var {{=}} value}}) is faster than declaring ''then'' assigning the variable.
  [[private]] _a = 1;
  [[private]] _a = 1;
  [[private]] _b = 2;
  [[private]] _b = 2;
  [[private]] _c = 3;
  [[private]] _c = 3;
  [[private]] _d = 4;
  [[private]] _d = 4;
  {{codecomment|// 0.0023 ms}}
  {{cc|0.0023 ms}}


  [[private]] ["_a", "_b", "_c", "_d"];
  [[private]] ["_a", "_b", "_c", "_d"];
Line 247: Line 354:
  _c = 3;
  _c = 3;
  _d = 4;
  _d = 4;
  {{codecomment|// 0.0040 ms}}
  {{cc|0.0040 ms}}


However, if you have to reuse the same variable in a loop, external declaration is faster.<br />
However, if you have to reuse the same variable in a loop, external declaration is faster.<br>
The reason behind this is that a declaration in the loop will create, assign and delete the variable in each loop.<br />
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.
An external declaration creates the variable only once and the loop only assigns the value.
  [[private]] ["_a", "_b", "_c", "_d"];
  [[private]] ["_a", "_b", "_c", "_d"];
Line 257: Line 364:
  _a = 1; _b = 2; _c = 3; _d = 4;
  _a = 1; _b = 2; _c = 3; _d = 4;
  };
  };
  {{codecomment|// 0.0195 ms}}
  {{cc|0.0195 ms}}


  [[for]] "_i" [[from]] 1 [[to]] 10 [[do]]
  [[for]] "_i" [[from]] 1 [[to]] 10 [[do]]
Line 263: Line 370:
  [[private]] _a = 1; [[private]] _b = 2; [[private]] _c = 3; [[private]] _d = 4;
  [[private]] _a = 1; [[private]] _b = 2; [[private]] _c = 3; [[private]] _d = 4;
  };
  };
  {{codecomment|// 0.0235 ms}}
  {{cc|0.0235 ms}}


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


=== {{colorball|red|0.9}} isEqualType and typeName ===
=== {{colorball|red|0.9}} isEqualType and typeName ===
[[isEqualType]] is much faster than [[typeName]]
[[isEqualType]] is much faster than [[typeName]]
  "string" [[isEqualType]] 33; {{codecomment|// 0.0006 ms}}
  "string" [[isEqualType]] 33; {{cc|0.0006 ms}}
  [[typeName]] "string" == [[typeName]] 33; {{codecomment|// 0.0018 ms}}
  [[typeName]] "string" == [[typeName]] 33; {{cc|0.0018 ms}}


=== {{colorball|green|0.9}} isEqualTo and count ===
=== {{colorball|green|0.9}} isEqualTo and count ===
  {{codecomment|// with a items array}}
 
  [[allUnits]] [[isEqualTo]] []; {{codecomment|// 0.0040 ms}}
  {{cc|with a items array}}
  [[count]] [[allUnits]] == 0; {{codecomment|// 0.0043 ms}}
  [[allUnits]] [[isEqualTo]] []; {{cc|0.0040 ms}}
  [[count]] [[allUnits]] == 0; {{cc|0.0043 ms}}
 
=== {{colorball|green|0.9}} select and param ===
 
  [1,2,3] [[select]] 0; {{cc|0.0008 ms}}
  [1,2,3] [[param]] [0]; {{cc|0.0011 ms}}


=== {{colorball|orange|0.9}} objectParent and vehicle ===
=== {{colorball|orange|0.9}} objectParent and vehicle ===
  [[isNull]] [[objectParent]] [[player]]; {{codecomment|// 0.0013 ms}}
 
  [[vehicle]] [[player]] == [[player]]; {{codecomment|// 0.0022 ms}}
  [[isNull]] [[objectParent]] [[player]]; {{cc|0.0013 ms}}
  [[vehicle]] [[player]] == [[player]]; {{cc|0.0022 ms}}


=== {{colorball|red|0.9}} nearEntities and nearestObjects ===
=== {{colorball|red|0.9}} nearEntities and nearestObjects ===
[[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of objects within the given range.
[[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]].
If range is over 100 meters it is highly recommended to use [[nearEntities]] over [[nearestObjects]].
{{cc|tested with a NATO rifle squad amongst solar power plant panels on [[Altis]] at coordinates [20762,15837]}}
[[getPosATL]] [[player]] [[nearEntities]] [["Man"], 50]; {{cc|0.0075 ms}}
[[nearestObjects]] [<nowiki/>[[getPosATL]] [[player]], ["Man"], 50]; {{cc|0.0145 ms}}
{{Important|[[nearEntities]] only searches for [[alive]] objects and on-foot soldiers.<br>
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.}}
=== {{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:
<code>SomeGlobalVariable = [123];
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]]
{
SomeGlobalVariable [[select]] 0;
};
{{cc|0.13 ms}}</code>
is noticeably slower than


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


=== {{colorball|green|0.9}} Config path delimiter ===
=== {{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]].
{{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"; {{cc|0.0019 ms}}
  [[configFile]]  / "CfgVehicles"; {{codecomment|// 0.0023 ms}}
  [[configFile]]  / "CfgVehicles"; {{cc|0.0023 ms}}
{{note|A config path can be stored in a variable for later use, saving CPU time: ''{{Inline code|_cfgVehicles {{=}} [[configFile]] >> "CfgVehicles"}}'' }}
{{Informative | A config path can be stored in a variable for later use, saving CPU time: ''{{Inline code|_cfgVehicles {{=}} [[configFile]] >> "CfgVehicles"}}'' }}


=== {{colorball|orange|0.9}} getPos* and setPos* ===
=== {{colorball|orange|0.9}} getPos* and setPos* ===
[[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}}


  [[setPosWorld]] {{codecomment|// 0.0060 ms}}
  [[getPosWorld]] {{cc|0.0015 ms}}
  [[setPosASL]] {{codecomment|// 0.0060 ms}}
  [[getPosASL]] {{cc|0.0016 ms}}
  [[setPosATL]] {{codecomment|// 0.0060 ms}}
  [[getPosATL]] {{cc|0.0016 ms}}
  [[setPos]] {{codecomment|// 0.0063 ms}}
  [[getPos]] {{cc|0.0020 ms}}
  [[setPosASLW]] {{codecomment|// 0.0068 ms}}
  [[position]] {{cc|0.0020 ms}}
  [[setVehiclePosition]] {{codecomment|// 0.0077 ms with "CAN_COLLIDE"}}
  [[getPosVisual]] {{cc|0.0021 ms}}
{{codecomment|// 0.0390 ms with "NONE"}}
[[visiblePosition]] {{cc|0.0021 ms}}
[[getPosASLW]] {{cc|0.0023 ms}}


[[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"}}
=== {{colorball|orange|0.9}} toLower/toUpper vs toLowerANSI/toUpperANSI ===
{{cc|_myString is a 100 chars "aAaAaA(…)" string}}
[[toLowerANSI]] _myString; {{cc|0.0006 ms}}
[[toLower]] _myString; {{cc|0.0016 ms}}
[[toLowerANSI]] _myString; {{cc|0.0006 ms}}
[[toLower]] _myString; {{cc|0.0016 ms}}


== Conversion from earlier versions ==
== Conversion from earlier versions ==
Each iteration of Bohemia games ({{ofp}}, {{arma}}, {{arma2}}, {{tkoh}}, {{arma3}}) brought their own new commands, especially {{arma2}} and {{arma3}}.<br />
 
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.
For that, if you are converting scripts from older versions of the engine, the following aspects should be reviewed.


=== Loops ===
=== Loops ===
* [[forEach]] loops, depending on the situation, can be replaced by:
* [[forEach]] loops, depending on the situation, can be replaced by:
** [[apply]]
** [[apply]]
Line 327: Line 480:


=== Array operations ===
=== 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]]
 
* '''Adding an item:''' {{Inline code|myArray [[valuea_plus_valueb|+]] [element]}} and {{Inline code|myArray [[set]] [<nowiki/>[[count]] myArray, element]}} have been replaced by [[pushBack]]
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]]
* '''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]]
* '''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 +
* '''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]]
* '''Comparing:''' use [[isEqualTo]] instead of [[BIS_fnc_areEqual]]
* '''Finding common items:''' [[in]] [[forEach]] loop has been replaced by [[arrayIntersect]]
* '''Finding common items:''' [[in]] [[forEach]] loop has been replaced by [[arrayIntersect]]
* '''Condition filtering:''' [[forEach]] can be replaced by [[select]] (alternative syntax)
* '''Condition filtering:''' [[forEach]] can be replaced by [[select]] (alternative syntax)
  result = (arrayOfNumbers [[select]] { [[_x]] % 2 == 0 }); {{codecomment|// 1.55 ms}}
  result = (arrayOfNumbers [[select]] { [[_x]] % 2 == 0 }); {{cc|1.55 ms}}


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


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


=== Number operations===
=== Number operations ===
 
* [[BIS_fnc_linearConversion]] has been replaced by [[linearConversion]]. The command is '''9 times faster'''.
* [[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'''.
* [[BIS_fnc_selectRandomWeighted]] has been replaced by [[selectRandomWeighted]]. The command is '''7 times faster'''.


=== Type comparison ===
=== Type comparison ===
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]].
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]].


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

Revision as of 12:42, 19 March 2020

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