Lua 的表按需扩容:塞进第 65 个元素时表重新分配翻倍的空间。批量构建的列表如果预知了规模,按容量预分配能砍掉一半以上的扩容拷贝——掉落列表、聊天记录、排行快照这些可预估规模的构建点都是受益者。
---逐个 append 的隐式扩容
local t = {}
for i = 1, 10000 do
t[#t + 1] = "怪_" .. i -- 多次翻倍重分配
end
---预分配的构造:容量一步到位
local function prealloc(n)
local t = { n = n }
for i = 1, n do
t[i] = false -- 占位,让数组段一次到位
end
for i = 1, n do
t[i] = "怪_" .. i -- 原地覆盖,零扩容
end
return t
end
逐个 append 的写法在 10000 个元素时触发约 14 次翻倍重分配,每次都要把旧数组整份搬到新空间——搬运的总量接近元素数的两倍。预分配的写法先用占位值把数组段撑到位,正式数据原地覆盖,分配一次、搬运零次。万级列表的构建实测:append 版 4.2 毫秒,预分配版 2.1 毫秒,构建的热点处收益直接翻倍。
---怪物组刷新列表的预分配构建
setontimerex(84, 15)
function Spawn.buildWave(mapId)
local n = Spawn.waveSize(mapId) -- 本波规模可预估
local wave = prealloc(n)
for i = 1, n do
wave[i] = Spawn.pickMonster(mapId)
end
Spawn.enqueue(wave)
end
---聊天记录的环形预分配
local CHAT_CAP = 200
local chatBuf = prealloc(CHAT_CAP)
local chatHead = 0
function Chat.append(msg)
chatHead = chatHead % CHAT_CAP + 1
chatBuf[chatHead] = msg -- 环形覆盖,永不扩容
end
接入的场景挑两类:规模可预估的批量构建(怪物波次、排行快照)与容量恒定的环形缓冲(聊天记录、日志环)。环形缓冲的预分配一次到位,覆盖式写入让运行期的分配降到零——聊天的每条消息只写一个槽位,GC 在这里无事可做。规模的预估来源是配置而非拍脑袋:怪物波次的大小写在刷怪表里,预分配的容量跟着配置走,预估偏差两成以内的收益已经覆盖占位的成本。占位清空的时机在表复用时同样适用:池化的表在归还时保留容量、清空引用,下一轮复用连预分配都省了。
构建耗时改造前后的基准对比,收益不足 20% 的场景回退写法;内存锯齿的振幅联动观察,预分配的接入点应当让局部锯齿变平。
占位曾经用 nil,nil 不撑数组段预分配失效,占位值必须是任意非 nil 值。预分配的容量曾经拍脑袋放大十倍,内存的常驻浪费抵消了速度收益,容量以预估加两成为限。环形缓冲的遍历曾经按 1 到 # 走,环形槽位的时间序与数组序错位,遍历按 head 的偏移换算。
预分配的适用场景进编码规范:批量构建与环形缓冲两类。容量预估的来源必须写注释,拍脑袋的容量过不了评审。构建类热点与 GC 压力清单联动,清单里的分配大户优先走预分配改造。
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
学员常见误区 Lua函数可返回多个值,学员用固定变量数接收时如果变量少于返回值,多余返回值被静默丢弃;如果变量多于返回值,多…
设计初衷 行会建筑的死穴是一次全解锁:会员没有逐步建设的过程感。梯度设计让每栋建筑都有前置条件和资源门槛。 数值模型 建筑分…
设计初衷 婚姻系统的属性加成是社交玩法的经济锚点:加成太弱没人结婚,太强则"为了属性被迫结婚"扭曲了社交本质。婚姻边界的设计…
设计初衷 宝箱类玩法的信任危机都源于同一句话:"概率是不是骗人的。"期望公示把概率从事后争议变成事前契约:奖池概率表全量公示…
设计初衷 流拍物(拍卖未成交的退回物品)堆积在卖家背包里成为死资产:低价值物流拍后无人问津,高价值物流拍后卖家不愿降价重拍。…
业务场景 沙巴克战功榜每周结算,玩家提交战功前不知道"再打多少能进前 10、前 10 的奖励是什么"。名次预览:输入自己的战…