Lua 教程 - 第十二章:最佳实践与优化
第十二章:最佳实践与优化
写出“能运行”的代码只是第一步,写出“高效、易读、易维护”的代码才是我们的目标。在本章,我们将总结 Lua 编程中的一些最佳实践和性能优化技巧。
12.1 代码风格与规范
良好的代码风格有助于团队协作和后期维护。
1. 局部变量优先
始终使用 local 声明变量,除非你确实需要全局变量。
-
性能:局部变量的访问速度比全局变量快得多。
-
安全:避免污染全局命名空间,减少变量冲突。
-
GC:局部变量的作用域结束时,引用解除,有利于垃圾回收。
-- Bad
count = 0
function inc() count = count + 1 end
-- Good
local count = 0
local function inc() count = count + 1 end
2. 命名约定
-
变量/函数:小写字母开头,驼峰命名 (e.g.,
userName,calculateTotal) 或下划线分隔 (e.g.,user_name,calculate_total)。保持一致即可。 -
常量:全大写,下划线分隔 (e.g.,
MAX_RETRY_COUNT)。 -
类/模块:大写字母开头 (e.g.,
HttpServer,StringUtil)。 -
私有成员:下划线开头 (e.g.,
_internalValue),这只是一种约定。
3. 注释
-
函数上方应说明功能、参数和返回值。
-
复杂逻辑代码块需要注释解释“为什么这么做”。
12.2 性能优化技巧
Lua 本身已经很快,但糟糕的代码会拖累性能。
1. 缓存全局变量
如果在循环或高频调用的函数中访问全局变量(包括标准库函数),应将其缓存为局部变量。
-- Bad
for i = 1, 1000000 do
x = math.sin(i)
end
-- Good
local sin = math.sin
for i = 1, 1000000 do
x = sin(i)
end
原因:访问局部变量只需读取栈或寄存器,而访问全局变量需要查找全局表(哈希表查找)。
2. 字符串拼接
在循环中进行大量字符串拼接时,使用 table.concat 而不是 ..。
-- Bad (产生大量中间字符串对象,增加 GC 压力)
local s = ""
for i = 1, 1000 do
s = s .. "a"
end
-- Good
local t = {}
for i = 1, 1000 do
t[#t + 1] = "a"
end
local s = table.concat(t)
3. 表的预分配
如果你知道表的大致大小,预先填充 nil 或者使用 C API 创建预分配大小的表(在纯 Lua 中较难做到,但可以通过构造器暗示)。
更重要的是,避免在循环中创建表。
-- Bad
for i = 1, 10000 do
local point = {x = i, y = i} -- 每次循环都创建新表
process(point)
end
-- Good (如果 process 不保存 point 的引用)
local point = {}
for i = 1, 10000 do
point.x = i
point.y = i
process(point)
end
4. 减少闭包创建
闭包(Closure)也是对象。在循环中创建函数会产生大量闭包实例。
-- Bad
for i = 1, 100 do
doSomething(function() return i end) -- 每次创建新闭包
end
-- Good (将函数移出循环,如果逻辑允许)
local function callback() ... end
for i = 1, 100 do
doSomething(callback)
end
mindmap
root((Lua 性能优化))
缓存全局变量
局部变量更快
避免哈希表查找
缓存标准库函数
字符串拼接
避免循环中使用..
使用 table.concat
减少 GC 压力
表的预分配
避免循环创建表
复用表对象
预知大小时预分配
减少闭包创建
循环外定义函数
避免频繁创建闭包
减少 GC 负担
12.3 安全编码
1. 防止注入
在拼接 SQL 语句或系统命令(os.execute)时,务必对用户输入进行过滤或转义,防止注入攻击。
2. 保护全局环境
可以使用 setmetatable(_G, {__newindex = ...}) 来监控或禁止全局变量的创建,防止意外拼写错误导致创建了全局变量。
local mt = {
__newindex = function(_, n)
error("attempt to write to undeclared variable " .. n, 2)
end,
__index = function(_, n)
error("attempt to read undeclared variable " .. n, 2)
end
}
setmetatable(_G, mt)
flowchart TD
A[Lua 安全编码] --> B[防止注入]
A --> C[保护全局环境]
B --> B1[SQL 注入防护]
B --> B2[命令注入防护]
B --> B3[输入过滤转义]
C --> C1[监控全局变量]
C --> C2[禁止未声明变量]
C --> C3[防止拼写错误]
style A fill:#e3f2fd
style B fill:#a5d6a7
style C fill:#ffcc80
12.4 结语
Lua 是一门“小而美”的语言。它的简洁性赋予了开发者极大的自由,但自由也意味着责任。
通过本系列教程的学习,你已经掌握了 Lua 的核心语法、常用库、与 C 的交互以及实战技巧。但这只是开始,Lua 在游戏开发(OpenResty, Love2D)、嵌入式(NodeMCU)、工具脚本等领域还有广阔的天地等待你去探索。
Keep Coding, Keep Learning!
教程全系列完结。
