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 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 战士的烈火剑法一击熄火后要等八秒冷却,连招断档感强烈。疾风烈火斩在烈火剑法命中后给一次连击窗口:三秒内再…
实战应用:用在哪里 阵营对抗打到中期容易疲软:打赢没有额外好处,输掉也没有代价。阵营声望体系给阵营战装上荣誉刻度:个人与阵营…
实战应用:用在哪里 拿下沙巴克之后呢?城主除了名字挂在城墙上,对城市毫无影响。城主施政玩法让占领变成治理:城主每周获得市政令…
实战应用:用在哪里 主线路光缆被挖断的深夜,全服掉线 20 分钟,玩家以为游戏倒闭。备用线路方案给接入层配第二条出口:主线路…
实战应用:用在哪里 行会官员由帮主任命的老模式弊端明显:任人唯亲、能上不能下。行会选举接口把官员换届做成投票制:每两个月一次…
实战应用:用在哪里 烈火剑法的伤害加成、冷却时间、下一级提升,全靠玩家记忆或去官网查。技能说明悬浮卡片在鼠标悬停技能图标零点…