NativeAOT显著降低启动时间和内存占用但削弱高并发吞吐能力,因泛型展开致二进制膨胀、禁用动态代码生成、GC调优受限、ThreadPool行为固化及调试能力下降。
NativeAOT 编译后,dotnet publish 输出的是纯本地二进制,没有 JIT 编译开销和运行时元数据,因此冷启动接近零——这对 serverless 或短生命周期服务很关键。但代价是:所有泛型实例在编译期全量展开,类型爆炸会导致最终二进制体积膨胀,且无法使用运行时动态代码生成(如 Reflection.Emit、Expression.Compile),很多高性能网络库(如早期版本的 Kestrel)依赖这些机制做连接复用或 pipeline 优化。
高并发场景下更敏感的是线程调度和 GC 行为:NativeAOT 默认启用 ServerGC,但无法动态调优(如 GC.Collect 调用被禁用,GCSettings.LatencyMode 无效),且堆外内存(如 MemoryMappedFile、Unsafe.Allocate)需手动管理,稍有不慎就引发泄漏或竞争。
NativeAOT 不等于“无 GC”——它仍保留一个精简版 SGC(Simple GC),仅支持 Gen0 回收,且不可禁用。真正实现亚毫秒级延迟,必须做到:
Span 和 stackalloc,避免 new byte[] 或 string 构造System.Text.Json 的默认反射序列化,改用源生成器(JsonSerializerContext + [JsonSourceGenerationOptions])async/await 在 hot path 上创建状态机对象;必要时用同步 I/O + IOCP 模式(如 Socket.ReceiveAsync 配合 MemoryPool)rd.xml 显式保留必需的反射目标,否则 typeof(T).GetMethod 在运行时返回 null
ThreadPool 行为与传统 .NET 不同NativeAOT 下 ThreadPool 仍可用,但初始线程数固定(默认 1),且 ThreadPool.SetMinThreads 无效。这意味着:
Task.Run)会排队等待,而非快速扩容Parallel.For 等并行构造可能退化为串行执行Channel + 预启动 worker loop(while (!ct.IsCancellationRequested)),完全绕过线程池另外,Task 对象本身在 NativeAOT 中开销更大(无 JIT 优化,所有 awaiter 都是虚方法调用),高频创建 Task.CompletedTask 也会累积压力。
没有 JIT,意味着没有 dotnet-dump 的托管堆快照、没有 dotnet-trace 的 GC/ThreadPool 事件、也没有 PerfView 的 IL 级别采样。你能拿到的只有:
perf record -e cycles,instructions,page-faults(Linux)或 ETW(Windows)的原生事件Microsoft.Diagnostics.Runtime(当启用 EmbedInteropTypes=true 且导出符号)读取有限堆信息ActivitySource + EventSource,且不能依赖 DiagnosticSource 的订阅机制(部分被裁剪)一个典型陷阱:Console.WriteLine 在 NativeAOT 中底层调用 write() 系统调用,高并发写 stdout 会成为瓶颈,应替换为无锁 ring buffer + 单独 flush 线程。
using System.Buffers;
using System.IO.Pipelines;
// 推荐的日志缓冲模式(非阻塞)
var pipe = new Pipe(new PipeOptions(
pool: MemoryPool.Shared,
minimumSegmentSize: 4096));
// 后续通过 pipe.Writer 以 Span 形式写入,避免 string → UTF8 编码分配
NativeAOT 不是“一键低延迟”,而是用编译期确定性换掉运行时灵活性。真正压测时,常发现瓶颈不在 CPU 或网络,而在某个被忽略的 ToString() 调用触发了隐式 StringBuilder 分配——这种细节,在传统 .NET 里可

perf script 反查 callgraph。