游戏卡顿白皮书 › L1 客户端游戏进程
客户端垃圾回收 Client GC (Unity C#, Unreal, Lua)
原因 ID cg-gc · 主责 研发团队·客户端开发
在含图示和实验的完整版中打开此卡片 →
回收用完丢弃的内存(垃圾对象)期间,整个游戏会停住。特点是按固定间隔一卡一卡。
起因 每帧都创建并丢弃临时字符串、数组、列表 → 结果 垃圾对象堆积后,GC 暂停主线程来回收 → 画面表现 每隔几秒到几十秒,有规律地一卡一卡
- 症状
- 一卡一卡, 卡住
- 因素
- 停顿
- 谁会遇到
- 只有我
- 何时出现
- 固定周期, 人多的时候
- 负责方
- 主责 研发团队·客户端开发
- 研发团队要做的事
- 减少分配(避免字符串拼接、LINQ、lambda 捕获),使用对象池,开启增量 GC(Unity 2020 起为默认值),在加载界面这类停一下也无妨的时机提前执行 GC。
- 数值参考
- 通常每次几 ms 到 100 ms,低端手机或内存占用大的游戏会更长(实验中约 150~170 ms)。Unity 的 GC 每次运行都要扫描整个堆,游戏占用的内存越多,耗时越长。
- 监控图上
- 周期性尖峰 · 帧耗时、GC 执行时刻
- 查看位置
- 在开发版本中查看 Unity Profiler 的 GC.Collect、GC.Alloc 标记。Unreal 看 stat GC 和 stat Hitches(超过 t.HitchFrameTimeThreshold 所设时长的帧会记入日志)
- 确认依据
- 每个冲高的帧里都有 GC.Collect 区段,其长度与冲高时长相近,间隔为几秒到几十秒且有规律。人多的地方每帧 GC.Alloc 增加
- 排除依据
- 冲高的帧里没有 GC 区段,看“主线程同步加载/Shader 编译”或“大规模同屏渲染负载”
- 确认手段
- 需要游戏服务器/客户端的日志和指标
- 深入了解
- 在 Unity 这类使用 C# 的客户端中很常见。每帧重新生成战斗日志字符串、伤害数字、UI 文本的代码是主要来源。如果只在人多的地方卡,说明有代码产生的垃圾对象随人数成比例增加。增量 GC 每帧只回收一小部分(Unity 默认 3 ms),但产生垃圾的速度一旦超过回收速度,最终还是会一次性停住。Unreal Engine 也有自己的 GC,用来清理不再使用的游戏对象。具体取决于引擎版本和设置,默认设置下约每 1 分钟运行一次,有时会造成间隔 1 分钟的短暂停顿。用 Lua 等脚本编写游戏规则的客户端,脚本本身的 GC 也会另外运行。
出处
- Garbage collection modes Unity
增量 GC 为默认设置,分多帧回收;关闭后,扫描整个堆期间主线程会停住,可达数百 ms - Scripting.GarbageCollector.CollectIncremental Unity
增量 GC 单次占用时间(时间片)的默认目标为 3 ms(incrementalTimeSliceNanoseconds) - Garbage Collection Settings in the Unreal Engine Project Settings Epic Games
Unreal GC 按设定间隔(Time Between Purging Pending Kill Objects,单位为秒)运行的设置项。该文档未写明默认值 - Profiler markers reference Unity
GC.Collect:垃圾回收期间程序代码暂停的区段(不到 1 ms 到数百 ms),GC.Alloc:托管堆分配 - Stat Commands in Unreal Engine Epic Games
stat GC(垃圾回收统计)、stat Hitches(把超过 t.HitchFrameTimeThreshold 的帧记入日志)
相关原因
同一层:L1 客户端游戏进程
其他层中同样导致“一卡一卡”的原因
查看含图示和实验的原卡片