战斗结算每秒创建上千个伤害记录表,GC 压力集中在团战时刻,服务端偶发 40 毫秒的停顿。内存池方案预分配 512 个战斗记录对象,用完归还复用,对象总量恒定,GC 停顿从 40 毫秒降到 5 毫秒以内。对象池的适用前提是对象结构固定且生命周期短暂,伤害记录、飘字缓冲、坐标快照都属于这一类。池的规模按战斗峰值人数估算后再留两成余量,宁可池大一点也不要让战斗路径走到降级分支。
池内对象带 used 标记防止重复发放,acquire 从空闲列表取、release 清字段后归还,两个操作都是常数开销。
local freeList, active = {}, {}
for i = 1, 512 do
freeList[i] = { dmg = 0, ts = 0, name = "" }
end
local function acquire()
local n = #freeList
if n == 0 then return nil end
local obj = freeList[n]
freeList[n] = nil
return obj
end
local function release(obj)
obj.dmg = 0
obj.ts = 0
obj.name = ""
freeList[#freeList + 1] = obj
end
烈火剑法命中时从池里取记录对象填入伤害与时间,超时对象由整理定时器统一归还,活跃表与池表此消彼长总量不变。
local function onHit(actor, dmg)
local rec = acquire()
if rec == nil then return end
rec.dmg = dmg
rec.ts = os.time()
rec.name = class(actor):GetName()
table.insert(active, rec)
end
setontimerex(197, 30)
function ontimer197()
for i = #active, 1, -1 do
if os.time() - active[i].ts > 60 then
release(active[i])
table.remove(active, i)
end
end
end
压测十万个伤害记录的取用归还,池内对象总数恒为 512 不增不减;归还后字段必须归零,抽查脏数据为零;GC 停顿峰值从 40 毫秒降到 5 毫秒。池耗尽的降级策略单独验证:获取失败时结算退回普通路径,业务不中断只是少了统计明细。监控活跃表长度与池剩余量,剩余量长期低于 32 就要扩池。池的运行指标并入每季度的容量回顾,与玩法规模同步调整。
release 忘记清空 name 字段,下一个使用者读到了上一个玩家的名字,跨人数据串显,归还时逐字段清零写进 release 本体。池耗尽时直接返回 nil 让调用方兼容,最早的版本返回了新表,新表脱离池管理又回到 GC 压力老路。active 表的遍历删除用倒序循环,正序删除会跳元素,漏归一的对象永远占着池位。池大小 512 按百人团战峰值估算,跨服攻城扩到 2000,池参数跟着玩法规模走而不是拍一个固定数。used 标记在归还时忘了复位,池里全是标记已用的对象,acquire 永远取不到,调试半天才发现漏了置位。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 装备回收商 NPC 每天按评分收购玩家的装备,裁决之杖、骨玉权杖按攻击、持久加权打分,榜上只要前五名。数…
实战应用:用在哪里 沙巴克攻城是全服行会战的最高舞台:攻方砸开城门、抢占皇宫,守方依托城墙与复活点反打。规则设计的关键是节奏…
实战应用:用在哪里 影分身玩法给法师职业补上生存短板:危急时刻召唤四个影分身分担仇恨,敌人需要在六格范围内分辨真身,猜错就浪…
实战应用:用在哪里 组队邀请、行会宣战这类关键信令丢了没有兜底,玩家只能反复手点,高峰期体验很差。重试机制给信令加确认回执:…
实战应用:用在哪里 新资料片上线首日预约量常是日常十倍,一口气全开会让数据库与登录网关同时过载。滚动开区把开区动作排成队列:…
实战应用:用在哪里 百人同屏的沙巴克外围战场,每个玩家每秒跑动会触发数次位置更新,全部即时下发时广播量随人数平方增长,网关先…