한국어English日本語简体中文繁體中文DeutschไทยTiếng ViệtРусскийPortuguês (Brasil)EspañolBahasa Indonesia

游戏卡顿白皮书 › 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 也会另外运行。

出处

  1. Garbage collection modes Unity
    增量 GC 为默认设置,分多帧回收;关闭后,扫描整个堆期间主线程会停住,可达数百 ms
  2. Scripting.GarbageCollector.CollectIncremental Unity
    增量 GC 单次占用时间(时间片)的默认目标为 3 ms(incrementalTimeSliceNanoseconds)
  3. Garbage Collection Settings in the Unreal Engine Project Settings Epic Games
    Unreal GC 按设定间隔(Time Between Purging Pending Kill Objects,单位为秒)运行的设置项。该文档未写明默认值
  4. Profiler markers reference Unity
    GC.Collect:垃圾回收期间程序代码暂停的区段(不到 1 ms 到数百 ms),GC.Alloc:托管堆分配
  5. Stat Commands in Unreal Engine Epic Games
    stat GC(垃圾回收统计)、stat Hitches(把超过 t.HitchFrameTimeThreshold 的帧记入日志)

相关原因

同一层:L1 客户端游戏进程

其他层中同样导致“一卡一卡”的原因

查看含图示和实验的原卡片