JIT 编译器在运行时做了什么
CLR 的 JIT(Just-In-Time)编译器把 IL(中间语言)在首次调用方法时编译成本地机器码,同时做内联、循环优化、寄存器分配等。这意味着同一段
IL在不同 CPU 架构或 .NET 运行时版本下可能生成不同质量的汇编码。
典型影响包括:
首次执行有明显延迟(如Console.WriteLine第一次调用比后续慢数倍) 热路径(hot path)会被多次重编译(tiered compilation),从 Tier 0 到 Tier 1,提升指令级优化强度 调试模式下默认禁用部分优化(如
DebuggableAttribute影响内联),导致性能差异可达 2–5×
AOT 编译(如 NativeAOT)如何改变执行模型
NativeAOT 在构建时就将整个程序(含依赖)提前编译为原生二进制,不依赖运行时 JIT,也不加载
coreclr.dll或
libcoreclr.so。它本质上是“静态链接 + 预优化”的产物。
关键行为差异:
启动极快(无 JIT 开销,无元数据解析),适合 CLI 工具或 serverless 场景 内存占用更低(无 JIT 编译缓存、无运行时元数据表冗余) 但失去运行时类型反射能力(Type.GetType("Foo") 失败)、动态代码生成(Expression.Compile()报错)、以及大部分
Assembly.Load*行为 泛型实例化必须在编译期确定(
List<int></int>和
List<string></string>都得显式包含,否则运行时报
MissingMethodException)
性能对比:什么时候 JIT 更快,什么时候 AOT 更稳
不是“谁更快”,而是“谁更适合当前负载”。JIT 的优势集中在动态性高的场景;AOT 的优势集中在冷启动敏感、资源受限、且逻辑稳定的场景。
实测常见情况:
Web API(Kestrel + JSON 序列化):JIT 在持续请求下吞吐略高(约 5–10%),因 tiered JIT 能针对真实请求分布优化热点方法;AOT 启动后首秒响应更稳,但长期吞吐略低(缺少运行时反馈驱动的再优化) 命令行工具(如dotnet-ef替代品):AOT 启动时间从 ~300ms 降到 ~20ms,总执行时间反超 JIT 版本(尤其子命令少、生命周期短) 带大量反射/插件加载的系统:AOT 直接不可用,除非用
TrimmerRootAssembly+
DynamicDependency显式标注,否则会在运行时报
System.MissingMetadataException
如何判断你的项目该用哪个
看三个信号:
是否调用Assembly.LoadFrom、
Type.GetMethod(含框架自动反射,如 ASP.NET Core MVC 的 action 绑定)—— 是 → JIT 是唯一可行选项 是否部署在容器中且要求
OCI image启动 是否使用
Span<t></t>/
Memory<t></t>+
Unsafe等底层操作 → AOT 对这些支持良好,但需确认所有
DllImport的 native 库已静态链接或随包分发
一个简单验证方式:
dotnet publish -r linux-x64 -p:PublishAot=true如果构建失败,错误里出现
ILLink相关提示(如
Unresolved assembly或
RequiresDynamicCode),说明当前代码路径与 AOT 不兼容,得先重构或加
[UnconditionalSuppressMessage]注解。
真正难的不是选 JIT 还是 AOT,而是识别出那些隐式依赖运行时动态能力的第三方库——比如某些 ORM 的 lazy loading、日志框架的 caller info 提取,它们在 AOT 下会静默失效或抛出异常。
