🎓 A1Learner
🔍
0 of 52 lessons completed

Lua Performance Habits

A performance habit is a small coding choice that cuts repeated work inside a loop. Two habits at this level are localising a lookup and joining strings with table.concat.

Localising a global cuts lookups

The counting metatable below ticks once for each lookup of the missing global double. The first loop looks double up five times, and the second loop looks it up only once, when it is stored in a local.

local calls = 0
setmetatable(_G, {__index = function(t, k)
  if k == "double" then
    calls = calls + 1
    return function(x) return x * 2 end
  end
  return nil
end})
local total = 0
for i = 1, 5 do
  total = total + double(i)
end
print(total, calls)
local d = double
total = 0
for i = 1, 5 do
  total = total + d(i)
end
print(total, calls)

table.concat joins in one call

The dotdot loop below performs four joining operations, one per piece. The table.concat call performs one joining operation for the same four pieces.

local parts = {"a", "b", "c", "d"}
local s = ""
local dotOps = 0
for i = 1, 4 do
  s = s .. parts[i]
  dotOps = dotOps + 1
end
print(s)
print(dotOps)
local concatCalls = 0
local function joinAll(list)
  concatCalls = concatCalls + 1
  return table.concat(list)
end
local joined = joinAll(parts)
print(joined)
print(concatCalls)

Localising a table field cuts lookups

The counting table below ticks once for each lookup of a missing field. The first loop looks the field up five times, and the second loop looks it up only once, when it is stored in a local.

local lookups = 0
local data = setmetatable({}, {__index = function(t, k)
  lookups = lookups + 1
  return 10
end})
local total = 0
for i = 1, 5 do
  total = total + data.value
end
print(total, lookups)
local v = data.value
total = 0
for i = 1, 5 do
  total = total + v
end
print(total, lookups)

Example

local scores = {10, 20, 30}
local lookups = 0
local data = setmetatable({}, {__index = function(t, k)
  lookups = lookups + 1
  return scores
end})
local list = data.scores
local total = 0
for i = 1, 3 do
  total = total + list[i]
end
local parts = {"total", tostring(total)}
local report = table.concat(parts, ": ")
print(report)
print(lookups)
print(total)

Key points

  • Local: a local variable holds a value once, so a loop can reuse it instead of looking it up again.
  • Global lookup: reading a global searches the global table on every read.
  • Table lookup: reading a table field repeats that search on every loop pass.
  • Counter proof: a counter that ticks on each lookup shows the saving, because a clock reading would change on every run.
  • Concat: table.concat joins a list of pieces in one call, instead of building a new string on every loop pass.
  • Dotdot cost: joining with .. inside a loop builds a new string on each pass.

In this example, the scores list is fetched from the counting table once and stored in a local, so the loop reuses the local instead of looking the field up again. The total is then turned into a report with one table.concat call. The lookup counter stays at one while the report still shows the correct total.

Note

measure with counters at this level, never with a clock, because clock readings are not stable across runs.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A loop reads the same table field on every pass. Which change cuts the repeated lookups?