C# AOT 是将 IL 提前编译为原生机器码,实现秒级启动;--self-contained 仍含运行时需 JIT,/p:PublishAot=true 生成无依赖原生可执行文件(15–40MB);反射、序列化等动态行为易导致回退 JIT 或报错,需源生成、显式保留等适配;实测冷启动从 850ms 降至 120ms,内存减半。

C# AOT 不是“另一种编译方式”,而是 .NET 应用从“带运行时启动”走向“像 Go 一样秒开”的关键切换点——它把 IL 中间语言在发布时就彻底翻译成目标平台的原生机器码,跳过运行时 JIT 编译环节。
dotnet publish -r win-x64 --self-contained 和 dotnet publish -c Release /p:PublishAot=true 有啥区别?
前者是传统“自包含部署”,仍含完整 .NET 运行时(约 100MB+),启动时仍要 JIT;后者才是真 AOT:生成纯原生可执行文件,无 .NET 运行时依赖,体积通常压缩到 15–40MB(取决于代码量和 trimming 程度)。
-
PublishAot=true必须配合RuntimeIdentifier(如win-x64、linux-arm64)使用,否则构建失败 - 不加
--self-contained时,AOT 模式默认就是自包含的——这个参数在 AOT 下已冗余 - 若项目含
Microsoft.Extensions.Hosting,需显式禁用某些动态特性,否则ilc编译器会报错:ILC : error IL2026: Using member '...' which has 'RequiresUnreferencedCode' attribute
为什么加了 PublishAot=true 却没变快?常见失效场景
AOT 效果被“反射”“序列化”“动态委托”等行为悄悄抵消——只要代码里有 typeof(T).GetMethod(...)、JsonSerializer.Serialize(obj)(未配置源生成)、Assembly.LoadFrom,AOT 编译器就无法静态确定调用路径,要么报错,要么自动回退到部分 JIT 模式(即“混合模式”,性能提升打折)。
- JSON 序列化必须用源生成器:
,并配置JsonSerializerContext - 反射调用需标注
[RequiresUnreferencedCode]并用或显式保留--reflection-serialization - 泛型集合(如
List)若类型未在编译期“被看到”,AOT 会丢弃其实现,导致运行时报MissingMethodException
.NET 9 AOT 实测性能:启动快多少?内存省多少?
以一个最小 Web API(仅返回 "OK")为例,在 Linux x64 容器中冷启动实测:
dotnet run # JIT 模式:启动耗时 ~850ms,内存峰值 ~120MB dotnet publish -r linux-x64 -c Release /p:PublishAot=true ./bin/Release/net9.0/linux-x64/publish/myapp # AOT 模式:启动耗时 ~120ms,内存峰值 ~65MB
也就是说,启动时间缩短近 85%,内存占用减半。但注意:长期运行的吞吐量(如 QPS)未必更高——JIT 能根据运行时热点做动态优化,AOT 是“一次编译,终身不变”。对 Serverless、CLI 工具、桌面小应用,这是质变;对跑几天不重启的后端服务,收益集中在首请求延迟。
真正容易被忽略的是:AOT 不是开关一开就完事,它把“运行时不确定性”提前到了构建期。你得让编译器“看懂”所有可能路径,否则不是慢,而是直接起不来。










