Lou Montana/Sandbox – User
Lou Montana (talk | contribs) m (Lou Montana moved page Lou Montana's Sandbox to User:Lou Montana/Sandbox: Proper name) |
Lou Montana (talk | contribs) (First Code Optimisation sharing) |
||
Line 1: | Line 1: | ||
{{Hatnote|This page is an intended replacement for '''[[Code Optimisation]]'''. It is obviously '''a WORK-IN-PROGRESS'''.}} | |||
[[Category:Sandbox]] | |||
<!-- | |||
TODO: color code about importance of performance saving? (green / yellow / red) | |||
--> | |||
= Code Optimisation = | |||
== Introduction == | |||
This article will try to be a general guide about improving your code '''and''' its performance. | |||
== 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.<br /> | |||
<br /> | |||
With that being said, here are the three basic rules to get yourself in the clear: | |||
# [[#Make it work|Make it work]] | |||
# [[#Make it readable|Make it readable]] | |||
# [[#Optimise then|Optimise then]] | |||
=== 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.<br /> | |||
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 [[forEach|for each]] west soldier in them, add damage''. | |||
=== Make it readable === | |||
Whether you are cleaning your code or a different person's, you must understand the code | |||
* While [[SQF syntax|SQF]] ''is'' impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. | |||
* ''One-lining'' (putting everything in one statement) memory improvement is 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. | |||
* Finally, camel-casing (namingLikeThis) your variables and commands will naturally make the code more readable, especially for long names. | |||
See the following code: | |||
<code>_w=[];{_w pushbackunique primaryweapon _x}foreach((allunits+alldeadmen)select{_x call bis_fnc_objectside==east});</code> | |||
The same example is far more readable with proper spacing, good variable names and intermediate results: | |||
<code>_weaponNames = []; | |||
_allUnitsAliveAndDead = allUnits + allDeadMen; | |||
_allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east }; | |||
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;</code> | |||
* 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. | |||
=== Optimise then === | |||
Once you know what is what, you can understand your code better. | |||
* You were iterating multiple times on the same array? | |||
** You should be able to spot your issue now. | |||
* Do you see the same code, but with different parameters? | |||
** Time to write a function. | |||
* Are you using [[execVM]] on the same file, many times? | |||
** Store your function in memory to avoid file reading every call with <code>_myFunction = [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";</code> | |||
* Is your function code far too long? | |||
** Split it in understandable-sized bites, for your own sanity. | |||
== Code optimisation == | |||
=== Successive condition check === | |||
In [[SQF syntax|SQF]] the following code will check '''all and every''' condition, even if one fail: | |||
[[if]] (condition1 && condition2 && condition3) [[then]] { /* thenCode */ }; | |||
e.g: | |||
[[if]] ([[alive]] unit1 && [[not]] [[alive]] aPvehicle && [[daytime]] > 5) [[then]] { /* thenCode */ }; | |||
This code will check if {{Inline code|unit1}} is alive, and if it is not {{Inline code|[[not]] [[alive]] aPvehicle && [[daytime]] > 5}} will execute anyway. | |||
To avoid this behaviour, you can either imbricate [[if|ifs]] or use '''lazy evaluation''' such as the following: | |||
[[if]] ([[alive]] unit1 && { [[not]] [[alive]] aPvehicle } && {{ [[daytime]] > 5}}) [[then]] { /* thenCode */ }; | |||
This lazy evaluation will stop code execution 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}] call BIS_fnc_codePerformance; {{codecomment|// fastest}} | |||
[true || false || false ] call BIS_fnc_codePerformance; {{codecomment|// normal}} | |||
[false || false || false ] call BIS_fnc_codePerformance; {{codecomment|// same as above}} | |||
[false || {false} || {false}] call BIS_fnc_codePerformance; {{codecomment|// slowest}} | |||
==== Adding large strings together ==== | |||
For small strings {{Inline code|a = a + b}} works fine, however the bigger the string gets the slower this becomes: | |||
s = ""; [[for]] "_i" [[from]] 1 [[to]] 10000 [[do]] {s = s + "123"}; {{codecomment|// 30000 chars @ 290ms}} | |||
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]] ""; {{codecomment|// 30000 chars @ 30ms}} | |||
=== Multiplayer recommendations === | |||
''TODO'' | |||
* no [[publicVariable]] or public [[setVariable]] at high frequency | |||
* the server is supposed to have a lot of memory, use it | |||
=== createSimpleObject vs createVehicle === | |||
''TODO'' | |||
create at [0,0,0] | |||
== Equivalent commands performance == | |||
''TODO'' | |||
=== if === | |||
Use: | |||
[[if]] (condition1) then {/* thenCode */} else {/* elseCode */}; {{codecomment|// faster}} | |||
Instead of: | |||
[[if]] (condition1) then [{/* thenCode */}, {/* elseCode */}]; {{codecomment|// slower}} | |||
=== for === | |||
Use: | |||
[[for]] "var" [[from]] 0 [[to]] 100 [[do]] { /* forCode */ }; {{codecomment|// faster}} | |||
Instead of: | |||
[[for]] [{_i = 0}, {_i < 100}, {_i = _i + 1}] [[do]] { /* forCode */ }; {{codecomment|// slower}} | |||
=== forEach vs count === | |||
Both commands will step through supplied array of elements one by one and both commands will contain reference to current element in the [[_x]] variable. | |||
However, [[count]] loop is a little faster than [[forEach]] loop, but it does not have [[_forEachIndex]] variable.<br /> | |||
Also, there is a limitation as the code inside [[count]] expects [[Boolean]] or [[Nothing]] while itself returns [[Number]]. | |||
<code>{[[diag_log]] _x} [[count]] [1,2,3,4,5,6,7,8,9]; {{codecomment|// faster}}</code> | |||
<code>{[[diag_log]] _x} [[forEach]] [1,2,3,4,5,6,7,8,9]; {{codecomment|// slower}}</code> | |||
Usage: | |||
<code>_someoneIsNear = {_x [[distance]] [0,0,0] < 1000} [[count]] [[allUnits]] > 0;</code> | |||
_someoneIsNear = { | |||
[[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] }; | |||
[[false]] | |||
} [[forEach]] [[allUnits]]; | |||
''TODO: findIf?'' | |||
=== + and format === | |||
When concatenating more than two strings, [[format]] is faster than [[valuea_plus_valueb|+]]. Use: | |||
[[format]] ["%1%2%3%4%5", "string1", "string2", "string3", "string4", "string5"]; {{codecomment|// faster}} | |||
Instead of: | |||
"string1" + "string2" + "string3" + "string4" + "string5"; {{codecomment|// slower}} | |||
=== select and if === | |||
Use: | |||
result = ["false result", "true result"] [[select]] _condition; {{codecomment|// faster}} | |||
Instead of the lazy-evaluated [[if]]: | |||
result = [[if]] (_condition) [[then]] { "true result"; } [[else]] { "false result"; }; {{codecomment|// slower}} | |||
=== private === | |||
Use: | |||
private _a = 1; | |||
private _b = 2; | |||
private _c = 3; | |||
private _d = 4; | |||
{{codecomment|// 0.0023 ms}} | |||
Instead of: | |||
private ["_a", "_b", "_c", "_d"]; | |||
_a = 1; | |||
_b = 2; | |||
_c = 3; | |||
_d = 4; | |||
{{codecomment|// 0.0040 ms}} | |||
However: | |||
[[for]] "_i" from 1 to 100 do | |||
{ | |||
private _a = 1; private _b = 2; private _c = 3; private _d = 4; | |||
}; | |||
{{codecomment|// 0.186776 ms}} | |||
is slower than: | |||
private ["_a", "_b", "_c", "_d"]; | |||
for "_i" from 1 to 100 do | |||
{ | |||
_a = 1; | |||
_b = 2; | |||
_c = 3; | |||
_d = 4; | |||
}; | |||
{{codecomment|// 0.146327 ms}} | |||
The reason behind this is that in the first example variables are created, assigned and deleted in each loop.<br /> | |||
In the second example the variables are only created/deleted once and changed often. | |||
=== objectParent and vehicle === | |||
<code>isNull objectParent player {{codecomment|// 0.0013 ms, slightly faster}}</code> | |||
<code>vehicle player == player {{codecomment|// 0.0022 ms}}</code> | |||
=== nearEntities and nearestObjects === | |||
* [[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of object(s) which are within the given range. | |||
If a range was set to more thean 100 meters it is highly recommend to use [[nearEntities]] instead of [[nearestObjects]]. | |||
Note: [[nearEntities]] only searches for objects which are alive. | |||
Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command. | |||
=== Config path delimiter === | |||
''>>'' is slightly faster than ''/'' when used in config path with [[configFile]] or [[missionConfigFile]]: | |||
[[configFile]] >> "CfgVehicles" {{codecomment|// 0.0019 ms}} | |||
is (very slighlty) faster than | |||
[[configFile]] / "CfgVehicles" {{codecomment|// 0.0023 ms}} | |||
=== getPos* and setPos* === | |||
====Position World is the fastest==== | |||
[[getPosASL]], [[getPosATL]] and [[visiblePositionASL]] are faster than [[getPos]], [[position]] and [[visiblePosition]]. But new to Arma 3 command [[getPosWorld]] is the fastest of them all. | |||
<code>[[getPosWorld]] [[player]] | |||
//0.0014 ms</code> | |||
<code>[[getPosASL]] [[player]] | |||
//0.0014 ms</code> | |||
<code>[[getPosATL]] [[player]] | |||
//0.0015 ms</code> | |||
<code>[[visiblePositionASL]] [[player]] | |||
//0.0014 ms</code> | |||
<code>[[visiblePosition]] [[player]] | |||
//0.0048 ms</code> | |||
<code>[[getPos]] [[player]] | |||
//or | |||
[[position]] [[player]] | |||
//0.005 ms</code> | |||
== Conversion from earlier versions == | |||
Each iteration of Bohemia games ({{ofp}}, {{arma}}, {{arma2}}, {{tkoh}}, {{arma3}}) brought their own new commands, especially {{arma2}} and {{arma3}}.<br /> | |||
For that, if you are converting scripts from older versions of the engine, some aspects should be reviewed: | |||
=== Loops === | |||
* [[forEach]] loops, depending on the situation, can be replaced by: | |||
** [[apply]] | |||
** [[count]] | |||
** [[findIf]] | |||
** [[select]] | |||
=== Array operations === | |||
* '''Adding an item:''' {{Inline code|myArray [[valuea_plus_valueb|+]] [element]}} and {{Inline code|myArray [[set]] <nowiki>[</nowiki>[[count]] myArray, element]}} have been replaced by [[pushBack]] | |||
* '''Selecting a random item:''' [[BIS_fnc_selectRandom]] has been replaced by [[selectRandom]] | |||
* '''Removing items:''' {{Inline code|myArray [[set]] [1, objNull]; myArray [[a_-_b|-]] [objNull]}} has been replaced by [[deleteAt]] and [[deleteRange]] | |||
* '''Concatenating:''' {{Inline code|myArray {{=}} myArray [[valuea_plus_valueb|+]] [element]}} has been ''reinforced'' by [[append]]: if you don't need the original array to be modified, use + | |||
* '''Comparing:''' use [[isEqualTo]] instead of [[BIS_fnc_areEqual]] or a custom comparison function | |||
** {{Inline code|[[count]] myArray == 0}} is pretty fast, but direct comparison with [[isEqualTo]] is stilla little faster: {{Inline code|myArray [[isEqualTo]] []}} | |||
* '''Finding common items:''' [[in]] [[forEach]] loop has been replaced by [[arrayIntersect]] | |||
* '''Condition filtering:''' [[forEach]] can be replaced by [[select]] (alternative syntax) | |||
<code>result = []; | |||
{ | |||
[[if]] (_x % 2 == 0) [[then]] | |||
{ | |||
result [[pushBack]] [[_x]]; | |||
}; | |||
} [[forEach]] arrayOfNumbers; {{codecomment|// 2.57 ms}}</code> | |||
result = (arrayOfNumbers [[select]] { _x % 2 == 0 }); {{codecomment|// 1.55 ms}} | |||
=== String operations === | |||
[[String]] manipulation has been simplified with the following commands: | |||
* string [[select]] index | |||
* [[toArray]] and [[toString]] have been reinforced with [[splitString]] and [[joinString]] | |||
=== Multiplayer === | |||
* [[BIS_fnc_MP]] has been replaced by [[remoteExec]] and [[remoteExecCall]] |
Revision as of 00:46, 9 May 2018
Code Optimisation
Introduction
This article will try to be a general guide about improving your code and its performance.
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 damage.
Make it readable
Whether you are cleaning your code or a different person's, you must understand the code
- While SQF is impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
- One-lining (putting everything in one statement) memory improvement is 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.
- Finally, camel-casing (namingLikeThis) your variables and commands 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;
- 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.
Optimise then
Once you know what is what, you can understand your code better.
- You were iterating multiple times on the same array?
- You should be able to spot your issue now.
- Do you see the same code, but with different parameters?
- Time to write a function.
- Are you using execVM on the same file, many times?
- Store your function in memory to avoid file reading every call with
_myFunction = compile preprocessFileLineNumbers "myFile.sqf";
- Store your function in memory to avoid file reading every call with
- Is your function code far too long?
- Split it in understandable-sized bites, for your own sanity.
Code optimisation
Successive condition check
In SQF the following code will check all and every condition, even if one fail:
if (condition1 && condition2 && condition3) then { /* thenCode */ };
e.g:
if (alive unit1 && not alive aPvehicle && daytime > 5) then { /* thenCode */ };
This code will check if unit1
is alive, and if it is not not alive aPvehicle && daytime > 5
will execute anyway.
To avoid this behaviour, you can either imbricate ifs or use lazy evaluation such as the following:
if (alive unit1 && { not alive aPvehicle } && {{ daytime > 5}}) then { /* thenCode */ };
This lazy evaluation will stop code execution 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}] call BIS_fnc_codePerformance; // fastest [true || false || false ] call BIS_fnc_codePerformance; // normal [false || false || false ] call BIS_fnc_codePerformance; // same as above [false || {false} || {false}] call BIS_fnc_codePerformance; // slowest
Adding large strings together
For small strings -No code provided-
works fine, however the bigger the string gets the slower this becomes:
s = ""; for "_i" from 1 to 10000 do {s = s + "123"}; // 30000 chars @ 290ms
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 ""; // 30000 chars @ 30ms
Multiplayer recommendations
TODO
- no publicVariable or public setVariable at high frequency
- the server is supposed to have a lot of memory, use it
createSimpleObject vs createVehicle
TODO create at [0,0,0]
Equivalent commands performance
TODO
if
Use:
if (condition1) then {/* thenCode */} else {/* elseCode */}; // faster
Instead of:
if (condition1) then [{/* thenCode */}, {/* elseCode */}]; // slower
for
Use:
for "var" from 0 to 100 do { /* forCode */ }; // faster
Instead of:
for [{_i = 0}, {_i < 100}, {_i = _i + 1}] do { /* forCode */ }; // slower
forEach vs count
Both commands will step through supplied array of elements one by one and both commands will contain reference to current element in the _x variable.
However, count loop is a little faster than forEach loop, but it does not have _forEachIndex variable.
Also, there is a limitation as the code inside count expects Boolean or Nothing while itself returns Number.
{diag_log _x} count [1,2,3,4,5,6,7,8,9]; // faster
{diag_log _x} forEach [1,2,3,4,5,6,7,8,9]; // slower
Usage:
_someoneIsNear = {_x distance [0,0,0] < 1000} count allUnits > 0;
_someoneIsNear = { if (_x distance [0,0,0] < 1000) exitWith { true }; false } forEach allUnits;
TODO: findIf?
+ and format
When concatenating more than two strings, format is faster than +. Use:
format ["%1%2%3%4%5", "string1", "string2", "string3", "string4", "string5"]; // faster
Instead of:
"string1" + "string2" + "string3" + "string4" + "string5"; // slower
select and if
Use:
result = ["false result", "true result"] select _condition; // faster
Instead of the lazy-evaluated if:
result = if (_condition) then { "true result"; } else { "false result"; }; // slower
private
Use:
private _a = 1;
private _b = 2;
private _c = 3;
private _d = 4;
// 0.0023 ms
Instead of:
private ["_a", "_b", "_c", "_d"];
_a = 1;
_b = 2;
_c = 3;
_d = 4;
// 0.0040 ms
However:
for "_i" from 1 to 100 do { private _a = 1; private _b = 2; private _c = 3; private _d = 4; }; // 0.186776 ms
is slower than:
private ["_a", "_b", "_c", "_d"];
for "_i" from 1 to 100 do
{
_a = 1;
_b = 2;
_c = 3;
_d = 4;
};
// 0.146327 ms
The reason behind this is that in the first example variables are created, assigned and deleted in each loop.
In the second example the variables are only created/deleted once and changed often.
objectParent and vehicle
isNull objectParent player // 0.0013 ms, slightly faster
vehicle player == player // 0.0022 ms
nearEntities and nearestObjects
- nearEntities is much faster than nearestObjects given on range and amount of object(s) which are within the given range.
If a range was set to more thean 100 meters it is highly recommend to use nearEntities instead of nearestObjects.
Note: nearEntities only searches for objects which are alive. Killed units, destroyed vehicles, static objects and buildings will be ignored by the nearEntities command.
Config path delimiter
>> is slightly faster than / when used in config path with configFile or missionConfigFile:
configFile >> "CfgVehicles" // 0.0019 ms
is (very slighlty) faster than
configFile / "CfgVehicles" // 0.0023 ms
getPos* and setPos*
Position World is the fastest
getPosASL, getPosATL and visiblePositionASL are faster than getPos, position and visiblePosition. But new to Arma 3 command getPosWorld is the fastest of them all.
getPosWorld player
//0.0014 ms
visiblePositionASL player
//0.0014 ms
visiblePosition player
//0.0048 ms
getPos player
//or
position player
//0.005 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, some 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 by append: if you don't need the original array to be modified, use + - Comparing: use isEqualTo instead of BIS_fnc_areEqual or a custom comparison function
- Finding common items: in forEach loop has been replaced by arrayIntersect
- Condition filtering: forEach can be replaced by select (alternative syntax)
result = [];
{
if (_x % 2 == 0) then
{
result pushBack _x;
};
} forEach arrayOfNumbers; // 2.57 ms
result = (arrayOfNumbers select { _x % 2 == 0 }); // 1.55 ms
String operations
String manipulation has been simplified with the following commands:
- string select index
- toArray and toString have been reinforced with splitString and joinString
Multiplayer
- BIS_fnc_MP has been replaced by remoteExec and remoteExecCall