三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Unity Lua性能分析工具Miku-LuaProfiler实战攻略:6步搞定GC排查与泄漏检测

Unity Lua性能分析工具Miku-LuaProfiler实战攻略:6步搞定GC排查与泄漏检测

Unity Lua性能分析工具Miku-LuaProfiler实战攻略:6步搞定GC排查与泄漏检测

【免费下载链接】Miku-LuaProfiler项目地址: https://gitcode.com/gh_mirrors/mi/Miku-LuaProfiler

如果你正在用Unity + XLua/toLua做游戏开发,一定遇到过这样的困境:游戏卡顿、内存只涨不跌,却说不清是哪段Lua代码在"偷走"性能。Miku-LuaProfiler 就是为解决这个问题而生的轻量级Lua性能分析工具,它能精准定位每个Lua函数的调用耗时、Lua GC与Mono GC分配量,甚至帮你揪出"打开就再也释放不掉"的对象泄漏。本文将以资深开发者的视角,手把手带你完成从安装、跑通、读数据到查泄漏的完整流程,全程可复制、可落地。

1、先认识它:Miku-LuaProfiler 到底能做什么

在动手之前,咱们先把工具的能力边界摸清楚,这样后面每一步你都知道"我在看什么"。

1.1 四大核心能力一览

  • 函数级耗时统计:记录每个Lua函数当前帧耗时、平均耗时与累计耗时,卡顿点一目了然。
  • Lua GC / Mono GC 分配统计:算出每个函数"亲手"产生的GC量(self)和"连带"产生的GC总量(total),内存暴涨的元凶无所遁形。
  • 录制回放:像示波器一样录制一段时间的运行数据,拖动时间轴逐帧回放,定位内存跳变的精确帧。
  • 泄漏检测:通过"打快照→对比快照"(MarkStaticRecord / MarkLuaRecord / DiffRecord)找出被持有却永不释放的对象,以及C#侧泄漏的Lua回调(ref)。

1.2 它支持哪些平台

系统支持情况
Windows(编辑器内本地分析)
Android(真机远程分析)
MAC
iOS支持中

其中"编辑器本地模式"是最常用的调试路径,而真机分析则用于验证打包后的真实表现,两条路咱们下面都会走一遍。

2、第一步:把工具装进你的Unity项目(两种安装方式)

安装方式取决于你的Unity版本,二选一即可。

2.1 方式一:Package Manager 一键添加(推荐 Unity 2019+)

打开 Unity 的Window > Package Manager,点击左上角"+"选择Add package from git URL,填入仓库地址:

https://gitcode.com/gh_mirrors/mi/Miku-LuaProfiler

等待解析完成后,包就进工程了。

2.2 方式二:手动拷贝目录(兼容 Unity 5.6+)

把项目克隆到本地,然后把LuaProfiler目录整体拷贝进你的 Assets 目录:

git clone https://gitcode.com/gh_mirrors/mi/Miku-LuaProfiler

拷贝完成后回到 Unity 等它编译结束。验证是否安装成功:菜单栏应出现Window > Analysis > Lua Profiler选项,点击后能弹出分析窗口,即代表安装成功。

📌 提示:如果项目里没有用 XLua,而是别的 Lua 方案,不影响安装;编辑器本地模式对 Lua 虚拟机类型没有硬性要求。

3、第二步:3分钟跑通本地模式,拿到第一份数据

安装完成后,先别急着上真机,用编辑器本地模式(local mode)跑一次,这是成本最低的验证路径。

3.1 打开窗口并启动

  1. 点击菜单Window > Analysis > Lua Profiler打开分析窗口。
  2. 确认窗口顶部处于local mode(本地模式)。
  3. 直接点击 Unity 的 Play 按钮运行游戏。

运行后你会看到类似下面的界面:左侧是内存/FPS 指标区,中间是折线图,底部是函数明细表。

3.2 如何判断数据在流动

  • 左侧Lua / Mono / Fps指标开始出现实时数值,说明采集链路已打通。
  • 底部表格开始出现[lua]xxx开头的函数条目,说明Lua代码已被注入采样点。

只要看到这两点,你的第一次Lua性能分析就已经成功了。

4、第三步:读懂面板上的每个数字(数据字典)

很多新手拿到数据后一脸懵,这里给你一张速查表,对照着看就不会迷路。

字段含义
Overview函数名称([lua]开头是Lua函数,[C#]:开头是托管函数)
totalLuaMemory该函数连带产生的所有Lua GC 总和
self(Lua)函数本身产生的 Lua GC 量
totalMonoMemory该函数连带产生的所有 Mono GC 总和
self(Mono)函数本身产生的 Mono GC 量
currentTime函数在当前帧的运行耗时
averageTime该函数的平均耗时
totalTime该函数累计消耗的总时间
LuaGC / MonoGC当前帧新产生的 GC 量
totalCalls / Calls游戏开始后的总调用次数 / 当前帧调用次数

实战读法:先盯totalLuaMemorytotalTime两列。哪个函数排最前,哪个就是当前性能或内存问题的头号嫌疑人;再看它的self,判断是它自己分配的多,还是它调用的子函数分配的多,顺着调用链一路往下就能找到根因。

5、第四步:进阶三板斧——录制、排序、搜索

拿到数据只是开始,真正高效的分析要靠下面三个操作组合。

5.1 录制回放:定位内存暴涨的精确帧

实时数据是"流动的",难以回看。这时用Record模式:

  1. 点击顶部的Record按钮开始录制。
  2. 在游戏里执行你要测的操作(比如打开背包、切换场景)。
  3. 再次点击停止录制。
  4. 在时间轴上用鼠标拖选(或按键盘左右键)一段内存明显上涨的区域,底部表格会自动展示该时间段内的热点函数。

5.2 排序:一键揪出内存/耗时冠军

在搜索框中输入[lua]过滤出Lua函数,然后点击右上角的merge按钮合并同名函数,最后点击任意数据列的表头即可排序。想看内存大户就点totalLuaMemory,想看卡顿来源就点totalTime

5.3 搜索:精确过滤目标函数

搜索框支持直接输入函数名关键词,配合排序使用,可以快速确认"某个特定模块的Lua代码"是否健康。

6、第五步:真机 Android 远程分析(remote mode)

编辑器数据不代表真机表现,Lua GC 在低端安卓机上更容易暴露问题。真机流程稍复杂,但按下面步骤走很稳。

6.1 打包前的准备

打包时必须加上宏USE_LUA_PROFILER,否则真机上不会注入采样代码。

6.2 激活真机 Hook

Miku-LuaProfiler 默认通过 hook 系统dlopen来寻找Lua的native库,这需要你在应用沙盒里放一个"开关文件"。打包安装后,用 ADB 执行:

adb shell cd /sdcard/Android/data/{你的包名}/files touch need_hook_miku_lua

这个文件被读取后会自动删除,下次需要重新创建,避免对正式包产生副作用。

6.3 连接并查看数据

  1. 运行游戏。
  2. 在分析窗口点击local mode切换为remote mode
  3. 在 IP 栏输入手机的局域网 IP(端口默认 2333),点击连接。

如果公司网络禁止直连手机 IP,用 USB 数据线连接后执行端口转发,IP 填本机回环地址即可:

adb forward tcp:2333 tcp:2333

之后在 IP 栏输入127.0.0.1就能收到数据。

6.4 进阶:指定Lua库避免闪退

hook 系统dlopen偶尔会引发莫名闪退,官方建议在 Assembly-CSharp 中实现ILuaCustomSetting接口,主动指定你的Lua库(比如libxlua.so):

#if USE_LUA_PROFILER && UNITY_ANDROID && !UNITY_EDITOR namespace MikuLuaProfiler { public class CustomLua : ILuaCustomSetting { const string LIB_NAME = "miku_hook"; [DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr miku_dlopen(string path, int mode); public IntPtr GetPtr() { // 换成你项目实际的lua库名即可 IntPtr result = miku_dlopen("libxlua.so", 2); return result; } } } #endif

注意加上防裁剪配置,确保这段代码在 Release 包中存活。

7、第六步:查泄漏实战——快照对比三连击

这是 Miku-LuaProfiler 最硬核也最实用的能力:用三次点击定位"打开就再也释放不掉"的对象。

7.1 标准操作流程

假设你要查"打开 UI 后泄漏"的问题:

  1. 打开 UI 前,点击MarkStaticRecord,拍下第一张快照(基准状态)。
  2. 打开 UI,执行一段操作后,点击MarkLuaRecord,拍下第二张快照(持有状态)。
  3. 关闭并释放 UI,点击DiffRecord,工具自动对比两次快照。

对比结果中:

  • add values:第二张快照新增的对象,若释放后仍存在即疑似泄漏。
  • rm values:已被正确释放的对象,可视为对照。
  • Destory null values:已变成"空引用"的对象,同样值得关注。

7.2 看懂 ref 数据:C# 侧的回调泄漏

窗口中的ref列表通常存放 C# 持有的 Lua 回调函数。操作建议:每次打开 UI 前先clear数据,进入 UI 后记录,释放 UI 后再看——如果仍然持有不少委托,说明回调没被正确注销,这同样是典型泄漏。

对于已销毁对象,工具会给出详细的引用链文本(null.txt),按图索骥就能找到是谁在"握着"它不放。

7.3 给业务代码加自定义采样点

有时你想精确测量某段业务逻辑,不必等函数级统计,直接在 Lua 里打点:

local LuaProfiler = MikuLuaProfiler.LuaProfiler LuaProfiler.BeginSampleCustom("my_business_point") -- 你要测量的代码 LuaProfiler.EndSampleCustom()

这样这段代码会作为独立条目出现在分析列表中,方便与整体数据对照。

8、避坑指南:新手最容易踩的5个坑

坑1:打开 DeepLua 后报一堆空异常Miku-LuaProfiler 会在代码执行前注入 BeginSample/EndSample 的 Upvalue,导致原先使用debug.getupvalue取值改值的逻辑错乱。解法是遍历 Upvalue 表按名字取值:

function debug.getupvalue_byname(func, name) local i = 1 while true do local n, v = debug.getupvalue(func, i) if not n then break end if n == name then return v end i = i + 1 end end

坑2:真机只出协程/resume 数据多半是用了 luac 加密后的字节码。请直接使用明文字符串加载Lua,否则注入采样点会失败。

坑3:跑完分析后手动调 luaGC 无效工具为了统计准确接管了 GC 触发逻辑,用户手动触发被屏蔽。GC 会在内存增长到上次的约 1.2 倍时自动触发,属正常现象。

坑4:totalLuaMemory 出现负数底层按 Lua 虚拟机总量差值计算 GC,如果函数运行中途发生了 GC,差值就可能为负。统计时关闭自动 GC 即可规避。

坑5:XLua 自带 Demo 跑不起来把 Demo 里internal static LuaEnv luaEnv = new LuaEnv()的赋值挪到Awake()中即可。

9、写在最后:你的下一步行动清单

到这里,你已经完整掌握了 Miku-LuaProfiler 的六步实战法:装包 → 跑通本地模式 → 读懂数据 → 录制排序搜索 → 真机远程分析 → 快照查泄漏。核心逻辑值得深挖的入口有两个:采集与注入逻辑在LuaProfiler/Runtime/Core/Driver/,泄漏对比脚本在LuaHookSetup.cs内的diff_script,想进阶源码的同学可以从这里入手。

给你的下一步建议:今天就在你的项目里跑一次本地模式,录一段"打开主界面"的操作,按totalLuaMemory排个序——相信我,你会第一次如此直观地看到你的 Lua 代码把钱花在了哪里。性能优化这条路没有终点,但有了趁手的工具,每前进一步都算数。动手吧,跑起来的那一瞬间,你会感谢现在的自己。🚀

【免费下载链接】Miku-LuaProfiler项目地址: https://gitcode.com/gh_mirrors/mi/Miku-LuaProfiler

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

← 返回列表