完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
高频创建大 table(几千字段的配置副本、批量战斗日志)时,GC 的回收节奏跟不上分配节奏,内存锯齿与卡顿随之而来。内存池定制把"反复创建销毁大对象"改为"预分配、循环使用",配合 userdata 载体可以让这部分内存对 Lua GC 近乎不可见。适用于:战斗日志缓冲、批量导出工具、跨服快照拼接。
大字符串拼接的场景,预分配一块固定容量的缓冲区(table + 写入游标模拟),用完整体重置:
local Buffer = {}
Buffer.__index = Buffer
function Buffer.new(capacity)
return setmetatable({ buf = {}, n = 0, cap = capacity }, Buffer)
end
function Buffer:append(s)
self.n = self.n + 1
self.buf[self.n] = s
if self.n >= self.cap then self:flush() end -- 达到容量即落盘并重置
end
function Buffer:flush()
local data = table.concat(self.buf)
self.buf = {}
self.n = 0
writeToFile(data) -- 落盘后内存归零
end
配合 collectgarbage("setpause", 400) 提高触发阈值,让缓冲区的整存整取与 GC 周期错开。
LuaJIT 下用 ffi.new("char[?]", size) 分配 C 内存:cdata 不参与常规 table 的 GC 标记遍历,大块分配不会显著拉长 GC 停顿。注意托管 cdata 依赖 finalizer 释放,非托管指针(ffi.cast 获得)必须手动 ffi.C.free,用错就是泄漏或崩溃。手动管理userdata内存的收益与风险并存:1000 个 1KB 结构体的分配耗时可从 3.4ms 降到 0.4ms,但生命周期管理全部落在代码纪律上。
内存池只服务"生命周期确定、体量可预估"的对象,通用对象进池反而制造复用污染。验证三件套:collectgarbage("count") 曲线(池化后应无锯齿)、GC 触发间隔(拉长 3 倍以上为有效)、峰值内存(池上限即峰值上限,可预期)。某日志导出工具池化后,10 万条记录导出的内存峰值从 210M 降到 18M,耗时同步减少 31%——内存池是典型的"先用测量证明值得,再动手实施"的优化。
池容量规划按峰值上浮两成:历史峰值 120M 的导出任务,池容量给到 150M。池内对象加代际标记,跨批次复用时先重置再使用。若任务结构不可预估,改用队列式缓冲(写满即落盘清空),比固定池更弹性。