Tiered Compilation 是 .NET Core 3.0+ 的分层 JIT 策略,先以 Tier 0 快速生成低优化代码启动,再按需升级为启用内联、边界检查消除等深度优化的 Tier 1 代码,默认开启且关闭可能降低长周期服务吞吐量。

什么是 Tiered Compilation(分层编译)
Tiered Compilation 是 .NET Core 3.0+ 引入的 JIT 编译策略,它让同一段 IL 代码在运行时可被多次编译——先用快但质量低的方式(Tier 0),再按需升级为慢但优化强的方式(Tier 1)。这不是“一次编译、永久运行”,而是“边跑边调优”。
默认开启(DOTNET_TieredCompilation=1),关掉反而可能降低吞吐量,尤其对长周期服务。
- Tier 0:JIT 极速生成代码,几乎不内联、不循环展开、不进行逃逸分析,只为快速启动
- Tier 1:JIT 重新编译热点方法,启用完整优化(如内联
MethodImplOptions.AggressiveInlining方法、向量化、类型特化) - 触发条件由运行时统计决定:方法被调用次数 + 循环执行次数达到阈值(非固定值,受 GC 压力、CPU 负载等动态调整)
怎么观察 Tiered Compilation 是否生效
最直接方式是看 JIT 日志或用 dotnet-trace 捕获 Jit/JitStart 和 Jit/MethodCompiled 事件,但日常调试更推荐:
- 设置环境变量:
DOTNET_JitDisasm=YourMethodName(只对指定方法输出汇编) - 搭配
DOTNET_JitDisasmAsm=true可看到 tier 信息,例如输出里出现[Tier-0] YourMethodName和后续的[Tier-1] YourMethodName - 用
PerfView录制并查看JIT/MethodJITTED事件,字段Tier列会显示 0 或 1
注意:DOTNET_JitDisasm 仅影响当前进程,且只对 JIT 编译的方法生效(AOT 编译或已 Tier-1 的方法不会重复打印)。
JIT 在 Tier 1 里实际做了哪些关键优化
Tier 0 和 Tier 1 生成的机器码差异远不止“多几个寄存器分配”。真正影响性能的是这几类决策是否启用:
-
方法内联:Tier 0 基本禁用;Tier 1 会内联标记
[MethodImpl(MethodImplOptions.AggressiveInlining)]的小函数,甚至自动内联无副作用的 getter -
数组边界检查消除:当 JIT 推导出索引恒在范围内(如
for (int i = 0; i ),Tier 1 会删掉每次访问的cmp/jae检查 - 虚拟调用去虚化(devirtualization):若运行时发现某
virtual方法在当前加载上下文中只有唯一实现(如 sealed 类型的 override),Tier 1 可能直接转为静态调用,避免 vtable 查找 -
GC 内联优化:对短生命周期局部对象,Tier 1 更倾向栈上分配(通过
ref struct或逃逸分析判定),避免堆分配和后续 GC 压力
这些不是开关式配置,而是 JIT 根据方法热度、类型稳定性、控制流复杂度综合判断的结果。你无法强制某个方法立刻升 Tier 1,但可通过增加调用频次、减少参数多态性来提高概率。
什么情况下 Tiered Compilation 反而拖慢启动或增加内存
分层编译不是银弹。以下场景需谨慎:
- 超短命进程(如 CLI 工具执行 DOTNET_TieredCompilation=0
- 大量泛型实例化(如
List配合数百个不同T):每个泛型闭包都可能触发独立 Tier 0 → Tier 1 流程,导致 JIT 内存占用翻倍 - 使用
ReadyToRun(R2R)AOT 时:Tiered Compilation 默认仍启用,但 Tier 0 会被跳过(R2R 本身已是预优化代码),Tier 1 升级仍可能发生——若想彻底关闭,需同时设DOTNET_TieredCompilation=0和DOTNET_ReadyToRun=1
最容易被忽略的一点:Tiered Compilation 的“热点探测”依赖运行时统计,而统计本身有开销。在极低延迟敏感场景(如高频交易内核),哪怕几微秒的采样钩子也可能被质疑——这时得权衡是否值得。









