Web Analytics

Performance Optimization in Lua

Advanced ~25 min read

Writing fast, efficient code is crucial for performance-critical applications. In this final lesson, you'll learn how to profile your Lua code, identify bottlenecks, and apply optimization techniques. Let's explore performance optimization in Lua!

Profiling Your Code

Simple Timing

local function benchmark(func, iterations)
    iterations = iterations or 1000000
    
    local start = os.clock()
    for i = 1, iterations do
        func()
    end
    local finish = os.clock()
    
    local elapsed = finish - start
    print(string.format("Time: %.6f seconds", elapsed))
    print(string.format("Per iteration: %.9f seconds", elapsed / iterations))
end

-- Usage
benchmark(function()
    local x = 10 * 20
end)

Profiler Function

local Profiler = {}

function Profiler:new()
    local self = {
        times = {},
        counts = {}
    }
    setmetatable(self, {__index = Profiler})
    return self
end

function Profiler:start(name)
    self.times[name] = os.clock()
end

function Profiler:stop(name)
    if not self.times[name] then return end
    
    local elapsed = os.clock() - self.times[name]
    self.counts[name] = (self.counts[name] or 0) + 1
    self.times[name] = (self.times[name] or 0) + elapsed
end

function Profiler:report()
    print("\n=== Profiler Report ===")
    for name, time in pairs(self.times) do
        local count = self.counts[name]
        print(string.format("%s: %.6fs (%d calls, %.9fs avg)",
            name, time, count, time / count))
    end
end

-- Usage
local prof = Profiler:new()

prof:start("calculation")
-- Do work
prof:stop("calculation")

prof:report()
Output
Click Run to execute your code

Optimization Techniques

1. Use Local Variables

-- Slow: Global lookups
function slow()
    for i = 1, 1000000 do
        local x = math.sin(i)
    end
end

-- Fast: Local cache
function fast()
    local sin = math.sin
    for i = 1, 1000000 do
        local x = sin(i)
    end
end

2. Avoid table.insert() in Loops

-- Slower
local function buildArray1(n)
    local arr = {}
    for i = 1, n do
        table.insert(arr, i)
    end
    return arr
end

-- Faster: Direct assignment
local function buildArray2(n)
    local arr = {}
    for i = 1, n do
        arr[i] = i
    end
    return arr
end

3. Use table.concat() for Strings

-- Slow: String concatenation
local function buildString1(n)
    local s = ""
    for i = 1, n do
        s = s .. tostring(i)
    end
    return s
end

-- Fast: table.concat()
local function buildString2(n)
    local parts = {}
    for i = 1, n do
        parts[i] = tostring(i)
    end
    return table.concat(parts)
end

4. Reuse Tables

-- Slow: Create new table each iteration
for i = 1, 10000 do
    local temp = {}
    temp.x = i
    temp.y = i * 2
    -- Use temp
end

-- Fast: Reuse table
local temp = {}
for i = 1, 10000 do
    temp.x = i
    temp.y = i * 2
    -- Use temp
end
Output
Click Run to execute your code

Memory Management

Garbage Collection

-- Check memory usage
local function getMemoryUsage()
    return collectgarbage("count")
end

print("Memory:", getMemoryUsage(), "KB")

-- Force garbage collection
collectgarbage("collect")

-- Set GC parameters
collectgarbage("setpause", 100)  -- Pause between collections
collectgarbage("setstepmul", 200)  -- GC step multiplier

Weak Tables

-- Weak table for caching
local cache = {}
setmetatable(cache, {__mode = "v"})  -- Weak values

function getCachedValue(key)
    if not cache[key] then
        cache[key] = expensiveComputation(key)
    end
    return cache[key]
end

Algorithm Optimization

Memoization

-- Slow: Recursive fibonacci
local function fib1(n)
    if n <= 1 then return n end
    return fib1(n - 1) + fib1(n - 2)
end

-- Fast: Memoized fibonacci
local function memoize(func)
    local cache = {}
    return function(n)
        if not cache[n] then
            cache[n] = func(n)
        end
        return cache[n]
    end
end

local fib2
fib2 = memoize(function(n)
    if n <= 1 then return n end
    return fib2(n - 1) + fib2(n - 2)
end)

print(fib2(30))  -- Much faster!

Early Exit

-- Slow: Check all items
local function findItem1(items, target)
    local found = false
    for i, item in ipairs(items) do
        if item == target then
            found = true
        end
    end
    return found
end

-- Fast: Early exit
local function findItem2(items, target)
    for i, item in ipairs(items) do
        if item == target then
            return true
        end
    end
    return false
end
Output
Click Run to execute your code

Common Performance Pitfalls

Avoid These:
  • Global variables: Slower than local
  • Repeated table lookups: Cache in locals
  • String concatenation in loops: Use table.concat()
  • Creating tables in loops: Reuse when possible
  • Unnecessary function calls: Inline simple operations
  • Premature optimization: Profile first!

Performance Best Practices

Best Practices:
  1. Profile before optimizing: Find real bottlenecks
  2. Use local variables: Faster than globals
  3. Cache table lookups: Especially in loops
  4. Choose right data structures: Arrays vs hash tables
  5. Avoid unnecessary allocations: Reuse tables
  6. Use appropriate algorithms: O(n) vs O(nยฒ)
  7. Lazy evaluation: Compute only when needed
  8. Batch operations: Reduce overhead

Before and After Optimization

Before: Slow Code

function processData(data)
    local result = ""
    for i = 1, #data do
        result = result .. tostring(data[i])
        if math.sqrt(data[i]) > 10 then
            result = result .. " (large)"
        end
    end
    return result
end

After: Optimized Code

function processData(data)
    local parts = {}
    local sqrt = math.sqrt
    local tostring = tostring
    local n = #data
    
    for i = 1, n do
        local value = data[i]
        parts[#parts + 1] = tostring(value)
        if sqrt(value) > 10 then
            parts[#parts + 1] = " (large)"
        end
    end
    
    return table.concat(parts)
end

Improvements:

  • Use table.concat() instead of string concatenation
  • Cache math.sqrt and tostring in locals
  • Cache #data outside loop
  • Use direct array assignment

Practice Exercise

Optimize this code:

Output
Click Run to execute your code

Summary

In this lesson, you learned:

  • Profiling code with timing and profiler functions
  • Optimization techniques (local variables, table operations)
  • Memory management and garbage collection
  • Algorithm optimization (memoization, early exit)
  • Common performance pitfalls to avoid
  • Performance best practices
  • Before/after optimization examples

๐ŸŽ‰ Congratulations!

You've completed the Lua tutorial!

You've learned everything from basic syntax to advanced topics like coroutines, OOP, modules, error handling, and performance optimization. You now have the skills to build robust, efficient Lua applications.

What's next?

  • Build real projects to practice your skills
  • Explore Lua frameworks (Lร–VE for games, OpenResty for web)
  • Contribute to open-source Lua projects
  • Dive deeper into LuaJIT for maximum performance
  • Learn C API for extending Lua

Keep coding and happy Lua programming! ๐Ÿš€