Skip to content

Optimization Guide

ffredyk edited this page Jul 23, 2026 · 1 revision

Optimization Guide

Performance tips for SQ# scripts. Based on Arma 3 Code Optimisation best practices, adapted for SQ#'s bytecode VM and scheduler model.

The Three Rules

  1. Make it work — Get correct behavior first
  2. Make it readable — Clean names, consistent format
  3. Optimise then — Only after it works and is readable

SQ#-Specific Optimizations

String Building

// SLOW — each + creates a new string (1000 allocations):
_msg = "";
for "_i" from 0 to 999 do {
    _msg = _msg + "x";
};

// FAST — build array, join once (1 allocation):
_parts = [];
for "_i" from 0 to 999 do {
    _parts pushBack "x";
};
_msg = joinString [_parts, ""];

Array Building

// FAST — mutate in place:
_arr pushBack _item;           // O(1) amortized
_arr append _otherArray;       // O(n) bulk

// SLOW — creates new array every time:
_arr = _arr + [_item];         // O(n) copy

Loop Optimization

// FAST — for-from-to (native loop):
for "_i" from 0 to (count _arr - 1) do {
    _x = _arr select _i;
    process(_x);
};

// FAST — forEach (when _x needed):
{ process(_x) } forEach _arr;

// SLOW — while with count every iteration:
_i = 0;
while { _i < count _arr } do {  // count called every loop!
    _x = _arr select _i;
    process(_x);
    _i = _i + 1;
};

// FIX — cache the count:
_i = 0;
_count = count _arr;
while { _i < _count } do {
    _x = _arr select _i;
    process(_x);
    _i = _i + 1;
};

Early Exit

// SLOW — checks ALL elements:
{ _x == target } count _arr > 0;

// FAST — stops at first match:
(_arr find target) != -1;

// Also fast — forEach with exitWith:
_arr forEach {
    if (_x == target) exitWith { _found = true; };
};

Pre-Allocation

// SLOW — grows dynamically:
_arr = [];
for "_i" from 0 to 999 do {
    _arr pushBack _i;           // may reallocate internally
};

// FASTER — pre-allocate:
_arr = [];
_arr resize 1000;
for "_i" from 0 to 999 do {
    _arr set [_i, _i];          // no reallocation
};

// FASTEST — just pushBack (amortized O(1)):
_arr = [];
for "_i" from 0 to 999 do {
    _arr pushBack _i;           // good enough for most cases
};

Caching

// SLOW — repeated lookups:
for "_i" from 0 to 999 do {
    _val = _map get "key";     // hashmap lookup each iteration
    process(_val);
};

// FAST — cache the value:
_val = _map get "key";          // one lookup
for "_i" from 0 to 999 do {
    process(_val);
};

HashMap vs Array Scan

// SLOW for frequent lookups — O(n):
_idx = _arr find _target;

// FAST — O(1):
_val = _map get _target;

Use hashmaps when you need frequent key-based lookups. Use arrays for ordered data and iteration.

Frozen Arrays for Sharing

// SLOW — copy data to every scheduler:
[+_bigArray] spawnOn ["AI", { ... }];      // deep copies entire array
[+_bigArray] spawnOn ["Physics", { ... }]; // copies again

// FAST — freeze once, share everywhere:
_frozen = freeze _bigArray;
[_frozen] spawnOn ["AI", { _local = thaw (_this select 0); ... }];
[_frozen] spawnOn ["Physics", { _local = thaw (_this select 0); ... }];

Scheduler-Aware Design

// Check if work is needed before spawning:
if (schedulerLoad 2 > 80) then {
    // AI scheduler overloaded — defer work
    _deferred pushBack _task;
} else {
    _task spawnOn ["AI", { process(_this); }];
};

f-strings vs format

// FAST — compile-time interpolation:
_msg = f"Player {_name} has {_hp} HP";

// SLOWER — runtime parsing:
_msg = format ["Player %1 has %2 HP", _name, _hp];

Prefer f-strings when the template is known at compile time. Use format only for dynamic templates.

Anti-Patterns

Don't: Repeated count in Loop

// BAD:
while { _i < count _arr } do { ... };
// FIX:
_count = count _arr;
while { _i < _count } do { ... };

Don't: String Concatenation in Loop

// BAD:
_msg = "";
{ _msg = _msg + str _x + "," } forEach _arr;
// FIX:
_parts = [];
{ _parts pushBack str _x } forEach _arr;
_msg = joinString [_parts, ","];

Don't: Array Copy in Loop

// BAD:
{ _arr = _arr + [_x] } forEach _items;
// FIX:
_arr append _items;

Don't: Manual Sleep Loop When await Exists

// BAD — busy-wait:
while { !scriptDone _handle } do { sleep 0.01; };
// GOOD:
_result = await _handle;

Benchmarking

Use diag_tickTime to measure:

_start = diag_tickTime;
// ... code to measure ...
_elapsed = diag_tickTime - _start;
diag_log f"Operation took {_elapsed * 1000}ms";

See Also

SQ# Wiki

Home

Engine Docs

Migration

Commands

Value Constructors

Arithmetic

Comparison

Logic

Array

String

Math

Random

Type & Introspection

HashMap

Code Execution

Concurrency

Scheduler

Thread Safety

Error

Output

Time

Multiplayer

Compiler

Clone this wiki locally