Microsoft 在 .NET Blog 于 2026 年 9 月 15 日发布的一篇博文中,介绍了 .NET 11 引入的数百项改进,并将其描述为针对即时编译器 JIT、运行时和库的长期累积性工作的成果。核心理念并不是存在某项能让性能翻倍的单一功能,而是反复移除许多小成本:不再必要的边界检查、被取消的内存分配、得以避免的锁或系统调用,以及能够以更少处理器周期执行的循环。
这类改进的重要性在于,其中很大一部分不要求应用程序修改代码或重新设计。JIT 编译器会将通常由 C#、F# 和 Visual Basic 生成的 IL 中间语言转换为处理器执行的本机指令。当它能够证明某个虚调用可以定向到特定类型,或某项检查不会失败时,就可以生成更短的指令,并提高将函数相互内联的机会。
改进去抽象化
文章的一个重点部分关注 Microsoft 所称的去抽象化,即让运行时能够绕过开发者在程序设计中需要的某些抽象所带来的执行成本。例如接口调用和虚函数调用,传统执行方式可能需要加载多个指针并进行间接调用,从而阻止被调用函数与当前函数内联。
.NET 11 继续完善“守护式去虚拟化”(Guarded Devirtualization)。JIT 会识别运行期间最常见的类型,为直接调用它创建快速路径,同时保留虚拟路径,以便稍后出现不同类型时确保执行正确。当这允许函数内联时,还可以进行其他优化,例如常量传播、分支消除和边界检查消除。
这项工作还扩展到了泛型虚函数、ReadyToRun 和 NativeAOT 操作,以及接口中的默认虚实现。文章指出,当直接调用带来更多内联时,这些改进可能会增加代码体积,但也会让程序逻辑在更大程度上对优化器可见。
更少的分配,对垃圾收集器的影响更小
.NET 11 还继续扩展逃逸分析(Escape Analysis),该分析尝试确定在函数内创建的对象是否会超出其作用域。如果能够证明对象不会离开该作用域,JIT 就可以避免将其放置在托管堆上,或在某些情况下完全消除分配,从而减轻垃圾收集器的压力。
来源展示了改进可空值装箱(Nullable boxing)以及集合枚举条件逃逸分析的示例。在一项基准测试中,从实例字段构造的集合枚举场景中,一个大小为 32 字节的分配被消除,执行时间也从 .NET 10 中的 13.874 纳秒降至 .NET 11 中的 2.674 纳秒。一些使用泛型值或接口调用的场景也能够避免大小为 24 字节的临时分配。
这些数字针对的是非常小的操作,因此不应将其解读为所有应用程序都能获得的固定总体性能提升。它们的主要价值在于揭示运行时能够优化的代码模式,而实际影响将取决于应用程序的性质及其热点路径。
委托与异步运行时
.NET 11 对 CoreCLR 中的委托表示进行了调整,其中包括从 64 位进程中的每个委托对象移除一个指针大小的字段;根据文章,这意味着每个委托节省 8 字节。NativeAOT 中的委托表示字段也经过重新排列,并将一些共同使用的值放置在内存中相邻的位置,以改善其在 Arm64 等架构上的访问。
该博文还介绍了一种名为“runtime async”的新结构,将 async/await 方法转换工作的一部分从 C# 编译器转移到 JIT 和运行时。在传统模型中,编译器会生成一个包含恢复所需字段的状态机,例如参数、局部值、等待对象和执行状态。而新模型在 IL 中采用更小的契约,然后让运行时和 JIT 决定挂起点仍然存活的内容、延续对象的排列方式,以及如何创建外部可见的 Task 或 ValueTask。
实际会发生什么变化?
这一轮改进的编辑层面意义在于,.NET 11 更重视优化现有代码,而不是增加新的 API。高度依赖接口、泛型函数、委托、枚举和异步操作的应用程序可能无需直接修改源代码就能受益,但提升幅度会因处理器、操作系统、运行时设置和负载性质而异。
Microsoft 建议使用 BenchmarkDotNet 测试结果,同时安装 .NET 10 和 .NET 11,并在两个版本上运行相同的代码。Microsoft 还提醒,已发布的测量结果是非常精细的测试,可能会受到硬件、其他进程和环境设置的影响。因此,仅凭微基准测试结果不足以决定是否升级或证明整体性能提升;在得出更广泛的结论之前,还需要测量实际应用负载。