卡顿、掉帧怎么查
阴阳师、星铁、终末地都是这么查出来的。开发者的机器一般配置都很高,加核、加内存解决不了的才是要查的。
1. 先确认测的是什么
- 确认真进了要测的画面。菜单、读条、战斗、大世界要分开测。
- 战斗要按自动化脚本的日志切出每一场来算。30 秒一个窗口的话,里面混着菜单和读条,算出来的不是战斗帧率。
- 记下同机开着几个别的模拟器:开多了会全核降频,帧率整体掉,结论要连同这个数一起看。
- 对比时用同一场景、同一画质、同一实例配置。
2. 测帧率
- 客体 logcat 里
EGL_emulation每秒打一行app_time_stats: avg=… min=… max=… count=…。 - 只取游戏主进程的行:父进程是
zygote64、名字是包名的那个。 count加起来除以秒数就是帧率,max是这一秒里最慢的一帧。
3. 看瓶颈在客体还是宿主
客体:shell top -H -p <游戏 pid> 看各线程的 CPU,重点看主线程(Unity 游戏叫 UnityMain)和渲染线程。
- 一条线程吃满一个核、其余核大多空闲:单线程瓶颈。加核、加内存都没用。
- 主线程很忙,帧率却不随 CPU 频率变化:它多半在等(锁、访存、宿主回包),不是在算。
宿主:用任务管理器或 Process Explorer,看 qemu 进程里虚拟 CPU 线程和渲染线程各占多少,再看显卡 3D 占用。渲染线程满了,瓶颈在图形这边。
4. 往下挖
- 主线程在干什么、在等什么:在客体里
simpleperf record -g -t <线程 id>录二三十秒,看热点和调用链(例如大量futex等待)。 - 卡住或画面冻住:
shell su 0 debuggerd -b <pid>取所有线程的栈,看谁在等谁。 - 图形往返:渲染线程的时间如果大多耗在客体和宿主之间的同步往返上,就看能不能批量化,或者换掉同步调用。
- 加载和刚进战斗时最卡:看客体内存换出(swap)和磁盘读写,主线程这时多半在等,不在算。
5. 记结论
写清哪个场景、多少帧、哪条线程满了、改了什么开关,前后对比的数字各是多少。对比要交替多轮跑,看组间差别是不是大过组内波动,只跑一次比一次不算数。
以前查出来的例子
| 游戏 | 现象 | 查出来的原因 | 结果 |
|---|---|---|---|
| 阴阳师 | 庭院 44–48 fps;菜单约 44 fps,进出战斗读条卡 1 秒左右 | 庭院:GL 传输走 pipe。菜单和读条:游戏主线程用户态满载,跑的是转译后的 arm64 代码;客体其余 CPU 七成空闲,宿主渲染线程只有两三成 | GL 传输换成 asg 以后庭院 60 fps。菜单和读条是转译开销,加核没用 |
| 星铁 | 战斗 40–48 fps;第一次进战斗那 30 秒只有 7–16 fps | 战斗:主线程和渲染线程各吃满一个核,受单核速度限制,加到 8 核、加内存都不提高。首战:主线程只有 31%,在等加载和内存换出 | 战斗帧率只能靠转译效率、单核频率或降画质;首战那段要减少换出 |
| 终末地 | 跑任务时 12.8 fps;标题画面 14.6 fps | 跑任务:渲染线程 79% 的时间耗在客体和宿主之间的同步往返上,每帧约 159 次描述符分配。标题:宿主可见的显存(ReBAR)让上传走了慢路径 | 打开 Vulkan 批量描述符更新后 30.5 fps;设 ANDROID_EMU_VK_HIDE_REBAR=1 后标题 60.7 fps |