.NET 11 性能深度解读:这一次,真的「到 11」了 - 张善友

.NET 11 性能深度解读:这一次,真的「到 11」了 - 张善友

💡 原文中文,约7300字,阅读约需18分钟。
📝

内容提要

.NET 11 延续年度性能改进传统,核心亮点是 Runtime Async 重构异步底层:同步完成时结果直接穿透调用链,避免物化 Task,大幅降低异常处理开销。JIT 在逃逸分析、边界检查消除、去虚拟化上持续优化,向量化与 Arm64/SVE 指令选择提升明显,GC 写屏障和线程池开销降低。MAUI 全面切换到 CoreCLR。升级无需修改代码即可获益。

🔎

延伸解读

Runtime Async 的边界与代价

Runtime Async 并非让所有异步操作零分配。文章明确指出,如果把 Task 存进集合、手动挂接续体或以对象形式观察它,该对象依然必要。它的设计目标是“不为观察不到的边界付费”。这意味着收益集中在同步完成且结果直接穿透调用链的常见模式上,而需要真正挂起或显式操作 Task 的场景仍会承担原有开销。理解这一边界,有助于避免对异步性能提升产生不切实际的预期。

JIT 优化的实际触发条件

文章列举的边界检查消除、逃逸分析等改进都有具体前提。例如 != 循环克隆仅支持步长为 1 或 -1,i += 2 不行;可空装箱内部化针对 int? 格式化这类临时对象;接口泛型虚调用去虚拟化依赖 JIT 能识别接收者精确类型。这些条件说明,并非所有写法都能自动受益,代码结构仍会影响优化是否生效。开发者若想验证收益,应针对自身工作负载实测,而非假设所有循环和调用都会变快。

MAUI 切换 CoreCLR 的意义

MAUI 在 Android、iOS 和 Mac Catalyst 上正式切换到 CoreCLR,意味着移动应用与 ASP.NET Core、云服务、桌面 .NET 共享同一个运行时、JIT、GC 和诊断设施。这不仅是性能改进的合并,也引入了分层编译、ReadyToRun 和 PGO,并为 NativeAOT 打下共同基础。对移动开发者而言,运行时统一后,性能优化和诊断工具链的复用度更高,但具体收益仍取决于平台和负载。

如何量化自己的升级收益

文章建议用 BenchmarkDotNet 双目标编译,将 csproj 的 TargetFrameworks 设为 net11.0;net10.0,并用 --runtimes net10.0 net11.0 跑同一组基准,从而得到自己机器上的性能对比。文中所有数据均来自原文基准测试,具体结果因机器与工作负载而异。升级运行时通常无需改代码,但“免费午餐”的实际幅度需要实测确认,尤其是对启动时间、分配噪声或特定向量化路径敏感的应用。

Q&A

Runtime Async 是什么?它如何提升 .NET 异步性能?

Runtime Async 是 .NET 11 中对 CLR 异步底层的重大重构。传统 async/await 中,每个异步方法调用都会物化 Task 对象,即使同步完成也要包装和解包。.NET 11 的 JIT 能识别“调用返回 Task 的方法并立刻 await”的模式,同步完成时结果直接穿透调用链,只有最外层边界才包装成 Task;真正挂起时用续体记录整条链,中间层不再需要 Task。这大幅降低了异常处理开销,并减少了分配。

Runtime Async 能让所有异步操作都变成零分配吗?

不能。Runtime Async 的目标是“不为观察不到的边界付费”。如果你把 Task 存进集合、手动挂接续体、或者以对象形式观察它,那个 Task 对象依然必要。它只优化同步完成且结果直接穿透的场景。

.NET 11 在 JIT 逃逸分析方面有哪些具体改进?

主要改进包括:可空装箱的内部化(消除 int? 格式化时的临时堆分配,提速近 5 倍);接口泛型虚调用去虚拟化(如将 Processor 强转 IProcessor 后调用泛型方法,JIT 能去虚拟化并内联,消除分配);解决 constrained. 前缀的地址暴露问题(EqualityComparer<T>.Default 中 24 字节堆分配消失);Tier 0 也优化装箱(ArgumentNullException.ThrowIfNull 在非空值类型时 Tier 0 即可消除装箱)。

.NET 11 如何优化数组边界检查?

.NET 11 新增多项边界检查消除:支持 != 循环的循环克隆(步长 ±1 时);return 语句纳入范围检查克隆;基本块内合并多个边界检查;更智能的 Span 切片跟踪;解析器式前瞻检查(如 (uint)(i+2) < (uint)span.Length 消除后续检查);LeadingZeroCount 返回值范围已知,消除查找表下标检查;同一索引重复访问、长度贯穿方法追踪、arr[^4] 成立则 arr[^3] 无需再查等。

.NET 11 在向量化和 Arm64/SVE 指令选择上有哪些提升?

向量化方面:AVX-512 嵌入式广播与掩码减少常量池占用和指令数;更宽的向量归约(Vector256.Sum/Vector512.Sum 避免拆分成 128 位片段);stackalloc 清零提速近一倍。Arm64 方面:指令选择大丰收(and/cmp/ands、asr/ubfx、cnt/ctz、str/stp 等);SVE 继续演进,更多循环模式获得硬件生成的谓词掩码,减少掩码操作设置搬运,支持可伸缩向量常量与向量存储初始化。

.NET 11 对 GC 写屏障和线程池做了哪些优化?

GC 写屏障优化:协变数组存储时,JIT 展开辅助函数,知道数组精确类型时消除协变检查并优化屏障(循环写 object[] 4096 元素从 10.85 μs 降至 6.04 μs);结构体整体拷贝时,引用字段用引用 store、非引用数据用 SIMD 向量 store;相邻字段合并写(Int128 赋值从两次 64 位 store 变成一条 16 字节向量写,两个相邻 int 字段合并成一条 64 位 store)。GC 本体也有 ConditionalWeakTable 等改进。线程池开销降低。

MAUI 在 .NET 11 中有什么重大变化?

.NET 11 起,MAUI 在 Android、iOS 和 Mac Catalyst 上正式切换到 CoreCLR,最后一批还在用 Mono 的 MAUI 平台完成迁移。切换后,移动应用与 ASP.NET Core、云服务、桌面 .NET 共享同一个运行时、JIT、GC、诊断设施和性能改进,并引入 CoreCLR 的分层编译、ReadyToRun 和 PGO,也为 NativeAOT 打下共同基础。

升级到 .NET 11 需要修改代码吗?如何量化性能收益?

对于绝大多数应用,升级到 .NET 11 不需要改一行代码,就能获得所有性能收益。要量化收益,可以使用 BenchmarkDotNet 双目标编译:将 csproj 的 TargetFrameworks 设为 net11.0;net10.0,安装 BenchmarkDotNet,用 --runtimes net10.0 net11.0 运行同一组基准,即可得到自己机器上的性能对比报告。

🏷️

标签

➡️

继续阅读