Unity 项目如何消除 JIT 对卡顿的影响
ABSTRACT
腾讯 GAD 2016 年文章(arthuryu)。某 MMO 项目发现「首次执行某操作必卡、二次执行正常」的诡异热点,排除资源加载后定位到 Mono JIT 首次编译。团队先尝试启动时全量预热(8 秒不可接受),再改 Mono 源码在 JIT 必经之路插桩、配合开关精确采集热点函数,最终只对 100+ 个类型做预热,消除卡顿。
核心要点
- 现象特征:同一函数首次调用耗时远高于后续调用(如 SetOwner 首次 0.04ms、第二次近乎零),且无资源加载——典型的 JIT 首编译特征。
- 原理:C# 编译为 CIL,Mono 的 JIT 在运行时首次执行时才把 IL 编译成机器码;AOT 则编译期完成。JIT 换来灵活性与热更可能,代价是首次执行的编译开销。
- 方案一(被否决):启动时反射遍历 Assembly-CSharp.dll 全部方法,调
method.MethodHandle.GetFunctionPointer()强制预热——有效但高端机也要约 8 秒,用户体验不可接受。 - 方案二(采用):改 Mono 源码,在
mono_compile_method(JIT 必经之路)里插桩输出程序集/命名空间/类名;暴露unity_mono_enable_jit_dump(bool)开关给 C#,仅在性能热点发生区间采集。 - 精确预热:从采集结果筛出 100+ 个热点类型,启动时只对这些 Type 预热,性能热点消失。
- 方法论亮点:现象假设(JIT)→ 小规模验证(全量预热证明猜想)→ 工程化收敛(插桩 + 开关精确打击),并确认需自行编译定制 Mono。
关键实体与概念
JIT、AOT、Mono、CIL/MSIL、mono_compile_method、MethodHandle.GetFunctionPointer、代码预热、DllImport、性能插桩
关联概念
来源回溯
- 原始文件:
raw/gamedev/unity项目如何消除jit对卡顿的影响-gad腾讯游戏开发者平台.md(evernote/FW-游戏3研发技术 导入)
时效性评估
- 仍有效:「首次执行卡顿先怀疑 JIT/编译期开销」的排查思路、二分采集(开关圈定热点区间)的插桩方法论,可迁移到各类运行时问题。
- 已过时:现代 Unity 移动端默认 IL2CPP(全 AOT),该问题本身已消失;HybridCLR 为 AOT 程序集补充解释执行能力后,热更代码的「预热」又以新形式回归(AOT 泛型补充、元数据注册),但不再需要手改 Mono。