ReadyToRun启动快但并发吞吐未必提升,NativeAOT启动极快且内存低但受限于语言特性;选型应依场景而定:冷启动敏感选NativeAOT,依赖反射或动态特性的长时服务选ReadyToRun。

ReadyToRun 编译后启动快,但并发吞吐未必提升
ReadyToRun(R2R)本质是提前把 IL 编译成平台相关的机器码并打包进程序集,跳过 JIT 的首次编译开销。这对启动时间有明显改善,尤其在冷启动或小工具类场景中效果显著。
但 R2R 生成的代码仍依赖 .NET 运行时(如 GC、类型系统、反射基础设施),且未做跨方法内联、无用代码剪裁等深度优化。因此高并发下,线程频繁分配对象、触发 GC 或调用未 R2R 的第三方库时,性能优势会被抵消。
-
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishReadyToRun=true是启用 R2R 的标准命令 - R2R 镜像体积通常比普通发布大 2–3 倍,因嵌入了多架构适配码(如 x64 + ARM64 共存时)
- 若引用了含
DynamicMethod、Reflection.Emit或未标注[AssemblyMetadata("IsTrimmable", "true")]的库,R2R 可能静默退回到 JIT
NativeAOT 启动极快、内存占用低,但并发性能取决于代码写法
NativeAOT 完全脱离运行时,将 C# 编译为原生可执行文件,无 JIT、无托管堆初始化、无运行时加载阶段。冷启动常压到 10ms 级别,RSS 内存也显著下降。
但它对语言特性有硬性限制:不支持运行时生成代码、无反射动态绑定(除非显式保留)、无 COM 互操作、默认禁用 async/await 的非托管上下文切换(需手动配置 ThreadPool 回调)。
- 启用方式:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishAot=true - 必须用
[UnmanagedCallersOnly]标记导出函数;async方法需配合ThreadPool.UnsafeQueueUserWorkItem手动调度 - GC 仍存在(Sgen GC),但不可配置为 Server GC;高并发下若大量短生命周期对象集中分配,可能比 R2R 更早触发 GC 暂停
R2R 和 NativeAOT 在 ASP.NET Core 中的实际表现差异
ASP.NET Core 应用启动时需加载中间件管道、配置系统、DI 容器等,R2R 能加速这部分类型加载和 JIT 编译,实测冷启动从 ~800ms 降至 ~350ms(Linux x64,.NET 8)。
NativeAOT 下,相同应用冷启动可压至 ~90ms,但首次 HTTP 请求延迟可能升高 —— 因 AOT 无法预编译所有可能路径(如基于路由参数的策略选择),部分逻辑仍需运行时解析。
- 使用
WebApplication.CreateBuilder(new WebApplicationOptions { WebRootPath = null })可减少静态文件中间件初始化开销,对 AOT 更友好 - R2R 应用仍可热重载(
dotnet watch),NativeAOT 不支持;开发阶段切勿默认开 AOT - 若用了
Microsoft.Extensions.Http.Resilience等依赖动态代理的包,NativeAOT 会直接编译失败,需换用Polly原生 API
选型关键看你的瓶颈在哪
如果目标是 CLI 工具、云函数(Cold Start 敏感)、或嵌入式边缘服务,NativeAOT 几乎总是更优——它消灭了运行时启动不确定性。
如果是长时运行的后台服务或 Web API,且依赖大量反射、动态配置或第三方 SDK,R2R 是更稳妥的选择;强行上 NativeAOT 往往要重写大量胶水代码,反而拖慢交付节奏。
真正容易被忽略的是:两者都不解决 I/O 密集型瓶颈。哪怕启动再快,数据库连接池没调好、HTTP 客户端没复用、日志同步刷盘,照样卡在 100 QPS 就打满 CPU。











