Lou Montana/Sandbox – User
Lou Montana (talk | contribs) m (WIP) |
Lou Montana (talk | contribs) m (WIP) |
||
Line 17: | Line 17: | ||
=== Color code === | === Color code === | ||
* <div style="background-color: red; border-radius: 50%; display: inline-block; vertical-align: middle; height: | * <div style="background-color: red; border-radius: 50%; display: inline-block; vertical-align: middle; height: 1.125em; width: 1.125em;"></div> means you '''must''' change your ways today, ''or with us you will ride…'' | ||
* <div style="background-color: orange; border-radius: 50%; display: inline-block; vertical-align: middle; height: | * <div style="background-color: orange; border-radius: 50%; display: inline-block; vertical-align: middle; height: 1.125em; width: 1.125em;"></div> means you may want to look at it if you are targeting pure performance | ||
* <div style="background-color: green; border-radius: 50%; display: inline-block; vertical-align: middle; height: | * <div style="background-color: green; border-radius: 50%; display: inline-block; vertical-align: middle; height: 1.125em; width: 1.125em;"></div> 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 32: | Line 32: | ||
=== Make it work === | === 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. | {{note|"Premature optimization is the root of all evil." – ''[https://en.wikipedia.org/wiki/Donald_Knuth Donald Knuth]'' }} | ||
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''. | 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 [[allUnits|all the units]] [[distance|near]] [[locationPosition|the city]], and [[forEach|for each]] [[side|west]] soldier in them, [[setDamage|add 30% damage]]''. | |||
* Use [[Arma_3_Startup_Parameters#Developer_Options|-showScriptErrors]] startup parameter and '''make sure your code doesn't throw errors.''' Not only will your code run slower but it may also ''not work at all''. Be sure to read the error, isolate the issue and sort it out [[:Category:Scripting Commands|thanks to this Wiki]]. | |||
* Read your [[Crash_Files|Arma RPT]] (report) to read more details about the error that happened in your code. | |||
=== 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: | ||
* 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. Variables like '''_u''' instead of '''_uniform''' should not be present. | * While [[SQF syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. Variables like '''_u''' instead of '''_uniform''' should not be present. | ||
* ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it. | * ''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. | * 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 | * Same goes for line return; it helps to see a code block wrapping multiple | ||
* Do you see the same code multiple times, only with different parameters? Now is the time to write a function! | * Do you see the same code multiple times, only with different parameters? Now is the time to write a function! | ||
* Is your function code far too long? Split it in understandable-sized bites, for your own sanity. | * Is your function code far too long? Split it in understandable-sized bites, for your own sanity. | ||
Line 49: | Line 53: | ||
See the following code: | 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: | The same example is far more readable with proper spacing, good variable names and intermediate results: | ||
_weaponNames = []; | |||
_allUnitsAliveAndDead = allUnits + allDeadMen; | _allUnitsAliveAndDead = allUnits + allDeadMen; | ||
_allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east }; | _allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east }; | ||
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead; | { _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. | * 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. | ||
==== 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 | |||
_a = _x + BUFFER; | |||
_b = _y + BUFFER; | |||
This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant anymore. | |||
=== 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 | |||
* 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 | ** Store your function in memory to avoid file reading every call with {{Inline code|_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";}} | ||
* Is your variable name far too long? | * 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 76: | Line 94: | ||
== Code optimisation == | == Code optimisation == | ||
{{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 .}} | |||
=== Scheduled and unscheduled environment === | |||
See [[Scheduler]]. | |||
==== Threads ==== | |||
The game runs in a [[Scheduler#Scheduled_Environment|scheduled environment]], and there are two ways you can run your code. [[Scheduler#Scheduled_Environment|Scheduled]] and [[Scheduler#Unscheduled_Environment|unscheduled]]. | |||
The scope origin determines how the code is executed. [[Scheduler#Scheduled_Environment|Scheduled]] code is subject to delays between reading the script across the engine, and execution times can depend on the load on the system at the time. | |||
Some [[Scheduler#Unscheduled_Environment|unscheduled]] examples: | |||
* Triggers | |||
* All pre-init code executions | |||
* FSM conditions | |||
* Event handlers (on units and in GUI) | |||
* SQF code which is called from SQS | |||
=== Lazy evaluation === | === Lazy evaluation === | ||
[[private]] _myVar = [[if]] ([[false]]) [[then]] { 33; } [[else]] { 66; }; | [[private]] _myVar = [33, 66] [[select]] ([[false]]); {{codecomment|// 0.0013 ms}} | ||
[[private]] _myVar = [[if]] ([[false]]) [[then]] { 33; } [[else]] { 66; }; {{codecomment|// 0.0020 ms}} | |||
[[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{codecomment|// 0.0025 ms}} | [[private]] "_myVar"; [[if]] ([[false]]) [[then]] { _myVar = 33; } [[else]] { _myVar = 66; }; {{codecomment|// 0.0025 ms}} | ||
Line 104: | Line 140: | ||
[[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]] ""; {{codecomment|// 30 ms}} | ||
=== Manipulating arrays === | |||
==== Adding elements ==== | |||
New commands [[append]] and [[pushBack]] hold the best score. | |||
[[private]] _array = [0,1,2,3]; _array [[append]] [4,5,6]; {{codecomment|// 0.0020 ms}} | |||
[[private]] _array = [0,1,2,3]; _array = _array + [4,5,6]; {{codecomment|// 0.0023 ms}} | |||
[[private]] _array = [0,1,2,3]; { _array [[set]]<nowiki>[</nowiki>[[count]] _array, _x]; } [[forEach]] [4,5,6]; {{codecomment|// 0.0080 ms}} | |||
[[private]] _array = [0,1,2,3]; _array [[pushBack]] 4; {{codecomment|// 0.0016 ms}} | |||
[[private]] _array = [0,1,2,3]; _array = _array + [4]; {{codecomment|// 0.0021 ms}} | |||
[[private]] _array = [0,1,2,3]; _array [[set]] <nowiki>[</nowiki>[[count]] _array, _x]; {{codecomment|// 0.0022 ms}} | |||
==== Removing elements ==== | |||
[[private]] _array = [0,1,2,3]; _array [[deleteAt]] 0; {{codecomment|// 0.0015 ms}} | |||
[[private]] _array = [0,1,2,3]; _array [[set]] [0, [[objNull]]]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{codecomment|// 0.0038 ms}} | |||
[[private]] _array = [0,1,2,3]; _array [[deleteRange]] [1, 2]; {{codecomment|// 0.0018 ms}} | |||
[[private]] _array = [0,1,2,3]; { _array [[set]] [_x, [[objNull]]] } [[forEach]] [1,2]; _array = _array - <nowiki>[</nowiki>[[objNull]]]; {{codecomment|// 0.0078 ms}} | |||
=== Multiplayer recommendations === | === Multiplayer recommendations === | ||
Line 111: | Line 166: | ||
* variable name length impacts network? | * variable name length impacts network? | ||
<!-- | |||
=== createSimpleObject vs createVehicle === | === createSimpleObject vs createVehicle === | ||
''TODO'' | ''TODO'' | ||
* [[createSimpleObject]] is over '''43x''' faster than createVehicle! | |||
createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"];// ~3.5 ms | |||
createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]];// ~0.08 ms | |||
private _cube = createVehicle ["Land_VR_Shape_01_cube_1m_F",[0,0,0],[],0,"none"]; deleteVehicle _cube; // 244 cycles, 4.11066 ms | |||
private _cube = createSimpleObject ["a3\structures_f_mark\vr\shapes\vr_shape_01_cube_1m_f.p3d",[0,0,0]]; deleteVehicle _cube; // freezes the game for seconds, but 9748 cycles / 0.102585 ms?? Cannot confirm. | |||
* creating an object at [0,0,0] ''then'' [[setPos]] it will be faster than creating it at the wanted pos (''if spawning obstacles are present? What about "CAN_COLLIDE"?'') | |||
--> | |||
Line 119: | Line 182: | ||
=== if === | === if === | ||
[[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{codecomment|// fastest}} | [[if]]..[[then]] { {{codecomment|/* thenCode */}} }; {{codecomment|// fastest 0.0011 ms}} | ||
[[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{codecomment|// fast}} | [[if]]..[[exitWith]] { {{codecomment|/* exitCode */}} }; {{codecomment|// fast 0.0014 ms}} | ||
[[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{codecomment|// normal}} | [[if]]..[[then]] { {{codecomment|/* thenCode */}} } [[else]] { {{codecomment|/* elseCode */}} }; {{codecomment|// normal 0.0015 ms}} | ||
[[if]]..[[then]] [{ {{codecomment|/* thenCode */}} },{ {{codecomment|/* elseCode */}} }] {{codecomment|// normal | [[if]]..[[then]] [{ {{codecomment|/* thenCode */}} },{ {{codecomment|/* elseCode */}} }] {{codecomment|// normal 0.0016 ms}} | ||
=== for === | === for === | ||
Line 133: | Line 192: | ||
[[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 */}} }; {{codecomment|// 0.030 ms}} | ||
=== forEach vs count === | === forEach vs count vs findIf === | ||
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. | 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 /> | 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]]. | 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]; {{codecomment|// 0.082 ms}} | |||
{ [[diag_log]] _x } [[forEach]] [1,2,3,4,5]; {{codecomment|// 0.083 ms}} | |||
Usage: | Usage: | ||
< | _someoneIsNear = ([[allUnits]] [[findIf]] { _x [[distance]] [0,0,0] < 1000) != -1; | ||
_someoneIsNear = { _x [[distance]] [0,0,0] < 1000} [[count]] [[allUnits]] > 0; | |||
_someoneIsNear = { | _someoneIsNear = { | ||
[[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] }; | [[if]] ([[_x]] [[distance]] [0,0,0] < 1000) [[exitWith]] { [[true]] }; | ||
Line 159: | Line 219: | ||
=== select and if === | === select and if === | ||
Use | 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]]; {{codecomment|// 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"; }; {{codecomment|// 0.0017 ms}} | ||
Line 165: | Line 225: | ||
=== private === | === 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}} since {{arma3}} v1.53) 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}} | {{codecomment|// 0.0023 ms}} | ||
private ["_a", "_b", "_c", "_d"]; | [[private]] ["_a", "_b", "_c", "_d"]; | ||
_a = 1; | _a = 1; | ||
_b = 2; | _b = 2; | ||
_c = 3; | _c = 3; | ||
_d = 4; | _d = 4; | ||
{{codecomment|// 0.0040 ms}} | {{codecomment|// 0.0040 ms}} | ||
Line 181: | Line 241: | ||
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. | ||
[[for]] "_i" from 1 to 100 do | [[private]] ["_a", "_b", "_c", "_d"]; | ||
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]] | |||
{ | { | ||
_a = 1; _b = 2; _c = 3; _d = 4; | |||
}; | }; | ||
{{codecomment|// 0. | {{codecomment|// 0.146327 ms}} | ||
[[for]] "_i" [[from]] 1 [[to]] 100 [[do]] | |||
{ | { | ||
_a = 1; | [[private]] _a = 1; [[private]] _b = 2; [[private]] _c = 3; [[private]] _d = 4; | ||
}; | }; | ||
{{codecomment|// 0. | {{codecomment|// 0.186776 ms}} | ||
=== objectParent and vehicle === | === objectParent and vehicle === | ||
[[isNull]] [[objectParent]] [[player]] {{codecomment|// 0.0013 ms}} | |||
[[vehicle]] [[player]] == [[player]] {{codecomment|// 0.0022 ms}} | |||
=== nearEntities and nearestObjects === | === nearEntities and nearestObjects === | ||
* [[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of | * [[nearEntities]] is much faster than [[nearestObjects]] given on range and amount of objects within the given range. | ||
If | If range is over 100 meters it is highly recommended to use [[nearEntities]] over [[nearestObjects]]. | ||
Note: [[nearEntities]] only searches for objects | Note: [[nearEntities]] only searches for [[alive]] objects. | ||
Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command. | Killed units, destroyed vehicles, static objects and buildings will be ignored by the [[nearEntities]] command. | ||
=== Config path delimiter === | === 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" {{codecomment|// 0.0019 ms}} | ||
[[configFile]] / "CfgVehicles" {{codecomment|// 0.0023 ms}} | |||
[[configFile]] / "CfgVehicles" {{codecomment|// 0.0023 ms}} | {{note|A config path can be stored in a variable for later use, saving cycles: {{Inline code|_cfgVehicles {{=}} [[configFile]] >> "CfgVehicles"}}}} | ||
=== getPos* and setPos* === | === getPos* and setPos* === | ||
[[getPosWorld]] {{codecomment|// 0.0015 ms}} | [[getPosWorld]] {{codecomment|// 0.0015 ms}} | ||
[[getPosASL]] {{codecomment|// 0.0016 ms}} | [[getPosASL]] {{codecomment|// 0.0016 ms}} | ||
Line 237: | Line 293: | ||
== 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, | For that, if you are converting scripts from older versions of the engine, the following aspects should be reviewed. | ||
=== Loops === | === Loops === | ||
Line 255: | Line 311: | ||
* '''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 = []; | |||
{ | { | ||
[[if]] (_x % 2 == 0) [[then]] | |||
{ | |||
result [[pushBack]] [[_x]]; | |||
}; | |||
} [[forEach]] arrayOfNumbers; {{codecomment|// 2.57 ms}} | } [[forEach]] arrayOfNumbers; {{codecomment|// 2.57 ms}} | ||
result = (arrayOfNumbers [[select]] { _x % 2 == 0 }); {{codecomment|// 1.55 ms}} | result = (arrayOfNumbers [[select]] { _x % 2 == 0 }); {{codecomment|// 1.55 ms}} | ||
Revision as of 00:10, 10 May 2018
Code 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.
Color code
- means you must change your ways today, or with us you will ride…
- means you may want to look at it if you are targeting pure performance
- 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.
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:note Your first goal when coding is to make your code do what you want it does. A good way to reach this objective is to read and getting inspired by other people's code. If you understand it by reading it once, it is probably a good source of inspiration.
- When starting from scratch if you know what you want but miss the specific steps to get to your point, it is a good practice to write down in your native language what you want to do. E.g Get all the units near the city, and for each west soldier in them, add 30% damage.
- Use -showScriptErrors startup parameter and make sure your code doesn't throw errors. Not only will your code run slower but it may also not work at all. Be sure to read the error, isolate the issue and sort it out thanks to this Wiki.
- Read your Arma RPT (report) to read more details about the error that happened in your code.
Make it readable
Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:
- While SQF is (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. Variables like _u instead of _uniform should not be present.
- One-lining (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
- Indentation is important for the human mind, and space is too. Space is free, use it.
- Same goes for line return; it helps to see a code block wrapping multiple
- Do you see the same code multiple times, only with different parameters? Now is the time to write a function!
- Is your function code far too long? Split 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.
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.
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 _a = _x + BUFFER; _b = _y + BUFFER;
This also allows quick modifying of code; with the obvious loss of dynamics, but in that case it isn't a constant anymore.
Optimise then
Once you know what is what, you can understand your code better.
- use private variables instead of global variables (preceded with an underscore) as much as possible
- You were iterating multiple times on the same array?
- You should be able to spot your issue now.
- Are you using execVM on the same file, many times?
- Store your function in memory to avoid file reading every call with
_myFunction = compile preprocessFileLineNumbers "myFile.sqf";
- Store your function in memory to avoid file reading every call with
- Is your variable name far too long?
- Find a smaller name, according to the variable scope:
e.g
{ _opforUnitUniform = uniform _x; systemChat _opforUnitUniform; } forEach _allOpforUnits;
becomes
{ _uniform = uniform _x; systemChat _uniform; } forEach _allopforUnits;
Code optimisation
Scheduled and unscheduled environment
See Scheduler.
Threads
The game runs in a scheduled environment, and there are two ways you can run your code. Scheduled and unscheduled.
The scope origin determines how the code is executed. Scheduled code is subject to delays between reading the script across the engine, and execution times can depend on the load on the system at the time.
Some unscheduled examples:
- Triggers
- All pre-init code executions
- FSM conditions
- Event handlers (on units and in GUI)
- SQF code which is called from SQS
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
Successive condition check
In SQF the following code will check all and every condition, even if one fail:
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
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.
private _array = [0,1,2,3]; _array append [4,5,6]; // 0.0020 ms private _array = [0,1,2,3]; _array = _array + [4,5,6]; // 0.0023 ms private _array = [0,1,2,3]; { _array set[count _array, _x]; } forEach [4,5,6]; // 0.0080 ms
private _array = [0,1,2,3]; _array pushBack 4; // 0.0016 ms private _array = [0,1,2,3]; _array = _array + [4]; // 0.0021 ms private _array = [0,1,2,3]; _array set [count _array, _x]; // 0.0022 ms
Removing elements
private _array = [0,1,2,3]; _array deleteAt 0; // 0.0015 ms private _array = [0,1,2,3]; _array set [0, objNull]; _array = _array - [objNull]; // 0.0038 ms
private _array = [0,1,2,3]; _array deleteRange [1, 2]; // 0.0018 ms private _array = [0,1,2,3]; { _array set [_x, objNull] } forEach [1,2]; _array = _array - [objNull]; // 0.0078 ms
Multiplayer recommendations
TODO
- no publicVariable or public setVariable at high frequency
- the server is supposed to have a lot of memory, use it
- variable name length impacts network?
Equivalent commands performance
if
if..then { /* thenCode */ }; // fastest 0.0011 ms if..exitWith { /* exitCode */ }; // fast 0.0014 ms if..then { /* thenCode */ } else { /* elseCode */ }; // normal 0.0015 ms if..then [{ /* thenCode */ },{ /* elseCode */ }] // normal 0.0016 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 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]; // 0.082 ms { diag_log _x } forEach [1,2,3,4,5]; // 0.083 ms
Usage: _someoneIsNear = (allUnits findIf { _x distance [0,0,0] < 1000) != -1; _someoneIsNear = { _x distance [0,0,0] < 1000} count allUnits > 0;
_someoneIsNear = { if (_x distance [0,0,0] < 1000) exitWith { true }; false } forEach allUnits;
findIf (since Arma 3 v1.82) stops array iteration as soon as the condition is met.
[0,1,2,3,4,5,6,7,8,9] findIf { _x == 2 }; // 0.005 ms _quantity = { _x == 2 } count [0,1,2,3,4,5,6,7,8,9]; // 0.0114 ms
+ 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 [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
private
Direct declaration (private _var = value
since Arma 3 v1.53) 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 100 do { _a = 1; _b = 2; _c = 3; _d = 4; }; // 0.146327 ms
for "_i" from 1 to 100 do { private _a = 1; private _b = 2; private _c = 3; private _d = 4; }; // 0.186776 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.
Note: nearEntities only searches for alive objects. 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 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"
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
- 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 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. Use them!