完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
某 996 客户端(cocos2d-x Lua 绑定)在线挂机两小时后,系统内存从 180M 涨到 320M,掉帧明显。Lua 侧占用用 collectgarbage("count") 测量,单位 KB;原生侧内存用 Xcode Instruments 的 Allocations 与 Android Profiler 对照。实测数据:Lua 侧 62M、原生纹理 180M,两条曲线独立增长,先查 Lua。
缓存表无上限。 界面模块给每个道具对象挂缓存:cache[item] = result,道具对象销毁后条目仍在。改成弱值表后,条目随对象回收自动清除:
-- 修复前:强引用,只进不出
local cache = {}
-- 修复后:value 弱引用
local cache = setmetatable({}, { __mode = "v" })
闭包抓住大对象。 GUI:addOnClickEvent(btn, function() use(panel) end) 的回调捕获整个面板 table,按钮注册进全局事件后面板永不释放。用 debug 库确认:
for i = 1, debug.getupvalue(fn, 0) and 0 or math.huge do end
local name, val = debug.getupvalue(fn, 1) -- 枚举捕获变量
循环内临时对象。 OnLoop 触发器每帧 string.format 与新建 table,GC 频率随之上升。用内存曲线对照:每次 GC 后回落的基线持续抬高,就是这类代码的指纹。
按收益排序执行三项修改:道具缓存改弱表(-31M);六个界面回调改为持有控件 id 而非面板 table,销毁时反注册(-18M);战斗循环内拼串改 table.concat、临时表提到循环外复用(-11M,GC 次数同步下降 40%)。两小时挂机实测:Lua 侧 62M 降到 8M,总内存 200M 降到 50M 左右,帧率波动从 ±12fps 收窄到 ±3fps。
测量工具链:Lua 侧 collectgarbage("count") 打点脚本、Instruments/Android Profiler 看原生侧、luac -l 确认热点函数的字节码指令数。排查口诀:先分 Lua 与原生两条曲线,再分缓存、闭包、临时对象三个来源,每改一处看一次曲线。这一套流程在同一项目的另外三个面板复用时,单面板排查时间从半天缩短到一小时。
printusetime(play, on, off)
打印脚本总耗时(微秒),把 table 密集操作前后的耗时打出来,定位真正的热点
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| on | integer | 1=开始技术2=结束计时并打印耗时 |
| off | integer | 2=结束计时 |
printusetime(actor,1)
release_print(msg)
打印消息到控制台,临时输出表大小、元素个数一类的观测量
| 参数 | 类型 | 说明 |
|---|---|---|
| msg | any | 打印内容 |
tbl2jsonex(str)
表格转换成字符串,整表转 json 输出,看哪些 key 常驻没被回收
| 参数 | 类型 | 说明 |
|---|---|---|
| str | table | 需要转表的table |
json2tbl(str, reslut)
字符串转换成表格,需要还原快照时反向解析
| 参数 | 类型 | 说明 |
|---|---|---|
| str | string | 需要转表的json |
logact(play, logAct, loginfo, nParam1, nParam2, nParam3, nParam4, nParam5)
自定义日志,长期驻留的内存水位采样走日志通道,别用 print
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| logAct | integer | 日志ID大于等于10000以上 |
| loginfo | string | 日志内容 |
| nParam1 | integer | 整数型(可空)最大支持21亿 |
| nParam2 | integer | 整数型(可空)最大支持21亿 |
| nParam3 | integer | 整数型(可空)最大支持21亿 |