Lua性能优化技巧详解
Lua是一门设计精巧的脚本语言,以其轻量级和高性能著称。但要让Lua代码发挥最佳性能,需要掌握一些关键的性能优化技巧。
## 一、局部变量优先使用
### 核心原理
Lua对局部变量的访问速度远快于全局变量。全局变量需要查表操作,而局部变量可以直接通过寄存器或栈快速访问。
### 优化示例
**未优化的代码:**
```bash
function bad_practice()
for i = 1, 1000000 do
math.sin(i)
math.cos(i)
math.sqrt(i)
end
end
```
**优化后的代码:**
```bash
function good_practice()
local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
for i = 1, 1000000 do
sin(i)
cos(i)
sqrt(i)
end
end
```
**性能提升:** 通常可提升30-50%的性能
## 二、预分配表大小
### 核心原理
Lua表是动态数组,当表增长时需要重新分配内存。预分配表大小可以避免频繁的内存重新分配。
### 优化示例
**未优化的代码:**
```bash
function bad_table_creation()
local tbl = {}
for i = 1, 100000 do
tbl[i] = i
end
return tbl
end
```
**优化后的代码:**
```bash
function good_table_creation()
local tbl = {}
-- 预分配表大小
tbl[100000] = true -- 预分配足够空间
for i = 1, 100000 do
tbl[i] = i
end
tbl[100000] = nil -- 清除预分配元素
return tbl
end
```
**更优雅的预分配方法:**
```bash
function smart_table_creation(size)
local tbl = {}
for i = size, 1, -1 do
tbl[i] = i
end
return tbl
end
Lua 的表在内部有两种存储方式:
数组部分:连续整数索引(1, 2, 3...)
哈希部分:其他键
当使用 tbl[i] = i 时,如果 i 是从大到小赋值,Lua 能提前知道需要分配的最大索引,从而一次性分配足够的内存。
```
**并非所有情况都适用**
```bash
-- 从大到小但非连续索引,无效
local tbl = {}
for i = size, 1, -2 do -- 只赋值偶数索引
tbl[i] = i -- 不会预分配连续数组空间
end
---
-- 字符串键,从大到小无效
local tbl = {}
for i = size, 1, -1 do
tbl["key" .. i] = i -- 哈希表,无预分配效果
end
```
**最可靠的预分配方式**
```bash
-- 方式1:设置最大索引
local tbl = {[size] = 0}
-- 方式2:使用 table.create (Lua 5.3+)
local tbl = table.create(size, 0) -- 创建预分配大小的表
-- 方式3:从大到小(兼容性好)
local tbl = {}
for i = size, 1, -1 do
tbl[i] = nil -- 或者赋实际值
end
```
## 三、避免在循环中创建函数
### 核心原理
函数创建是相对耗时的操作,在循环中创建函数会产生大量临时函数对象。
### 优化示例
**未优化的代码:**
```bash
function bad_function_creation()
local result = {}
for i = 1, 100000 do
result[i] = function()
return i * 2
end
end
return result
end
```
**优化后的代码:**
```bash
function good_function_creation()
local result = {}
local function multiply(x)
return x * 2
end
for i = 1, 100000 do
result[i] = multiply
end
return result
end
```
## 四、字符串操作优化
### 核心原理
Lua中的字符串拼接会创建新的字符串对象,频繁的字符串拼接会导致大量的内存分配和垃圾回收。
### 优化示例
**未优化的代码:**
```bash
function bad_string_concat()
local result = ""
for i = 1, 10000 do
result = result .. tostring(i)
end
return result
end
```
**优化后的代码:**
```bash
function good_string_concat()
local parts = {}
for i = 1, 10000 do
parts[i] = tostring(i)
end
return table.concat(parts)
end
```
**带分隔符的高效拼接:**
```bash
function smart_string_concat()
local parts = {}
for i = 1, 10000 do
parts[i] = "data" .. i
end
return table.concat(parts, ", ") -- 添加分隔符
end
```
## 五、使用 ipairs 而不是 pairs
### 核心原理
`ipairs`专门针对连续的整数索引数组进行了优化,而`pairs`需要遍历所有键值对。
### 优化示例
**未优化的代码:**
```bash
function bad_array_traversal(array)
local sum = 0
for k, v in pairs(array) do
if type(k) == "number" then
sum = sum + v
end
end
return sum
end
```
**优化后的代码:**
```bash
function good_array_traversal(array)
local sum = 0
for i, v in ipairs(array) do
sum = sum + v
end
return sum
end
```
## 六、避免不必要的表查找
### 核心原理
减少对表的重复访问,特别是嵌套表的访问。
### 优化示例
**未优化的代码:**
```bash
function bad_table_access(config)
for i = 1, 100000 do
if config.database.host == "localhost" and
config.database.port == 3306 then
-- 处理逻辑
end
end
end
```
**优化后的代码:**
```bash
function good_table_access(config)
local db = config.database
local host = db.host
local port = db.port
for i = 1, 100000 do
if host == "localhost" and port == 3306 then
-- 处理逻辑
end
end
end
```
## 七、使用数字索引而不是字符串索引
### 核心原理
数字索引的查找速度快于字符串索引。
### 优化示例
**未优化的代码:**
```bash
local data = {
name = "Alice",
age = 25,
score = 95
}
for i = 1, 100000 do
local name = data.name
local age = data.age
local score = data.score
end
```
**优化后的代码:**
```bash
local data = {
[1] = "Alice",
[2] = 25,
[3] = 95
}
local name = data[1]
local age = data[2]
local score = data[3]
for i = 1, 100000 do
-- 直接使用局部变量
end
```
## 八、协程优化
### 核心原理
协程的创建和销毁有一定开销,可以复用协程或使用协程池。
### 协程池实现
```bash
local CoroutinePool = {}
CoroutinePool.__index = CoroutinePool
function CoroutinePool.new(size)
-- 返回新建的空表,并且这个表的元表被设置为CoroutinePool --
local self = setmetatable({}, CoroutinePool)
self.pool = {}
self.size = size or 10
for i = 1, self.size do
self.pool[i] = coroutine.create(function()
while true do
local task = coroutine.yield()
if task then
local success, result = pcall(task.func, table.unpack(task.args))
task.callback(success, result)
end
end
end)
coroutine.resume(self.pool[i])
end
return self
end
function CoroutinePool:submit(func, args, callback)
for i = 1, self.size do
if coroutine.status(self.pool[i]) == "suspended" then
coroutine.resume(self.pool[i], {func = func, args = args or {}, callback = callback})
return true
end
end
return false -- 池已满
end
```
## 九、内存管理优化
### 使用弱引用表避免内存泄漏
```bash
-- 弱引用表示例
local function create_cache()
local cache = {}
-- 只对值进行弱引用
setmetatable(cache, {__mode = "v"})
return {
get = function(key)
return cache[key]
end,
set = function(key, value)
cache[key] = value
end,
clear = function()
collectgarbage("collect")
end
}
end
local cache = create_cache()
cache.set("data", {value = "some data"})
-- 手动触发垃圾回收
collectgarbage("collect")
```
## 十、避免全局变量污染
### 核心原理
全局变量不仅影响性能,还可能导致命名冲突。
### 优化示例
```bash
-- 封装到局部变量
local _M = {}
function _M.calculate(x, y)
return x + y
end
return _M
```
## 性能测试框架
```bash
-- 性能测试工具
local function benchmark(name, func, iterations)
local start = os.clock()
for i = 1, iterations do
func()
end
local elapsed = os.clock() - start
print(string.format("%s: %.4f 秒 (%d次迭代)", name, elapsed, iterations))
return elapsed
end
-- 测试用例
local function test_string_concat()
print("\n字符串拼接性能测试:")
benchmark("未优化版本", function()
local result = ""
for i = 1, 10000 do
result = result .. tostring(i)
end
end, 100)
benchmark("优化版本", function()
local parts = {}
for i = 1, 10000 do
parts[i] = tostring(i)
end
return table.concat(parts)
end, 100)
end
-- 运行测试
test_string_concat()
```
## 总结
Lua的性能优化主要遵循以下原则:
1. **优先使用局部变量**
2. **预分配表大小**
3. **避免循环中创建函数**
4. **优化字符串操作**
5. **使用ipairs遍历数组**
6. **减少表查找**
7. **使用数字索引**
8. **复用协程**
9. **合理管理内存**
10. **避免全局变量**
掌握这些技巧,你的Lua代码性能将得到显著提升!