Reading — step 1 of 4
Learn
Lua uses a generational (5.4+) or incremental mark-and-sweep (5.1-5.3) garbage collector. You rarely think about it — but in latency-sensitive code (game frames, hot HTTP loops), the GC pause can matter.
collectgarbage() is the control surface:
collectgarbage("count") -- KB used (with fraction)
collectgarbage("collect") -- full collection (default)
collectgarbage("stop") -- pause GC (allocations only)
collectgarbage("restart") -- resume GC
collectgarbage("step", n) -- run n KB of work, return true if cycle ended
collectgarbage("setpause", n) -- new collection when heap grows by n%
collectgarbage("setstepmul", n)-- step size multiplier
Default tuning (Lua 5.3):
pause = 200— start a new GC cycle when heap is 2x last collectionstepmul = 200— collect 2x faster than allocation
Latency-tuned (more frequent, smaller pauses):
collectgarbage("setpause", 100)
collectgarbage("setstepmul", 100)
Makes GC do more work per allocation; smaller pauses but lower throughput.
Throughput-tuned (less frequent, longer pauses):
collectgarbage("setpause", 400)
collectgarbage("setstepmul", 500)
Good for batch processing. Bad for game loops.
Manual control for hot paths:
collectgarbage("stop")
for frame = 1, 1000 do
update()
render()
end
collectgarbage("restart")
collectgarbage("collect") -- catch up after the loop
You prevent GC during the critical section. This works only if you're allocation-light — otherwise heap explodes.
Avoiding allocations (the real performance lever):
- Pre-allocate buffers; reuse them.
- Avoid string concatenation in loops (
table.concatinstead). - Avoid creating tables inside loops.
- Use
localreferences to globals (local print = print). - Avoid closures in hot paths (each closure allocates upvalues).
- Reuse functions instead of creating fresh closures per call.
Memory profiling:
local before = collectgarbage("count")
run_my_code()
local after = collectgarbage("count")
print(string.format("used %.1f KB", after - before))
Generational mode (Lua 5.4):
collectgarbage("generational") -- minor GCs cheap; major GCs less often
collectgarbage("incremental") -- back to default
Generational mode is faster for code that allocates many short-lived objects (most code) but uses more memory.
Real-world:
- Roblox: heavily customized GC for soft real-time
- LÖVE / game engines: stop GC during frame updates, run between frames
- OpenResty: GC tuning per-worker for HTTP latency
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…