User Tools

Site Tools


arma2:scripting:code_optimization

Code Optimization

ArmA 2 scripting code optimization tips.

Written it twice? Put it in a function

Pre-compilation by the game engine can save up 20x the amount of time processing, even if the initial time is slightly lengthened. If you've written it twice, or if there is a kind of loop consistently being compiled (perhaps a script run by execVM), make it into a function:

funcvar = compile preprocessFileLineNumbers "filename.sqf";

preprocessFileLineNumbers

The preprocessFileLineNumbers command remembers what it has done, so loading a file once will load it into memory, therefore if wanted to refrain from using global variables for example, but wanted a function precompiled, but not saved, you could simply use:

call compile preprocessFileLineNumbers "file";

Remembering the only loss of performance will be the compile time of the string returned and then the call of the code itself.

Length

If any script or function is longer than around 200-300 lines, then perhaps (not true in all cases by all means) you may need to rethink the structure of the script itself, and whether it is all within scope of the functionality required, and if you could do something cleaner, faster and better.

Use Indentation

Write clean code, use indentation which helps you fix 90% of the average errors in your code and makes it easy to read and even understand.

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 is it.

Loops

These first two loop types are identical in speed (+/- 10%), and are more than 3x as fast the proceeding two loop types.

for "_y" from # to # step # do { ... }
 
{ ... } foreach [ ... ];

Where as these two loops are much slower, and for maximum performance, avoided.

while { expression } do { code };
 
for [{ ... },{ ... },{ ... }] do { ... }

waitUntil can be used when you want something to only run once per frame, which can be handy for limiting scripts that may be resource heavy.

waitUntil
{
        expression;
};

Note: you can also use sleep in this command, like:

waitUntil
{
	sleep 1;
	expression;
};

Which effectively makes it not hog all your CPU cycles if you use many simultaneous waitUntil commands.

Threads

The game runs in a scheduled environment, and there are two ways you can run your code. Scheduled and non scheduled.

Depending on where the scope originates, 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 basic examples:

  • Triggers are inside what we call the 'non-scheduled' environment;
  • All pre-init code executions are without scheduling;
  • FSM conditions are without scheduling;
  • Event handlers (on units and in GUI) are without scheduling;
  • Sqf code which called from sqs-code are without scheduling.

The 0.3ms delay (not 3ms)

The 0.3ms delay is a delay introduced in ArmA2 for scheduled environments to prevent script overload during game play (assuredly due to the occurrences in ArmA1). The 0.3ms is experienced between statements and upon finishing a statement, will proceed down the schedule until returning to the start to go through the loop again. As I always seem to be able to explain things in code, the behavior as far as I can explain it is the following:

while (true)
{
	{
		execute1Statement (_x);
		sleep 0.3ms;
	} foreach SCRIPTS;
}

You can therefore see that the delay between statement execution inside your script is dependent on n scripts (or threads). In this way you should refrain from creating to many threads in your code to stop the system from scaling to the point functionality is seriously degraded. This information is subject to change as BIS may change things in the latest beta releases.

When am I creating new threads?

Using the spawn/execVM/exec commands are creating small threads within the scheduler for ArmA2 (verification from a BIS DEV for specifics is needed here), and as the scheduler works through each one individually, the delay between returning to the start of the schedule to proceed to the next line of your code can be very high (in high load situations, delays of up to a minute can be experienced!).

Obviously this problem is only an issue when your instances are lasting for longer than their execution time, ie spawned loops with sleeps that never end, or last a long time.

Avoid O(n^2)

Commonly you may set up foreach foreach's. 'For' example:

{
	{ ...} foreach [0, 0, 0];
} foreach [0, 0, 0];

This example is of the order (n^2) (3^2 = 9 iterations). For arrays that are twice as big, you will run 4 times slower, and for arrays that are 3 times as big you will run 9 times slower! Of course, you don't always have a choice, and if one (or both) of the arrays is guaranteed to be small it's not really as big of a deal.

Deprecated/Slow Commands

Adding elements to an array. Set is around 2x faster than binary addition.

_a set [count _a,_v];

Instead of:

_a = _a + [_v];

Removing elements from an array

When FIFO removing elements from an array, the set removal method works best, even if it makes a copy of the new array.

arrayx set [0, objNull];
arrayx = arrayx - [objNull];

CreateVehicle Array

It is highly recommended to use a standard of createVehicle array rather than the older (deprecated version) createVehicle. It is up to 500x faster than its older brother.

Measuring Velocity Scalar

Sure we can just use Pythagorean theorem to calculate the magnitude from a velocity vector, but a command native to the engine runs much faster (over 10x faster) than the math.

vector distance [0, 0, 0];

Works for 2D vectors as well.

Getting object positions

getPosASL and getPosATL are 2.11x faster than regular getPos

If your feeling self-conscious and want an AGL solution (ie identical to that of getPos):

private "_pos";
_pos = getposATL player;
if (surfaceIsWater _pos) then
{
	_pos = getposASL player;
};

It is still 25% faster than its getPos twin.

How to test and gain this information yourself?

There is a few ways to measure the information and run time durations inside ArmA 2, mostly using differencing of the time itself. The following code setup allows different ways to retrieve the information (chat text, rpt file, clipboard)

_fnc_dump =
{
	player globalchat str _this;
	diag_log str _this;
	//copytoclipboard str _this;
};
_t1 = diag_tickTime;
// ... code to test
(diag_tickTime - _t1) call _fnc_dump;
arma2/scripting/code_optimization.txt · Last modified: 2011-07-12 14:58 (external edit)