
Luau Performance
- 63 installs
- 10 repo stars
- Updated May 27, 2026
- stackfox-labs/luau-skills
Helps with ai & agent building tasks.
About
luau-performance is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- luau-performance
- AI & Agent Building
- AI-coding skill
Luau Performance by the numbers
- 63 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #6,190 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/stackfox-labs/luau-skills --skill luau-performanceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 10 |
| Last updated | May 27, 2026 |
| Repository | stackfox-labs/luau-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
luau-performance
When to Use
Use this skill when the task is primarily about Luau runtime cost:
- Profiling code to find real hotspots before changing implementation details.
- Reducing allocation churn, GC pressure, or closure creation in repeated paths.
- Choosing faster table construction, lookup, append, and iteration patterns.
- Making code cooperate with Luau fast paths for builtin calls, method calls, and imports.
- Explaining how compiler and runtime optimizations affect hot functions.
- Tuning a confirmed hot path while balancing readability and maintainability.
- Accounting for sandbox or environment behavior only when it changes Luau execution or deoptimizes code.
Do not use this skill when the task is mainly about:
- Teaching general Luau syntax, control flow, tables, or metatables from first principles.
- Designing deep type-system abstractions, analyzer behavior, or advanced type utilities.
- Roblox-specific replication, streaming, rendering, networking, physics, or other engine performance patterns.
Decision Rules
- Start with measurement. If the code is non-trivial, profile or benchmark before making optimization claims.
- Fix algorithmic cost before micro-optimizing bytecode-level details.
- Prioritize changes that remove repeated work, repeated allocation, or repeated dynamic dispatch in hot paths.
- Keep performance-sensitive modules in a pure environment. Avoid
getfenv,setfenv, andloadstringwhere speed matters. - Prefer stable table shapes, direct field access, direct builtin calls, and simple call graphs in hot code.
- Use environment-specific compilation features only after measurement shows a likely win and the runtime actually supports them.
- If the task shifts into core language teaching, use
luau-core. - If the task shifts into types, inference, or annotations as the main subject, use
luau-types. - If the task depends on Roblox engine architecture instead of Luau execution behavior, hand off to the appropriate
roblox/*skill. - If a request mixes performance work with out-of-scope topics, answer only the Luau runtime portion and exclude the rest.
Instructions
1. Define the workload first:
- what runs often,
- what allocates often,
- what is on the critical path,
- what metric matters most: wall time, frame budget, or memory churn.
2. Profile before rewriting. Use sampling or environment tooling to identify functions that actually dominate runtime. 3. Optimize the largest validated bottleneck first. Do not spread micro-optimizations across cold code. 4. Reduce allocation pressure in repeated paths:
- avoid rebuilding tables every iteration,
- avoid creating fresh closures in loops unless necessary,
- avoid retaining tables or connections longer than needed.
5. Shape tables for the runtime:
- use literals to create object-like tables with all known fields up front,
- keep object layouts uniform across calls,
- use
table.createonly for array-like tables with known capacity.
6. Choose iteration deliberately:
- use generalized iteration
for k, v in t dofor normal table traversal, - use
ipairsonly when stop-at-first-nilbehavior is required, - use numeric loops when the index itself is needed or sequential writes are part of the algorithm.
7. Keep builtin fast paths obvious:
- call builtins directly, such as
math.max(x, y)orstring.byte(s, 1), - do not hide hot builtin calls behind unnecessary indirection,
- prefer builtin function form over method form when fastcall behavior depends on it.
8. Keep call sites compiler-friendly:
- prefer
local functionfor hot helpers, - avoid unnecessary mutation of captured values,
- keep small helpers local to the module when that improves inlining opportunities.
9. Keep metatable usage cheap in hot code:
- store data on the object itself,
- point
__indexdirectly at a table, - avoid
__indexfunctions and deep lookup chains on critical paths.
10. Treat environment features as performance constraints:
getfenv,setfenv, andloadstringcan deoptimize imports and fast builtin handling,- debugging and breakpoints can alter observed runtime behavior in some environments,
- native compilation, where supported, should be applied selectively and measured.
11. Preserve practicality. Prefer the simplest change that removes measurable cost, even if a more aggressive rewrite is theoretically faster.
Using References
- Open
references/luau-performance-guide.mdfor the main Luau fast-path model: table access, imports, method calls, iteration, and allocation-aware table construction. - Open
references/profiling-guide.mdfor profiler workflow, interpreting flame graphs, naming functions for attribution, and environment-specific profiling notes. - Open
references/runtime-and-compiler-optimization-notes.mdfor compiler limits, inlining, constant folding, upvalues, closure caching, and selective native compilation guidance. - Open
references/library-performance-sensitive-patterns.mdfor practical choices aroundmath,string,table, iteration helpers, and array-oriented APIs. - Open
references/sandbox-constraints-relevant-to-runtime-behavior.mdfor the environment rules that disable or weaken runtime optimizations. - Do not open other skill references unless the task clearly crosses into another skill's scope.
Checklist
- A measurement plan or profiler result exists for the claimed hotspot.
- The proposed change targets a path that runs often enough to matter.
- Algorithmic cost has been considered before micro-tuning syntax.
- Allocation churn is reduced where the code repeats.
- Table shape and iteration strategy match the data pattern.
- Builtin calls stay direct enough to preserve fast paths where possible.
- Environment deoptimizers such as
getfenv,setfenv, orloadstringare avoided in hot modules. - Any environment-specific compilation feature is justified by measurement, not guesswork.
- The guidance stays within Luau execution behavior and avoids Roblox engine performance topics.
Common Mistakes
- Optimizing unmeasured code because it "looks hot."
- Using
table.createfor dictionaries instead of arrays. - Assuming
pairsoripairsare automatically faster than generalized iteration. - Rewriting
obj:Method()into cached method locals even when Luau already optimizes method calls well. - Hiding builtin calls behind wrappers or indirect dispatch on a hot path.
- Varying object table keys heavily and then expecting field lookup caching to stay effective.
- Using
getfenvonly for reads and assuming it has no optimization cost. - Sprinkling native compilation directives everywhere without measuring memory, startup, or actual runtime wins.
Examples
Preallocate and fill arrays sequentially
local function buildSquares(count)
local result = table.create(count)
for i = 1, count do
result[i] = i * i
end
return result
endKeep builtins direct in hot code
local function clamp01(x)
return math.min(math.max(x, 0), 1)
endUse stable object layouts and direct __index
local Counter = {}
Counter.__index = Counter
function Counter.new(step)
return setmetatable({
value = 0,
step = step,
}, Counter)
end
function Counter:advance()
self.value += self.step
return self.value
endAvoid deoptimizing the environment in performance-sensitive modules
local function magnitude2(x, y)
return math.sqrt(x * x + y * y)
end
return magnitude2Library Performance-Sensitive Patterns
Key Concepts
- Many builtin functions have specialized fast paths when called directly in pure environments.
- Builtin specialization is strongest when argument types match common cases, especially numeric
mathcalls. string.bytecalled as a builtin function is cheaper than method-styles:byte(...)when fastcall applies.tablehelpers are tuned for array-like data, but the right helper depends on whether size is known.#tis heavily optimized for array-like tables and usually inexpensive in practice.
Rules
- Keep hot builtin calls direct and obvious.
- Pass the expected argument types to fast-path builtins.
- Use
table.createonly for arrays, not dictionaries. - Use
table.insertfor append when capacity is unknown. - Use indexed assignment when preallocated sequential writes are available.
- Use
pairsoripairsonly when their specific semantics are needed; otherwise generalized iteration is fine.
Patterns
math library
- Direct numeric calls such as
math.abs,math.max,math.floor, andmath.sqrtare good fast-path candidates. - Indirect wrappers can hide optimization opportunities.
- Non-numeric coercion cases are slower than already-correct numeric inputs.
string library
- Prefer
string.byte(s, i)overs:byte(i)in hot code when the builtin form is clear. - Avoid repeated string-processing work when a parsed or cached representation can be reused.
table library
table.insert(t, value)is the best default append when array size is not known.table.create(n)preallocates array storage and pairs well witht[i] = value.table.sort,table.move, and related helpers are already tuned; use them instead of reimplementing them without evidence.
Examples
Direct numeric builtin calls
local function hypot2(x, y)
return math.sqrt(x * x + y * y)
endArray preallocation plus indexed writes
local function mapDouble(values)
local out = table.create(#values)
for i, value in values do
out[i] = value * 2
end
return out
endUnknown-size append path
local function collectPositives(values)
local out = {}
for _, value in values do
if value > 0 then
table.insert(out, value)
end
end
return out
endLuau Performance Guide
Key Concepts
- Luau is tuned for stable high performance in interpreted execution, not just for JIT-only workloads.
- Many optimizations reward idiomatic code instead of requiring unusual coding style.
- Runtime speed depends heavily on table access shape, builtin call shape, and avoiding unnecessary allocations.
- Fast paths are strongest when the environment remains pure and table structures stay predictable.
Rules
- Measure first, then optimize the largest real bottleneck.
- Prefer locals, direct field access, and direct builtin calls in hot code.
- Keep object-like tables uniform in shape across calls.
- Use table literals for object construction and
table.createonly for array preallocation. - Avoid environment features that invalidate imports or builtin fast paths.
Patterns
Favor predictable field access
- Use
obj.fieldwhen the field name is known at compile time. - Keep instance data on the table itself.
- Put methods on a metatable table referenced by
__index. - Avoid
__indexfunctions and deep metatable chains on hot paths.
Keep global and builtin access cheap
- Luau can import chains like
math.maxwhen the environment is pure. - Localizing a builtin can still be fine, but direct obvious calls are easy for the compiler to optimize.
getfenv,setfenv, andloadstringcan invalidate these optimizations.
Build and grow tables intentionally
- Create object-like tables with all known fields in the literal when possible.
- Preallocate arrays with
table.create(n)when capacity is known. - If final size is unknown, append with
table.insert. - If final size is known, sequential indexed writes are often better.
Choose iteration form for semantics, not folklore
- Generalized iteration
for k, v in t dois a first-class optimized path. pairsandipairsare also specialized, but not automatically better.- Numeric loops over
1..#tcan be slightly slower for traversal because each element is read manually.
Examples
Object construction with stable shape
local function makePoint(x, y)
return {
x = x,
y = y,
}
endSequential array fill
local function copyDoubles(values)
local out = table.create(#values)
for i, value in values do
out[i] = value * 2
end
return out
endDirect builtin call
local function largest(a, b, c)
return math.max(a, b, c)
endProfiling Guide
Key Concepts
- Luau provides a sampling profiler that records execution stacks and can be visualized as a flame graph.
- A flame graph shows where total runtime accumulates; wider frames matter more than deeper-looking frames.
- Sampling profilers are statistical: they are reliable for repeated cost, not single short events.
- Poor naming of local anonymous functions makes attribution harder during investigation.
Rules
- Profile optimized builds when possible; debug-style overhead can distort results.
- Reproduce the real workload before drawing conclusions.
- Investigate the widest stacks first, not the most visually complex code.
- Re-run the profiler after each meaningful optimization pass.
- Name hot local functions with
local function name()when you want clearer profiler output.
Patterns
Use a profiler workflow
1. Capture a baseline profile for the real scenario. 2. Find the few functions dominating runtime. 3. Inspect whether the cost is algorithmic, allocation-driven, or call-dispatch-driven. 4. Change one hotspot at a time. 5. Re-profile and compare.
Read flame graphs correctly
- Width represents cumulative sampled time.
- Nesting represents call stacks.
- Anonymous frames can still be traced by source location.
- Time spent in leaf C or builtin work may be attributed to the calling Luau function.
Environment-specific profiling notes
- In Roblox tooling, use Script Profiler or related Luau profiling tools to validate whether a function is actually hot.
- Use
debug.profilebeginanddebug.profileendonly to improve attribution when environment tooling supports them. - Breakpoints and debugger attachment can change observed execution behavior in some environments, especially for native execution.
Examples
Name a hot helper for clearer attribution
local function accumulate(values)
local total = 0
for _, value in values do
total += value
end
return total
endCompare before and after a focused rewrite
local function sumSquares(values)
local total = 0
for _, value in values do
total += value * value
end
return total
endRuntime and Compiler Optimization Notes
Key Concepts
- Luau uses a multi-pass compiler and a highly tuned bytecode interpreter.
- The compiler performs constant folding, peephole improvements, upvalue optimization, and limited interprocedural optimization within a module.
- Immutable upvalues are cheaper than mutable captured state.
- Closure creation can be optimized, but repeated closure allocation can still create pressure in hot paths.
- Small local functions may be inlined when profitable.
- In supported environments, selective native compilation can improve numeric or compute-heavy functions, but it has memory and tooling tradeoffs.
Rules
- Prefer
local functionfor small hot helpers that stay within one module. - Avoid unnecessary mutation of captured values when closures run often.
- Keep hot functions simple enough that compiler optimizations remain available.
- Treat native compilation as an opt-in measurement exercise, not a default.
- Annotate important value types where an environment-specific compiler uses that information for specialization.
Patterns
Work with compiler assumptions
- Local function calls are easier to optimize than indirect function values.
- Module-local reasoning is stronger than cross-module reasoning.
- Pure environments preserve import-based builtin optimization and inlining opportunities.
Reduce closure and upvalue cost
- Hoist reusable helper functions to module scope when they do not need per-iteration captures.
- Avoid recreating comparator or callback functions inside loops.
- Prefer immutable captures when practical so upvalue storage stays cheap.
Use native compilation selectively when supported
- Good candidates are functions called many times that do substantial arithmetic or buffer-heavy work in Luau itself.
- Poor candidates are scripts dominated by Roblox API calls, cold setup code, or large functions with heavy complexity.
- Measure both runtime and memory impact.
- Remember that breakpoints can disable native execution for affected functions.
Examples
Hoist a reusable comparator
local function ascending(a, b)
return a < b
end
local function sortValues(values)
table.sort(values, ascending)
endKeep captures simple
local scale = 2
local function scaleValue(x)
return x * scale
endEnvironment-specific native candidate
local function dot(ax, ay, az, bx, by, bz)
return ax * bx + ay * by + az * bz
endSandbox Constraints Relevant to Runtime Behavior
Key Concepts
- Luau sandboxes globals by using per-script environment tables that read from a protected builtin global table.
- This isolation model enables optimizations such as imported global chains and specialized builtin dispatch in pure environments.
- Environment mutation features exist for compatibility, but they weaken optimization assumptions.
- Some environment and tooling features change how code executes even when semantics stay the same.
Rules
- Keep performance-sensitive code in pure environments.
- Avoid
getfenv,setfenv, andloadstringin hot modules. - Do not depend on monkey-patching globals as an optimization technique.
- When measuring environment-specific compilation behavior, profile without active breakpoints.
Patterns
Understand pure versus impure environments
- Pure environments let Luau resolve many global access chains at load time.
- Calling
getfenv, even only to read values, marks the environment impure. setfenvandloadstringalso force deoptimization because they can change what globals mean.
Work with sandboxed globals
- Prefer locals for hot dependencies instead of relying on writable globals.
- Treat globals as stable host-provided services, not as mutable hot-path state.
- Keep module interfaces explicit so performance-sensitive code does not need environment tricks.
Account for tooling effects
- Debugging support is designed to minimize overhead in normal interpreted execution.
- In environments with native compilation, breakpoints can disable native execution for the affected function.
- Measure runtime behavior in conditions that match production as closely as practical.
Examples
Pure environment friendly helper
local function saturate(x)
return math.min(math.max(x, 0), 1)
endAvoid environment mutation in hot code
local sqrt = math.sqrt
local function length2(x, y)
return sqrt(x * x + y * y)
end