Code Optimisation: Difference between revisions
Lou Montana (talk | contribs) m (Some wiki formatting) |
Hypoxic125 (talk | contribs) m (Added if else and switch comparison) |
||
(9 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
{{TOC|side|0.9}} | {{TOC|side|0.9}} | ||
{{ Feature | | {{Feature|informative|This page is about [[Code Optimisation]]. For ''conception'' optimisation, see [[Mission Optimisation]].}} | ||
This article will try to be a general guide about improving your code '''and''' its performance. | This article will try to be a general guide about improving your code '''and''' its performance. | ||
* The first part ([[#Rules|Rules]]) will focus on having a clean, readable and maintainable code. | * The first part ([[#Rules|Rules]]) will focus on having a clean, readable and maintainable code. | ||
Line 15: | Line 15: | ||
With that being said, here are the three basic rules to get yourself in the clear: | With that being said, here are the three basic rules to get yourself in the clear: | ||
# | # {{Link|#Make It Work|Make it work}} | ||
# | # {{Link|#Make It Readable|Make it readable}} | ||
# | # {{Link|#Optimise Then|Optimise then}} | ||
=== Make | === Make It Work === | ||
{{Feature|quote|Premature optimization is the root of all evil.|{{ | {{Feature|quote|Premature optimization is the root of all evil.|{{Link|https://en.wikipedia.org/wiki/Donald_Knuth|Donald Knuth}}}} | ||
Your first goal when coding is to make your code do what you want it does. A good way to reach this objective is to read and getting inspired by other people's code. If you understand it by reading it once, it is probably a good source of inspiration. | 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 28: | Line 28: | ||
* Read your [[Crash_Files|Arma RPT]] (report) to read more details about the error that happened in your code. | * Read your [[Crash_Files|Arma RPT]] (report) to read more details about the error that happened in your code. | ||
=== Make | === 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 41: | Line 41: | ||
* Finally, camel-casing (namingLikeThis) your variables 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. | ||
{{Feature | | {{Feature|informative|See '''[[Code Best Practices]]''' for more information.}} | ||
See the following code: | See the following code: | ||
Line 70: | Line 70: | ||
</sqf> | </sqf> | ||
Becomes | Becomes | ||
<sqf> #define BUFFER 1.053 // note: no semicolon | <sqf> | ||
#define BUFFER 1.053 // note: no semicolon | |||
_a = _x + BUFFER; | _a = _x + BUFFER; | ||
_b = _y + BUFFER; | _b = _y + BUFFER; | ||
Line 81: | Line 82: | ||
var1 = 123; | var1 = 123; | ||
var2 = "123"; | var2 = "123"; | ||
var3[] = {1,2,3}; | var3[] = { 1, 2, 3 }; | ||
rand = __EVAL(random 999); | rand = __EVAL(random 999); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 96: | Line 97: | ||
it is better for a matter of performance to put all your definitions at the top of the file. | it is better for a matter of performance to put all your definitions at the top of the file. | ||
=== Optimise | === 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. | ||
Line 103: | Line 104: | ||
** 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 ( e.g | ** Store your function in memory to avoid file reading every call ( e.g <sqf inline>_myFunction = compile preprocessFileLineNumbers "myFile.sqf";</sqf>). | ||
* Is your variable name far too long? | * 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;<!-- | ||
Line 110: | Line 111: | ||
== Code | == Code Optimisation == | ||
{{Feature|important| | {{Feature|important| | ||
Line 116: | Line 117: | ||
}} | }} | ||
{{Feature| | {{Feature|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> | ||
Line 122: | Line 123: | ||
}} | }} | ||
=== Scheduled and | === 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! | ||
{{Feature | | {{Feature|informative|See [[Scheduler]] for more information.}} | ||
=== {{Colorball|orange|0.9}} Variable | === {{Colorball|orange|0.9}} Variable Assignment === | ||
<sqf> | <sqf> | ||
Line 137: | Line 138: | ||
</sqf> | </sqf> | ||
=== {{Colorball|orange|0.9}} Lazy | === {{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 173: | Line 174: | ||
</sqf> | </sqf> | ||
=== {{Colorball|red|0.9}} Concatenating | === {{Colorball|red|0.9}} Concatenating Strings === | ||
<sqf inline>myString = myString + otherString</sqf> works fine for small strings, however the bigger the string gets the slower the operation becomes: | <sqf inline>myString = myString + otherString</sqf> works fine for small strings, however the bigger the string gets the slower the operation becomes: | ||
Line 181: | Line 182: | ||
<sqf> | <sqf> | ||
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 ""; // 30 ms | strings = strings joinString ""; // 30 ms | ||
</sqf> | </sqf> | ||
=== {{Colorball|red|0.9}} | === {{Colorball|red|0.9}} Array Manipulation === | ||
==== | ==== Add Elements ==== | ||
New commands [[append]] and [[pushBack]] hold the best score. | New commands [[append]] and [[pushBack]] hold the best score. | ||
<sqf> | <sqf> | ||
Line 201: | Line 202: | ||
</sqf> | </sqf> | ||
==== | ==== Iterate Elements ==== | ||
[[for]] is twice as fast as [[forEach]] and is recommended '''if [[Magic_Variables#x|_x]] is not required'''. | [[for]] is twice as fast as [[forEach]] and is recommended '''if [[Magic_Variables#x|_x]] is not required'''. | ||
Line 230: | Line 231: | ||
</sqf> | </sqf> | ||
==== | ==== Remove Elements ==== | ||
<sqf> | <sqf> | ||
_array = [0,1,2,3]; _array deleteAt 0; // 0.0015 ms | _array = [0,1,2,3]; _array deleteAt 0; // 0.0015 ms | ||
Line 241: | Line 242: | ||
</sqf> | </sqf> | ||
=== {{Colorball|red|0.9}} Multiplayer | === {{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 247: | Line 248: | ||
* [[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> | * [[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]] & [[remoteExecCall]]. Ditch [[BIS_fnc_MP]] for good! | * Use, use and use [[remoteExec]] & [[remoteExecCall]]. Ditch [[BIS_fnc_MP]] for good! | ||
{{Feature | | {{Feature|informative|See [[Multiplayer Scripting]] for more information.}} | ||
== Equivalent | == Equivalent Commands Performance == | ||
=== {{Colorball|orange|0.9}} call === | === {{Colorball|orange|0.9}} call === | ||
Line 274: | Line 266: | ||
<sqf>player addEventHandler ["HandleDamage", { _this call my_fnc_damage }];</sqf> | <sqf>player addEventHandler ["HandleDamage", { _this call my_fnc_damage }];</sqf> | ||
=== {{Colorball|red|0.9}} execVM and call === | === {{Colorball|red|0.9}} execVM and call === | ||
{{Feature|important| | |||
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> | ||
If you | If you use the script more than once, store its code in a variable or better, make it a [[Arma 3: Functions Library|Function]]! | ||
}} | |||
<sqf> | |||
// myFile.sqf is an EMPTY file | |||
private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once | |||
call _myFunction; // 0.0009 ms | |||
execVM "myFile.sqf"; // 0.275 ms | |||
// myFile.sqf is BIS_fnc_showRespawnMenu | |||
private _myFunction = compile preprocessFileLineNumbers "myFile.sqf"; // compile time is done only once | |||
["close"] call _myFunction; // 0.0056 ms | |||
["close"] execVM "myFile.sqf"; // 0.506 ms | |||
</sqf> | |||
=== {{Colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers === | === {{Colorball|orange|0.9}} loadFile, preprocessFile and preprocessFileLineNumbers === | ||
Line 362: | Line 347: | ||
case (false): {}; | case (false): {}; | ||
}; // 0.0047 ms | }; // 0.0047 ms | ||
</sqf> | |||
=== {{Colorball|green|0.9}} if else and switch === | |||
<sqf> | |||
_mode = "killed"; | |||
switch _mode do { | |||
case "init": {}; | |||
case "killed": {}; | |||
case "respawned": {}; | |||
}; // 0.0019 ms | |||
</sqf> | |||
<sqf> | |||
_mode = "killed"; | |||
if (_mode == "init") then {} else { | |||
if (_mode == "killed") then {} else { | |||
if (_mode == "respawned") then {}; | |||
}; | |||
}; // 0.0019 ms | |||
</sqf> | </sqf> | ||
Line 380: | Line 387: | ||
=== {{Colorball|orange|0.9}} for === | === {{Colorball|orange|0.9}} for === | ||
The {{ | The {{hl|[[for]]..[[from]]..[[to]]..[[do]]}} is twice as fast as its alternative syntax, {{hl|[[for]]..[[do]]}}. | ||
<sqf> | <sqf> | ||
for "_i" from 0 to 10 do { /* forCode */ }; // 0.015 ms | for "_i" from 0 to 10 do { /* forCode */ }; // 0.015 ms | ||
for [{_i = 0}, {_i < 100}, {_i = _i + 1}] do { /* forCode */ }; | for [{ _i = 0 }, { _i < 100 }, { _i = _i + 1 }] do { /* forCode */ }; // 0.030 ms | ||
</sqf> | </sqf> | ||
Line 436: | Line 443: | ||
=== {{Colorball|green|0.9}} + vs format vs joinString === | === {{Colorball|green|0.9}} + vs format vs joinString === | ||
non-[[String]] data: | |||
<sqf> | <sqf> | ||
[33, 45, 78] joinString ""; // 0.0052 ms - no length limit | [33, 45, 78] joinString ""; // 0.0052 ms - no length limit | ||
format ["%1%2%3", 33, 45, 78]; // 0.0054 ms - limited to ~8Kb | 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 | str 33 + str 45 + str 78; // 0.0059 ms - no length limit | ||
</sqf> | |||
[[String]] data: | |||
<sqf> | |||
["str1", "str2", "str3"] joinString ""; // 0.0015 ms - no length limit | |||
format ["%1%2%3", "str1", "str2", "str3"]; // 0.0015 ms - limited to ~8Kb | |||
"str1" + "str2" + "str3"; // 0.0012 ms - no length limit | |||
</sqf> | </sqf> | ||
Line 486: | Line 501: | ||
<sqf> | <sqf> | ||
isNil "varName"; // 0.0007 ms | isNil "varName"; // 0.0007 ms | ||
isNil {varName}; // 0.0012 ms | isNil { varName }; // 0.0012 ms | ||
</sqf> | </sqf> | ||
Line 510: | Line 525: | ||
[1,2,3] select 0; // 0.0008 ms | [1,2,3] select 0; // 0.0008 ms | ||
[1,2,3] param [0]; // 0.0011 ms | [1,2,3] param [0]; // 0.0011 ms | ||
</sqf> | |||
=== {{Colorball|red|0.9}} createSimpleObject vs createVehicle === | |||
<sqf> | |||
// createSimpleObject is over 43× faster than createVehicle! | |||
deleteVehicle createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d", [0,0,0]]; // ~0.08 ms | |||
deleteVehicle createSimpleObject ["Land_VR_Shape_01_cube_1m_F", [0,0,0]]; // ~3.2 ms | |||
deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "CAN_COLLIDE"]; // ~2.7 ms | |||
deleteVehicle createVehicle ["Land_VR_Shape_01_cube_1m_F", [0,0,0], [], 0, "NONE"]; // ~78 ms | |||
</sqf> | </sqf> | ||
Line 534: | Line 559: | ||
{{Clear}} | {{Clear}} | ||
=== {{Colorball|orange|0.9}} Global | === {{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 575: | Line 600: | ||
getPosATL // 0.0016 ms | getPosATL // 0.0016 ms | ||
getPosASLW // 0.0023 ms | getPosASLW // 0.0023 ms | ||
getPos // 0.0030-0.0300 ms; performance depends on where | getPos // 0.0030-0.0300 ms; performance depends on where this command is used - see its documentation | ||
position // | position // same as getPos | ||
getPosVisual // | getPosVisual // same as getPos | ||
visiblePosition // | visiblePosition // same as getPos | ||
</sqf> | </sqf> | ||
Line 595: | Line 620: | ||
<sqf> | <sqf> | ||
// _myString is a 100 chars "aAaAaA(…)" string | // _myString is a 100 chars "aAaAaA(…)" string | ||
toLowerANSI _myString; // 0.0006 ms | toLowerANSI _myString; // 0.0006 ms | ||
toLower _myString; | toLower _myString; // 0.0016 ms | ||
toUpperANSI _myString; // 0.0006 ms | toUpperANSI _myString; // 0.0006 ms | ||
toUpper _myString; | toUpper _myString; // 0.0016 ms | ||
</sqf> | </sqf> | ||
== Equivalent | == Equivalent Data Structures Performance == | ||
=== Key- | === Key-Value Data Structures === | ||
<sqf> | <sqf> | ||
Line 617: | Line 642: | ||
// this takes 0.0038ms: | // this takes 0.0038ms: | ||
private _index = _goodFormat select 0 find "name"; // | private _index = _goodFormat select 0 find "name"; // loop in engine | ||
private _name = _goodFormat select 1 select _index; | private _name = _goodFormat select 1 select _index; | ||
// this takes 0.0116ms: | // this takes 0.0116ms: | ||
private _index = _slowFormat findIf { _x select 0 == "name" }; // | private _index = _slowFormat findIf { _x select 0 == "name" }; // loop in script | ||
private _name = _slowFormat select _index select 1; | private _name = _slowFormat select _index select 1; | ||
</sqf> | </sqf> | ||
Line 628: | Line 653: | ||
== Conversion | == Conversion From Earlier Versions == | ||
Each iteration of Bohemia games ({{ofp}}, {{arma1}}, {{arma2}}, {{tkoh}}, {{arma3}}) brought their own new commands, especially {{arma2}} and {{arma3}}.<br> | Each iteration of Bohemia games ({{ofp}}, {{arma1}}, {{arma2}}, {{tkoh}}, {{arma3}}) brought their own new commands, especially {{arma2}} and {{arma3}}.<br> | ||
Line 641: | Line 666: | ||
** [[select]] | ** [[select]] | ||
=== Array | === Array Operations === | ||
* '''Adding an item:''' | * '''Adding an item:''' <sqf inline>myArray + [element]</sqf> and <sqf inline>myArray set [count myArray, element]</sqf> have been replaced by [[pushBack]] | ||
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]] | * '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]] | ||
* '''Removing items:''' | * '''Removing items:''' <sqf inline>myArray set [1, objNull]; myArray - [objNull]</sqf> has been replaced by [[deleteAt]] and [[deleteRange]] | ||
* '''Concatenating:''' | * '''Concatenating:''' <sqf inline>myArray = myArray + [element]</sqf> has been ''reinforced'' with [[append]]: if you don't need the original array to be modified, use [[+]] | ||
* '''Comparing:''' use [[isEqualTo]] instead of [[BIS_fnc_areEqual]] | * '''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]] | ||
Line 658: | Line 683: | ||
</sqf> | </sqf> | ||
=== Vector | === Vector Operations === | ||
* [[BIS_fnc_vectorMultiply]] has been replaced by [[vectorMultiply]] (at least 6x faster) | * [[BIS_fnc_vectorMultiply]] has been replaced by [[vectorMultiply]] (at least 6x faster) | ||
Line 670: | Line 695: | ||
[_vector, _factor] call BIS_fnc_vectorMultiply; // 0.0145 ms | [_vector, _factor] call BIS_fnc_vectorMultiply; // 0.0145 ms | ||
_vector vectorMultiply 1/_factor; | _vector vectorMultiply (1 / _factor); // 0.003 ms - but beware of 0 divisor | ||
[_vector, _factor] call BIS_fnc_vectorDivide; // 0.017 ms | [_vector, _factor] call BIS_fnc_vectorDivide; // 0.017 ms | ||
</sqf> | </sqf> | ||
=== String | === String Operations === | ||
[[String]] manipulation has been simplified with the following commands: | [[String]] manipulation has been simplified with the following commands: | ||
Line 680: | Line 705: | ||
* [[toArray]] and [[toString]] have been ''reinforced'' with [[splitString]] and [[joinString]] | * [[toArray]] and [[toString]] have been ''reinforced'' with [[splitString]] and [[joinString]] | ||
=== Number | === 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 | === Type Comparison === | ||
* [[typeName]] has been more than ''reinforced'' with [[isEqualType]]. | * [[typeName]] has been more than ''reinforced'' with [[isEqualType]]. | ||
Line 692: | Line 717: | ||
* [[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! | ||
{{Feature|informative|See also [[Multiplayer Scripting]].}} | |||
=== Parameters === | === Parameters === |
Latest revision as of 05:56, 19 May 2024
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
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:
And
Becomes
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:
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";).
- Is your variable name far too long?
- Find a smaller name, according to the variable scope;e.g:becomes
- 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
Lazy Evaluation
In SQF the following code will evaluate every single condition, even if one fails:
Even if a returns false (and thus the entire Boolean expression can no longer become true), b and c will still be executed and evaluated regardless.
To avoid this behaviour, one can either imbricate if statements or use lazy evaluation. The latter is done like so:
In the example above, condition evaluation stops once any condition evaluates to false.
Influence on Semantics
Depending on the arrangement of the curly brackets, lazy evaluation can change the precedence (and therefore the semantics) of a condition. Consider this example:
Expression | Condition Value | Evaluated Conditions | Result | ||
---|---|---|---|---|---|
a | b | c | |||
false | any Boolean | true | a, b, c | true | |
a, c | true | ||||
a | false |
Performance
Using lazy evaluation is not always the best way as it can both speed up and slow down the code, depending on the current condition being evaluated:
Concatenating Strings
myString = myString + otherString works fine for small strings, however the bigger the string gets the slower the operation becomes:
The solution is to use a string array that you will concatenate later:
Array Manipulation
Add Elements
New commands append and pushBack hold the best score.
Iterate Elements
for is twice as fast as forEach and is recommended if _x is not required.
Remove Elements
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:
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:
instead of:
execVM and call
loadFile, preprocessFile and preprocessFileLineNumbers
if
if and select
Use [array] select boolean instead of the lazy-evaluated if.
if and switch
if else and switch
in vs find
for
The for..from..to..do is twice as fast as its alternative syntax, for..do.
forEach vs count vs findIf
Both forEach and count commands will step through all the array elements and both commands will contain reference to current element with the _x variable.
However, count loop is a little faster than forEach loop, but it does not benefit from the _forEachIndex variable.
Also, there is a limitation as the code inside count expects Boolean or Nothing while the command itself returns Number.
This limitation is very important if you try to replace your forEach by count. If you have to add an extra true/false/nil at the end to make count work, it will be slower than the forEach equivalent.
findIf
findIf stops array iteration as soon as the condition is met.
format vs str
+ vs format vs joinString
non-String data:
String data:
private
Direct declaration (private _var = value) is faster than declaring then assigning the variable.
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.
isNil
isEqualType and typeName
isEqualType is much faster than typeName
isEqualTo and count
select and param
createSimpleObject vs createVehicle
objectParent and vehicle
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.
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:
is noticeably slower than
Config path delimiter
>> is slightly faster than / when used in config path with configFile or missionConfigFile.
getPos* and setPos*
toLower/toUpper vs toLowerANSI/toUpperANSI
Equivalent Data Structures Performance
Key-Value Data Structures
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] and myArray 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)
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)
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!