Code Optimisation: Difference between revisions
Lou Montana (talk | contribs) m (Text replacement - "\{\{SideTOC *\| *([0-9]?\.[0-9]+)\}\}" to "{{TOC|side|$1}}") |
Lou Montana (talk | contribs) m (Fix casing) |
||
Line 21: | Line 21: | ||
=== Make it work === | === Make it work === | ||
{{ | {{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 109: | Line 109: | ||
{{Informative| | {{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. | ||
}} | }} | ||
Line 121: | Line 121: | ||
{{Informative | See [[Scheduler]] for more information.}} | {{Informative | See [[Scheduler]] for more information.}} | ||
=== {{ | === {{Colorball|orange|0.9}} Variable assignment === | ||
[[private]] _myVar = [33, 66] [[select]] ([[false]]); {{cc|0.0013 ms}} | [[private]] _myVar = [33, 66] [[select]] ([[false]]); {{cc|0.0013 ms}} | ||
Line 127: | Line 127: | ||
[[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{cc|0.0025 ms}} | [[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{cc|0.0025 ms}} | ||
=== {{ | === {{Colorball|orange|0.9}} Lazy evaluation === | ||
In [[SQF syntax|SQF]] the following code will evaluate every single condition, even if one fails: | In [[SQF syntax|SQF]] the following code will evaluate every single condition, even if one fails: | ||
Line 144: | Line 144: | ||
["[[false]] || {[[false]]} || {[[false]]} ", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00200 ms}} | ["[[false]] || {[[false]]} || {[[false]]} ", [[nil]], 100000] [[call]] [[BIS_fnc_codePerformance]]; {{cc|0.00200 ms}} | ||
=== {{ | === {{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: | ||
Line 154: | Line 154: | ||
strings = strings [[joinString]] ""; {{cc|30 ms}} | strings = strings [[joinString]] ""; {{cc|30 ms}} | ||
=== {{ | === {{Colorball|red|0.9}} Manipulating arrays === | ||
==== Adding elements ==== | ==== Adding elements ==== | ||
Line 173: | Line 173: | ||
_array = [0,1,2,3]; { _array [[set]] [<nowiki/>[[_x]], [[objNull]]] } [[forEach]] [1,2]; _array = _array - [<nowiki/>[[objNull]]]; {{cc|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 === | ||
* 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! | ||
Line 181: | Line 181: | ||
{{Informative | See [[Multiplayer Scripting]] for more information.}} | {{Informative | See [[Multiplayer Scripting]] for more information.}} | ||
<!-- | <!-- | ||
=== {{ | === {{Colorball|red|0.9}} createSimpleObject vs createVehicle === | ||
''TODO'' | ''TODO'' | ||
* [[createSimpleObject]] is over '''43x''' faster than createVehicle! | * [[createSimpleObject]] is over '''43x''' faster than createVehicle! | ||
Line 193: | Line 193: | ||
== Equivalent commands performance == | == Equivalent commands performance == | ||
=== {{ | === {{Colorball|orange|0.9}} call === | ||
[[call]] without arguments is faster than call with arguments: | [[call]] without arguments is faster than call with arguments: | ||
Line 206: | Line 206: | ||
<!-- TODO: compare execVM and spawn, and/or waitUntil scriptDone / call | <!-- 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> | Using [[execVM]] multiple times make the game read the file and recompile it every time.<br> | ||
Line 230: | Line 230: | ||
--> | --> | ||
=== {{ | === {{Colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers === | ||
{{cc|myFile.sqf is an '''empty''' file}} | {{cc|myFile.sqf is an '''empty''' file}} | ||
Line 250: | Line 250: | ||
-->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).}} | -->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 === | ||
[[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{cc|0.0011 ms}} | [[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{cc|0.0011 ms}} | ||
Line 257: | Line 257: | ||
[[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{cc|0.0016 ms}} | [[if]]..[[then]] [{ {{codecomment|/* thenCode */}} }, { {{codecomment|/* elseCode */}} }] {{cc|0.0016 ms}} | ||
=== {{ | === {{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]]. | ||
Line 263: | Line 263: | ||
_result = [[if]] ([[true]]) [[then]] { "true result"; } [[else]] { "false result"; }; {{cc|0.0017 ms}} | _result = [[if]] ([[true]]) [[then]] { "true result"; } [[else]] { "false result"; }; {{cc|0.0017 ms}} | ||
=== {{ | === {{Colorball|orange|0.9}} if and switch === | ||
_result = [[call]] { | _result = [[call]] { | ||
Line 281: | Line 281: | ||
}; {{cc|0.0047 ms}} | }; {{cc|0.0047 ms}} | ||
=== {{ | === {{Colorball|orange|0.9}} in vs find === | ||
{{cc|[[String]] search}} | {{cc|[[String]] search}} | ||
Line 291: | Line 291: | ||
"bar" [[find]] ["foo", "Bar", "bar", "BAR"] > -1; {{cc|? ms}} | "bar" [[find]] ["foo", "Bar", "bar", "BAR"] > -1; {{cc|? ms}} | ||
=== {{ | === {{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]]}}. | ||
Line 297: | Line 297: | ||
[[for]] [{_i = 0}, {_i < 100}, {_i = _i + 1}] [[do]] { {{codecomment|/* forCode */}} }; {{cc|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 === | ||
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. | ||
Line 323: | Line 323: | ||
} [[forEach]] [[allUnits]]; {{cc|0.0390 ms}} | } [[forEach]] [[allUnits]]; {{cc|0.0390 ms}} | ||
=== {{ | === {{Colorball|red|0.9}} findIf === | ||
[[findIf]] stops array iteration as soon as the condition is met. | [[findIf]] stops array iteration as soon as the condition is met. | ||
Line 330: | Line 330: | ||
_quantity = { [[_x]] == 2 } [[count]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0114 ms}} | _quantity = { [[_x]] == 2 } [[count]] [0,1,2,3,4,5,6,7,8,9]; {{cc|0.0114 ms}} | ||
=== {{ | === {{Colorball|green|0.9}} format vs str === | ||
[[str]] 33; {{cc|0.0016 ms}} | [[str]] 33; {{cc|0.0016 ms}} | ||
[[format]] ["%1", 33]; {{cc|0.0022 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}} | [33, 45, 78] [[joinString]] ""; {{cc|0.0052 ms - no length limit}} | ||
Line 341: | Line 341: | ||
[[str]] 33 + [[str]] 45 + [[str]] 78; {{cc|0.0059 ms - no length limit}} | [[str]] 33 + [[str]] 45 + [[str]] 78; {{cc|0.0059 ms - no length limit}} | ||
=== {{ | === {{Colorball|orange|0.9}} private === | ||
Direct declaration ({{Inline code|[[private]] _var {{=}} value}}) is faster than declaring ''then'' assigning the variable. | Direct declaration ({{Inline code|[[private]] _var {{=}} value}}) is faster than declaring ''then'' assigning the variable. | ||
Line 373: | Line 373: | ||
{{cc|0.0235 ms}} | {{cc|0.0235 ms}} | ||
=== {{ | === {{Colorball|orange|0.9}} isNil === | ||
[[isNil]] "varName"; {{cc|0.0007 ms}} | [[isNil]] "varName"; {{cc|0.0007 ms}} | ||
[[isNil]] {varName}; {{cc|0.0012 ms}} | [[isNil]] {varName}; {{cc|0.0012 ms}} | ||
=== {{ | === {{Colorball|red|0.9}} isEqualType and typeName === | ||
[[isEqualType]] is much faster than [[typeName]] | [[isEqualType]] is much faster than [[typeName]] | ||
Line 384: | Line 384: | ||
[[typeName]] "string" == [[typeName]] 33; {{cc|0.0018 ms}} | [[typeName]] "string" == [[typeName]] 33; {{cc|0.0018 ms}} | ||
=== {{ | === {{Colorball|green|0.9}} isEqualTo and count === | ||
{{cc|with a items array}} | {{cc|with a items array}} | ||
Line 390: | Line 390: | ||
[[count]] [[allUnits]] == 0; {{cc|0.0043 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] [[select]] 0; {{cc|0.0008 ms}} | ||
[1,2,3] [[param]] [0]; {{cc|0.0011 ms}} | [1,2,3] [[param]] [0]; {{cc|0.0011 ms}} | ||
=== {{ | === {{Colorball|orange|0.9}} objectParent and vehicle === | ||
[[isNull]] [[objectParent]] [[player]]; {{cc|0.0013 ms}} | [[isNull]] [[objectParent]] [[player]]; {{cc|0.0013 ms}} | ||
[[vehicle]] [[player]] == [[player]]; {{cc|0.0022 ms}} | [[vehicle]] [[player]] == [[player]]; {{cc|0.0022 ms}} | ||
=== {{ | === {{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. | ||
Line 411: | Line 411: | ||
In-vehicle units, killed units, destroyed vehicles, static objects and buildings will be ignored.}} | 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: | If you need to use global variable repeatedly in a loop, copy its value to local variable and use local variable instead: | ||
Line 432: | Line 432: | ||
{{cc|0.08 ms}}</code> | {{cc|0.08 ms}}</code> | ||
=== {{ | === {{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]]. | ||
Line 439: | Line 439: | ||
{{Informative | 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* === | ||
[[getPosWorld]] {{cc|0.0015 ms}} | [[getPosWorld]] {{cc|0.0015 ms}} | ||
Line 458: | Line 458: | ||
{{cc|0.0390 ms with "NONE"}} | {{cc|0.0390 ms with "NONE"}} | ||
=== {{ | === {{Colorball|orange|0.9}} toLower/toUpper vs toLowerANSI/toUpperANSI === | ||
{{cc|_myString is a 100 chars "aAaAaA(…)" string}} | {{cc|_myString is a 100 chars "aAaAaA(…)" string}} |
Revision as of 06:13, 2 January 2021
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:
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 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;
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?
- Store your function in memory to avoid file reading every call ( e.g
_myFunction = compile preprocessFileLineNumbers "myFile.sqf";
).
- Store your function in memory to avoid file reading every call ( e.g
- 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;
- Find a smaller name, according to the variable scope; e.g:
Code optimisation
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!
Variable assignment
private _myVar = [33, 66] select (false); // 0.0013 ms private _myVar = if (false) then { 33; } else { 66; }; // 0.0020 ms private "_myVar"; if (false) then { _myVar = 33; } else { _myVar = 66; }; // 0.0025 ms
Lazy evaluation
In SQF the following code will evaluate every single condition, even if one fails:
if (condition1 && condition2 && condition3) then {};
Even if condition1 returns false (and thus the entire Boolean expression can no longer become true), condition2 and condition3 will still be executed and evaluated regardless.
To avoid this behaviour, you can either imbricate if statements or use lazy evaluation:
if (condition1 && {condition2} && {condition3}) then {};
Lazy evaluation will stop condition evaluation once one condition evaluation returns false.
However, using lazy evaluation is not always the best way as it can both speed up and slow down the code, depending on the current condition being evaluated:
["true || { false || {false}}", nil, 100000] call BIS_fnc_codePerformance; // 0.00080 ms ["true || {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00105 ms ["false || false || false ", nil, 100000] call BIS_fnc_codePerformance; // 0.00123 ms ["true || false || false ", nil, 100000] call BIS_fnc_codePerformance; // 0.00128 ms ["false || {false} || {false} ", nil, 100000] call BIS_fnc_codePerformance; // 0.00200 ms
Concatenating 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
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
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
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 }];
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
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
if and select
Use [array] select Boolean
instead of the lazy-evaluated if.
_result = ["false result", "true result"] select true; // 0.0011 ms _result = if (true) then { "true result"; } else { "false result"; }; // 0.0017 ms
if and switch
_result = call { if (false) exitWith {}; if (false) exitWith {}; if (true) exitWith {}; if (false) exitWith {}; if (false) exitWith {}; }; // 0.0032 ms
_result = switch (true) do { case (false): {}; case (false): {}; case (true) : {}; case (false): {}; case (false): {}; }; // 0.0047 ms
in vs find
// String search "bar" in "foobar" // 0.0008 ms "foobar" find "bar" > -1 // 0.0012 ms
// Array search - case-sensitive "bar" in ["foo", "Bar", "bar", "BAR"]; // ? ms "bar" find ["foo", "Bar", "bar", "BAR"] > -1; // ? ms
for
The for..from..to..do
is twice as fast as its alternative syntax, for..do
.
for "_i" from 0 to 10 do { /* forCode */ }; // 0.015 ms for [{_i = 0}, {_i < 100}, {_i = _i + 1}] do { /* forCode */ }; // 0.030 ms
forEach vs count vs findIf
Both forEach and count commands will step through all the array elements and both commands will contain reference to current element with the _x variable.
However, count loop is a little faster than forEach loop, but it does not benefit from the _forEachIndex variable.
Also, there is a limitation as the code inside count expects Boolean or Nothing while the command itself returns Number.
This limitation is very important if you try to replace your forEach by count. If you have to add 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
findIf
findIf stops array iteration as soon as the condition is met.
[0,1,2,3,4,5,6,7,8,9] findIf { _x == 2 }; // 0.0050 ms { if (_x == 2) exitWith { _forEachIndex; }; } forEach [0,1,2,3,4,5,6,7,8,9]; // 0.0078 ms _quantity = { _x == 2 } count [0,1,2,3,4,5,6,7,8,9]; // 0.0114 ms
format vs str
str 33; // 0.0016 ms format ["%1", 33]; // 0.0022 ms
+ vs format vs joinString
[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
private
Direct declaration (private _var = value
) is faster than declaring then assigning the variable.
private _a = 1; private _b = 2; private _c = 3; private _d = 4; // 0.0023 ms
private ["_a", "_b", "_c", "_d"]; _a = 1; _b = 2; _c = 3; _d = 4; // 0.0040 ms
However, if you have to reuse the same variable in a loop, external declaration is faster.
The reason behind this is that a declaration in the loop will create, assign and delete the variable in each loop.
An external declaration creates the variable only once and the loop only assigns the value.
private ["_a", "_b", "_c", "_d"]; for "_i" from 1 to 10 do { _a = 1; _b = 2; _c = 3; _d = 4; }; // 0.0195 ms
for "_i" from 1 to 10 do { private _a = 1; private _b = 2; private _c = 3; private _d = 4; }; // 0.0235 ms
isNil
isNil "varName"; // 0.0007 ms isNil {varName}; // 0.0012 ms
isEqualType and typeName
isEqualType is much faster than typeName
"string" isEqualType 33; // 0.0006 ms typeName "string" == typeName 33; // 0.0018 ms
isEqualTo and count
// with a items array allUnits isEqualTo []; // 0.0040 ms count allUnits == 0; // 0.0043 ms
select and param
[1,2,3] select 0; // 0.0008 ms [1,2,3] param [0]; // 0.0011 ms
objectParent and vehicle
isNull objectParent player; // 0.0013 ms vehicle player == player; // 0.0022 ms
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
Global variables vs local variables
If you need to use global variable repeatedly in a loop, copy its value to local variable and use local variable instead:
SomeGlobalVariable = [123];
for "_i" from 1 to 100 do
{
SomeGlobalVariable select 0;
};
// 0.13 ms
is noticeably slower than
SomeGlobalVariable = [123];
private _var = SomeGlobalVariable;
for "_i" from 1 to 100 do
{
_var select 0;
};
// 0.08 ms
Config path delimiter
>>
is slightly faster than /
when used in config path with configFile or missionConfigFile.
configFile >> "CfgVehicles"; // 0.0019 ms configFile / "CfgVehicles"; // 0.0023 ms
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"
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, Armed Assault, Arma 2, Take On Helicopters, Arma 3) brought their own new commands, especially Arma 2 and Arma 3.
For that, if you are converting scripts from older versions of the engine, the following aspects should be reviewed.
Loops
Array operations
- Adding an item:
myArray + [element]
andmyArray set [count myArray, element]
have been replaced by pushBack - Selecting a random item: BIS_fnc_selectRandom has been replaced by selectRandom
- Removing items:
myArray set [1, objNull]; myArray - [objNull]
has been replaced by deleteAt and deleteRange - Concatenating:
myArray = myArray + [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 }); // 1.55 ms
result = []; { if (_x % 2 == 0) then { result pushBack _x; }; } forEach arrayOfNumbers; // 2.57 ms
Vector operations
- BIS_fnc_vectorMultiply has been replaced by vectorMultiply (at least 6x faster)
- BIS_fnc_vectorDivide too, to an extent (see its page for more information)
private _vector = [102, 687, 1543]; private _factor = 53; _vector vectorMultiply _factor; // 0.0028 ms [_vector, _factor] call BIS_fnc_vectorMultiply; // 0.0145 ms _vector vectorMultiply 1/_factor; // 0.003 ms - but beware of 0 divisor [_vector, _factor] call BIS_fnc_vectorDivide; // 0.017 ms
String operations
String manipulation has been simplified with the following commands:
- alternative syntax for select:
string select index
- toArray and toString have been reinforced with splitString and joinString
Number operations
- BIS_fnc_linearConversion has been replaced by linearConversion. The command is 9 times faster.
- BIS_fnc_selectRandomWeighted has been replaced by selectRandomWeighted. The command is 7 times faster.
Type comparison
- typeName has been more than reinforced with isEqualType.
Multiplayer
- BIS_fnc_MP has been replaced by remoteExec and remoteExecCall and internally uses them. Use the engine commands from now on!
Parameters
- BIS_fnc_param has been replaced by param and params. The commands are approximately 14 times faster. Use them!