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 序列化必须用源生成器:
<packagereference include="System.Text.Json.SourceGeneration" version="8.0.0"></packagereference>,并配置
JsonSerializerContext反射调用需标注
[RequiresUnreferencedCode]并用
<trimmerrootassembly></trimmerrootassembly>或
<ilcarg>--reflection-serialization</ilcarg>显式保留 泛型集合(如
List<mytype></mytype>)若类型未在编译期“被看到”,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 不是开关一开就完事,它把“运行时不确定性”提前到了构建期。你得让编译器“看懂”所有可能路径,否则不是慢,而是直接起不来。
