Skip to content
AgileLogics
6 min read

Performance

Prerequisites: Weak tables and garbage collection

Lua is fast, but a few habits make a big difference in hot code — game loops, request handlers, and large data processing. Measure first with os.clock(); optimize only the parts that matter.

1. Use locals

Local variables live in registers; globals are table lookups. Cache frequently used globals and library functions in locals:

local function withGlobals(n)
  local s = 0
  for i = 1, n do s = s + math.sin(i) end
  return s
end

local sin = math.sin
local function withLocals(n)
  local s = 0
  for i = 1, n do s = s + sin(i) end
  return s
end

print(withGlobals(1000) == withLocals(1000))
true

local sin = math.sin at the top of a module is a common idiom for this reason.

2. Build strings with table.concat

local function slowJoin(n)
  local s = ""
  for i = 1, n do s = s .. i .. "," end
  return s
end

local function fastJoin(n)
  local parts = {}
  for i = 1, n do parts[i] = i end
  return table.concat(parts, ",") .. ","
end

local t0 = os.clock(); local a = slowJoin(20000); local slow = os.clock() - t0
t0 = os.clock(); local b = fastJoin(20000); local fast = os.clock() - t0
print(a == b, slow > fast)
true	true

Repeated .. copies the growing string each time (quadratic); collecting pieces and joining once is linear.

3. Avoid creating garbage in loops

  • Reuse tables instead of creating new ones every frame.
  • Avoid closures created inside hot loops.
  • for i = 1, #t and ipairs are both fast; pairs is slower and unordered.
  • Append with t[#t + 1] = v — slightly faster than table.insert.

4. Choose the right structure

Membership tests on a list are linear; on a set (t[key] = true) they're constant time:

local list, set = {}, {}
for i = 1, 5000 do list[i] = "user" .. i; set["user" .. i] = true end

local function inList(name)
  for _, v in ipairs(list) do if v == name then return true end end
  return false
end

local t0 = os.clock()
for _ = 1, 2000 do inList("user4999") end
local listTime = os.clock() - t0
t0 = os.clock()
for _ = 1, 2000 do local _ = set["user4999"] end
local setTime = os.clock() - t0
print(listTime > setTime * 10)
true

5. Use LuaJIT when you can

For numeric and loop-heavy code, LuaJIT is often 10–50× faster than standard Lua, and its FFI calls C functions and uses C structs directly. It's what Neovim, LÖVE, and OpenResty run. See the next lesson for the language differences.