Performance & benchmarks
What Asobi handles, how to measure it, and how to tune tick rates, zone sizing, and BEAM knobs when you need more headroom.
Reference benchmarks
Measured on a single 4-core VM (2026-04 runs, asobi_bench):
Scenario Players Rate CPU RAM
─────────────────────────────────────────────────────────────────────
Match: 10-player arena, 20 Hz ticks 10 20 Hz ~3% ~40 MB
Match: 100 concurrent arenas 1000 20 Hz ~35% ~800 MB
World: 500 players on 128K x 128K 500 20 Hz ~55% ~208 MB
WebSocket fan-out: chat broadcast 10000 10 Hz ~25% ~600 MB
Matchmaker tick (10K tickets) 10000 1 Hz ~10% negligibleWorst-case p99 tick latency stays under 5 ms in all scenarios. Full methodology + reproducer: asobi/bench.
Tick budget
Everything in a tick must finish before the next tick fires. Default 20 Hz = 50 ms budget. If zone_tick/2 takes 60 ms, you miss the next tick; state updates bunch up and become bursty. Keep tick work to <50% of the budget to leave headroom for input spikes.
Tuning ticks
- Lower the tick rate (10–15 Hz) if you don't need 20 Hz visual smoothness.
- Split heavy per-entity work across frames (round-robin): see the cookbook AI-stepping recipe.
- Move expensive deterministic sims to NIFs if you've profiled them as the bottleneck (rare).
Zone sizing
- Too small: many zones, more subscription churn on movement, higher delta overhead.
- Too large: one zone dominates a CPU core; fewer parallelism opportunities.
- Rule of thumb: target 10–50 entities per zone under peak load, grid cell = 2× max interest radius.
Delta compression
Only changed fields ship. If your state map is deep (nested entities, lists of lists), diffs can be expensive to compute. Flatten hot-path state into top-level fields for cheap diffs; keep per-player “what did they see last tick” in match state so get_state short-circuits when nothing changed.
BEAM knobs
# vm.args — baseline for a 16-core host
+sbt db # bind schedulers to cores
+S 16:16 # schedulers = online cores
+A 64 # async IO threads for file/socket reads
+P 10000000 # max processes (matches + zones + sessions)
+K true # kernel poll (Linux)
+zdbbl 32768 # distribution buffer, 32 MiB (cluster only)Profiling
reconfor live-node inspection (top processes by memory/reductions).fprof/eproffor deterministic CPU profiles of specific callbacks.msaccfor scheduler utilization / where time goes.observer_clifor a continuous dashboard.
Lua-specific
- Luerl is interpreted. Lua callbacks run 3–10× slower than equivalent Erlang. For a 10 Hz arena this is fine; for 60 Hz physics, write the hot loop in Erlang and call from Lua.
- Avoid allocating big tables every tick — reuse state. Luerl's GC is per-VM and stops the tick.
- Set
instruction_limitinasobi_luaconfig to prevent pathological scripts from hogging a tick.
Load testing
The asobi_bench repo includes tsung-style scripts for WebSocket + REST load. Run against a staging node before major releases.