Lua 从入门到精通 / 16 - 性能优化 / Performance
性能优化 / Performance
Lua 本身已经很快,但不当的使用方式会导致性能问题。掌握这些技巧,让你的 Lua 代码飞起来。
Lua is fast by nature, but misuse causes slowdowns. Master these techniques to make your Lua code fly.
🟢 基础 / Basics — 常见优化
1. 局部变量 vs 全局变量
-- 全局变量访问很慢(哈希表查找)
-- 局部变量访问很快(寄存器/栈操作)
-- 慢
for i = 1, 1000000 do
math.floor(i / 3) -- 每次都要查找 _G["math"] -> math["floor"]
end
-- 快
local floor = math.floor
for i = 1, 1000000 do
floor(i / 3) -- 直接访问局部变量
end
-- 经验法则:在循环内频繁使用的全局函数,缓存为局部变量
local insert = table.insert
local concat = table.concat
local format = string.format
2. 避免不必要的表创建
-- 慢:每次循环都创建新表
local function slow()
local results = {}
for i = 1, 100000 do
results[#results + 1] = {x = i, y = i * 2}
end
return results
end
-- 快:重用表
local function fast()
local results = {}
local tmp = {}
for i = 1, 100000 do
tmp.x = i
tmp.y = i * 2
results[#results + 1] = tmp
tmp = {} -- 只在需要时创建
end
return results
end
-- 更快:预分配数组大小
local function fastest(n)
local results = {}
for i = 1, n do
results[i] = i * 2
end
return results
end
3. 字符串连接优化
-- 极慢:O(n^2)
local s = ""
for i = 1, 10000 do
s = s .. "x"
end
-- 快:O(n)
local parts = {}
for i = 1, 10000 do
parts[i] = "x"
end
local s = table.concat(parts)
-- 也可以指定分隔符
local s = table.concat(parts, ",")
4. ipairs vs pairs vs for i=1,#t
local t = {10, 20, 30, 40, 50}
-- ipairs:使用迭代器,稍慢
for i, v in ipairs(t) do end
-- 数字 for:最快(直接索引)
for i = 1, #t do
local v = t[i]
end
-- pairs:哈希遍历,无序,最慢(对于数组部分)
for k, v in pairs(t) do end
🟡 进阶 / Intermediate
1. 表的高效使用 / Efficient Table Usage
-- table.insert vs 直接赋值
-- table.insert 有额外的函数调用开销
local t = {}
for i = 1, 100000 do
t[#t + 1] = i -- 比 table.insert(t, i) 快
-- 或 t[i] = i -- 如果 i 是连续的
end
-- table.remove 的开销(需要移动元素)
-- 删除数组末尾元素:O(1)
-- 删除数组中间元素:O(n)
table.remove(t) -- 删除最后一个,O(1)
table.remove(t, 5) -- 删除第5个,O(n)
-- 如果需要频繁删除中间元素,考虑用链表或标记删除
-- table.sort 比手写排序快(C 实现)
table.sort(t, function(a, b) return a > b end)
2. 避免过度使用闭包
-- 每次调用都创建新闭包(有开销)
local function makeFilter(threshold)
return function(x) return x > threshold end
end
-- 如果在同一循环中频繁创建,考虑缓存
local filters = {}
local function getFilter(threshold)
if not filters[threshold] then
filters[threshold] = function(x) return x > threshold end
end
return filters[threshold]
end
3. 使用位运算代替取模 / Bitwise vs Modulo
-- Lua 5.3+ 位运算
-- x % 2 == 0 可以用 x & 1 == 0 替代(对 2 的幂)
-- x % 4 == 0 可以用 x & 3 == 0 替代
local function isEven(n)
return n & 1 == 0 -- 比 n % 2 == 0 快
end
-- 位移代替乘除
local x = 1 << 4 -- x = 16 (比 1 * 16 快)
local y = 100 >> 2 -- y = 25 (比 100 / 4 快)
🔴 高级 / Advanced — LuaJIT
1. LuaJIT 简介 / Introduction to LuaJIT
LuaJIT 是 Mike Pall 开发的 Lua 5.1 兼容的 JIT 编译器
LuaJIT is a JIT compiler compatible with Lua 5.1
┌───────────────────────────────────────────┐
│ LuaJIT 架构 │
├───────────────────────────────────────────┤
│ Lua 源码 │
│ │ │
│ ▼ │
│ Bytecode Compiler(字节码编译器) │
│ │ │
│ ▼ │
│ Interpreter(解释器) │
│ │ │
│ ├── Hot loop detected? │
│ │ │ │
│ │ ▼ │
│ │ Trace Compiler(追踪编译器) │
│ │ │ │
│ │ ▼ │
│ │ Machine Code(机器码) │
│ │ │
│ ▼ │
│ Native Performance(原生性能) │
└───────────────────────────────────────────┘
性能对比 / Performance comparison:
- 纯 Lua 解释器: 1x
- LuaJIT 解释器: ~3-5x
- LuaJIT JIT 编译: ~30-100x(接近 C 性能)
2. FFI 库 — 直接调用 C / FFI Library
-- LuaJIT 的 FFI 允许直接调用 C 函数和操作 C 数据结构
-- 比 Lua C API 快得多(零开销调用)
local ffi = require("ffi")
-- 声明 C 函数
ffi.cdef[[
int printf(const char *fmt, ...);
double sqrt(double x);
void *malloc(size_t size);
void free(void *ptr);
]]
-- 调用 C 函数
ffi.C.printf("Hello from C! %d\n", 42)
local result = ffi.C.sqrt(16.0)
print(result) -- 4.0
-- 创建 C 类型
local arr = ffi.new("double[10]")
for i = 0, 9 do
arr[i] = i * 1.5
end
print(arr[5]) -- 7.5
-- FFI vs Lua C API 性能:
-- FFI 调用 C 函数: ~纳秒级
-- Lua C API 调用: ~微秒级(栈操作开销)
3. Trace 编译原理 / Trace Compilation
-- LuaJIT 通过"追踪"执行路径来编译热代码
-- 1. 解释器正常执行字节码
-- 2. 检测到"热循环"(执行次数超过阈值)
-- 3. 开始录制 trace(记录执行路径)
-- 4. 如果 trace 是线性的(没有分叉),直接编译为机器码
-- 5. 如果 trace 分叉,可能需要"guard"(检查点)
-- 有利于 trace 编译的代码:
local sum = 0
for i = 1, 1000000 do
sum = sum + i -- 简单循环,容易被 JIT
end
-- 不利于 trace 编译的代码:
for i = 1, 1000000 do
if type(x) == "number" then -- 类型不稳定会打断 trace
-- ...
end
end
-- 类型稳定性非常重要!
-- LuaJIT 为每种类型生成专门的机器码
-- 类型变化会导致 trace 失效,回退到解释器
4. LuaJIT vs Lua 5.4 兼容性
-- LuaJIT 基于 Lua 5.1,缺少 5.2-5.4 的特性
-- 需要注意的差异:
-- Lua 5.2+:
-- - goto 语句(LuaJIT 支持)
-- - 无 __len 对 table(LuaJIT 支持)
-- - bit32 库(LuaJIT 有 bit 库)
-- Lua 5.3+:
-- - 整数/浮点数分离(LuaJIT 无,全用 double)
-- - // 整除(LuaJIT 无)
-- - 位运算(LuaJIT 用 bit 库)
-- - utf8 库(LuaJIT 无)
-- Lua 5.4:
-- - 分代 GC(LuaJIT 无)
-- - to-be-closed 变量(LuaJIT 无)
-- - 常量变量(LuaJIT 无)
-- 选择建议:
-- - 需要极致性能 → LuaJIT
-- - 需要最新语言特性 → Lua 5.4
-- - 嵌入式环境 → Lua 5.4(更小)
-- - OpenResty / Kong → LuaJIT(强制)
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | 局部变量缓存全局函数、table.concat 替代 ..、避免不必要创建 |
| 🟡 进阶 | 数组直接索引 vs ipairs、table.insert vs 直接赋值、位运算 |
| 🔴 高级 | LuaJIT 架构、FFI 零开销调用 C、trace 编译、类型稳定性 |