.NET 9中的异常处理性能提升分析:为什么过去慢,未来快

news2025/6/8 17:48:53

一、为什么要关注.NET异常处理的性能

随着现代云原生、高并发、分布式场景的大量普及,异常处理(Exception Handling)早已不再只是一个冷僻的代码路径。在高复杂度的微服务、网络服务、异步编程环境下,服务依赖的外部资源往往不可靠,偶发失效或小概率的“雪崩”场景已经十分常见。实际系统常常在高频率地抛出、传递、捕获异常,异常处理性能直接影响着系统的恢复速度、吞吐量,甚至是稳定性与容错边界。

.NET平台在异常处理性能方面长期落后于C++、Java等同类主流平台——业内社区多次对比公开跑分就证实了这一点,.NET 8时代虽然差距有所缩小,但在某些高并发/异步等极端场景下,异常高开销持续困扰社区和大厂工程师。于是到了.NET 9,终于迎来了一次代际变革式的性能飞跃,抛出/捕获异常的耗时基本追平C++,成为技术圈最关注的.NET runtime底层事件之一。

二、实测:.NET 9异常处理提速直观对比

1. 测试代码

最经典的异常性能测试如下——C# 和 Java的实现基本一致

C#:

class ExceptionPerformanceTest
{
    public void Test()
    {
        var stopwatch = Stopwatch.StartNew();
        ExceptionTest(100_000);
        stopwatch.Stop();
        Console.WriteLine(stopwatch.ElapsedMilliseconds);
    }
    private void ExceptionTest(long times)
    {
        for (int i = 0; i < times; i++)
        {
            try
            {
                throw new Exception();
            }
            catch (Exception ex)
            {
                // Ignore
            }
        }
    }
}

Java:

public class ExceptionPerformanceTest {
    public void Test() {
        Instant start = Instant.now();
        ExceptionTest(100_000);
        Instant end = Instant.now();
        Duration duration = Duration.between(start, end);
        System.out.println(duration.toMillis());
    }

    private void ExceptionTest(long times) {
        for (int i = 0; i < times; i++) {
            try {
                throw new Exception();
            } catch (Exception ex) {
                // Ignore
            }
        }
    }
}

2. 早期测试结果(以.NET Core 2.2时代为例)

  • .NET: 2151ms
  • Java: 175ms

.NET 的异常抛出/捕获速度相较慢得多。但到了.NET 8后期和.NET 9,基准成绩已翻天覆地:

3. 新时代基准结果(.NET 8 vs .NET 9)

借助 BenchmarkDotNet 可以更科学对比:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Environments;

namespace ExceptionBenchmark
{
    [Config(typeof(Config))]
    [HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio, Column.Gen0, Column.Gen1)]
    [MemoryDiagnoser]
    public class ExceptionBenchmark
    {
        private const int NumberOfIterations = 1000;

        [Benchmark]
        public void ThrowAndCatchException()
        {
            for (int i = 0; i < NumberOfIterations; i++)
            {
                try
                {
                    ThrowException();
                }
                catch
                {
                    // Exception caught - the cost of this is what we're measuring
                }
            }
        }

        private void ThrowException()
        {
            throw new System.Exception("This is a test exception.");
        }

        private class Config : ManualConfig
        {
            public Config()
            {
                AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80).AsBaseline());
                AddJob(Job.Default.WithId(".NET 9").WithRuntime(CoreRuntime.Core90));

                SummaryStyle =
                    SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
            }
        }
    }
}

如下图结果,抛出+捕获1000次异常:

  • .NET 8:每次约 12μs
  • .NET 9:每次减少至约 2.8μs (约76~80%提升)

image

.NET 9的性能提升几乎让EH成本降到C++/Java同量级,成为托管平台的性能标杆之一。


三、.NET早期异常处理为何如此之慢?

1. 策略层面的历史误区

传统观点认为:“异常只为异常流程准备,主业务应以if/else或TryXXX等方式避免极端异常分支”。社区和官方因此忽视了EH系统的极限性能,无论架构设计还是细节实现都欠缺优化,反映在:

  • 内部优先保证兼容性和健壮性,而不是高性能
  • 代码中凡是热路径,都让开发者“自觉避开异常”

近年来,现代服务常常:

  • 依赖于“不可靠资源” (如网络、外部API、云存储),短暂失效随时发生
  • 借助基于**async/await**的异步编程,异常常常跨栈、跨线程重抛
  • 在微服务系统中,单点故障可能导致“异常风暴”,大量请求因依赖故障极短时间内批量失败

这些场景下,异常处理已极易成为性能瓶颈,应用的可用性与SLA依赖于异常恢复速度。

2. CoreCLR/Mono 异常实现机制的先天劣势

Windows实现

  • 采用Windows的Structured Exception Handling (SEH),异常抛出后,OS内核统一回溯堆栈、查找/触发catch和finally,且需要“双遍遍历”栈帧(第一次查catch、第二次触发catch/finally,源数据由Windows维护)

    Structured Exception Handling(结构化异常处理,简称 SEH)是微软 Windows 操作系统上一种异常处理机制,主要用于捕获和处理程序运行过程中产生的异常,如访问违规(Access Violation)、除零错误、非法指令等。在 Windows 平台上,SEH 被底层编译器和系统广泛支持。

  • 用户层主要通过回调介入,绝大多数性能消耗“锁死”在OS堆栈查找、回调和上下文切换中,优化空间很小

NameExc %ExcInc %Inc
ntdll!RtlpxLookupFunctionTable11.44,52511.44,525
ntdll!RtlpUnwindPrologue11.24,44111.24,441
ntdll!RtlLookupFunctionEntry7.22,85728.411,271
ntdll!RtlpxVirtualUnwind6.52,57917.77,020
ntdll!RtlpLookupDynamicFunctionEntry3.61,4259.83,889
coreclr!EEJitManager::JitCodeToMethodInfo2.91,1672.91,167
ntdll!RtlVirtualUnwind2.91,13717.97,099
ntoskrnl!EtwpWriteUserEvent2.59904.31,708
coreclr!ExceptionTracker::ProcessManagedCallFrame2.494118.77,405
coreclr!ProcessCLRException2.493893.336,969
ntdll!LdrpDispatchUserCallTarget2.28712.2871
coreclr!ExecutionManager::FindCodeRangeWithLock2.28682.2868
coreclr!memset2.07932.0793
coreclr!ExceptionTracker::ProcessOSExceptionNotification1.974231.912,622
coreclr!SString::Replace1.87201.8720
ntoskrnl!EtwpReserveTraceBuffer1.87181.8718
coreclr!FillRegDisplay1.87091.8709
ntdll!NtTraceEvent1.76737.12,803

Unix/Linux实现

  • 没有SEH,只能自己模拟

  • 采用C++异常,异常抛出后靠libgcc/libunwind的_C++机制回溯托管栈,但需“桥接”托管/本地的边界,异常对象需反复throw/catch,初始化/过滤时会有多次C++异常嵌套传递

    libunwind 是一个开源的栈回溯库,主要用于在运行时获取和操作调用栈,从而支持异常处理、调试和崩溃分析等功能。

  • 托管运行时(如ExecutionManager) 需要频繁做函数表和异常元数据线性遍历(链表查找),并发场景下会有大量锁竞争,极易成为瓶颈

实际CPU性能热点采样发现:

  • libgcc_s.so.1/_Unwind_Find_FDE等C++异常系统函数占用近13%的热点
  • 托管代码层大量链表遍历/锁(ExecutionManager::FindCodeRangeWithLock等)
  • 多线程/多异常场景下lock恶性竞争,栈查找速度极慢
OverheadShared ObjectSymbol
+ 8,29%libgcc_s.so.1[.] _Unwind_Find_FDE
+ 2,51%libc.so.6[.] __memmove_sse2_unaligned_erms
+ 2,14%ld-linux-x86-64.so.2[.] _dl_find_object
+ 1,94%libstdc++.so.6.0.30[.] __gxx_personality_v0
+ 1,85%libgcc_s.so.1[.] 0x00000000000157eb
+ 1,77%libc.so.6[.] __memset_sse2_unaligned_erms
+ 1,36%ld-linux-x86-64.so.2[.] __tls_get_addr
+ 1,28%libcoreclr.so[.] ExceptionTracker::ProcessManagedCallFrame
+ 1,26%libcoreclr.so[.] apply_reg_state
+ 1,12%libcoreclr.so[.] OOPStackUnwinderAMD64::UnwindPrologue
+ 1,08%libgcc_s.so.1[.] 0x0000000000016990
+ 1,08%libcoreclr.so[.] ExceptionTracker::ProcessOSExceptionNotification

额外开销

  • 每次抛出异常需清空/复制完整CONTEXT结构(Windows上下文),单次就近1KB数据
  • 捕获栈信息、生成调试辅助、捕获完整stacktrace等都增加明显延迟

3. Async/多线程场景放大性能损耗

现代C#的async/await广泛出现。每遇到await断点,异常需在async状态机多次catch/throw重入口,即使只有1层异常,实际走了多倍catch分支。多线程下,本地堆栈互不关联,所有栈回溯、元数据查找都需走OS或本地锁/链表,进一步拉低性能扩展性。

4. 跨平台和历史兼容包袱

因Windows/Unix两套机制并存,大量platform abstraction和边界容错逻辑,极大增加了维护成本和bug风险。每一次异常跨界都需要特殊处理,开发运维和调优都十分困难。

以下是.NET9以前多线程和单线程异常抛出耗时,可以看到随着堆栈深度的增加,抛出异常要花费的世界越来越长。

image

image


四、技术极客视角:.NET 9彻底变革的细节原理

.NET 9之所以实现了异常处理的性能“质变”,核心思路是吸收NativeAOT的极简托管实现,将主力流程自托管直接管理,核心只依赖native stack walker完成功能边界,避免一切反复嵌套或冗余环节

(一)NativeAOT异常处理架构剖析

1. 设计变革
  • 完全托管驱动主流程
    异常的捕获、catch分派、finally查找、异常对象/类型的元数据查找等主环节,全部写成托管代码(C#逻辑)。
  • native code仅负责栈帧展开(stack walking)
    需要时才调用本地API(libunwind/Windows API)由native/cross平台实现stack frame的move next/遍历,极简无其他依赖。
  • 无C++异常桥接,这样省去了_os-unwind、double catch-rethrow等所有历史冗余。
  • 功能单纯、易于调优和定制,不到300行关键路径代码。
2. 优势分析
  • 代码极简,热路径关键点完全可控
  • 不存在异步场景下的“状态机分支回溯”性能急剧下滑
  • 托管逻辑易于内联、缓存
  • Native代码只做最小功能、极易换实现/裁剪
  • 性能调优点固定且标志性突出(大部分耗时都在stack walker/元数据cache里)
  • 兼容可扩展,后续想做特殊异常/自定义类型极为简便
3. 技术细节
  • 异常对象的stacktrace/元数据在托管代码按需附加
  • 若已知异常只在本地代码路径,完全可绕开“不需要的”full stacktrace/callstack/diagnostic等场景
  • 可以整合cache优化,如将每个托管JIT帧的元数据查找结果放本地线程缓存(甚至开启pgo热点分支识别,见后续)。

(二).NET 9实现与补全 —— 同步NativeAOT设计到CoreCLR

在.NET 9,团队把NativeAOT的异常处理模式移植到了CoreCLR上。主要技术变更包括:

  1. 将异常展开、catch/finally分派等环节全部搬到托管主流程
  2. native helper只做最小的stack frame展开,与垃圾回收栈遍历接口复用(易于维护)
  3. 强化托管级缓存与元数据管理。关键链表遍历全部升级成缓存/高速哈希表,一举解决了多线程、深栈、频繁异常场景下的scalability困境
  4. 钉死所有多余的C++ throw/catch——对Unix/Windows都生效
  5. 为Async/Await生成优化代码路径,避免多次重复抛出/捕获
工程落地与效果
  • 性能测试实测,异常处理耗时降幅约76%~80%,多线程/高并发效果更好
  • 性能剖析热点:主要耗时已缩小到stack walker和关键数据结构哈希效率上,其他已近极致
  • 全平台统一,无历史特殊兼容路径、包袱
真实图片示例
  • 单线程性能提升图:

image

  • 多线程性能提升图:

image


(三)可进一步优化的场景与细节

  1. 热点分支profile(PGO)

    • 异常的“常用路径”可被profile,按pgo机制热路径内联/重编排逻辑
    • 比如async await状态机里常抛异常的分支inline获得最佳cache局部性
  2. Unwind Section缓存/优化

    • 比如在libunwind的findUnwindSections过程中用cache提升多线程吞吐,已实测可提速近7倍
    • 类似样板代码见:https://gist.github.com/filipnavara/9dca9d78bf2a768a9512afe9233d4cbe
  3. 双检省栈trace与细粒度采集

    • 支持仅按需采集stacktrace(避免捕获所有调试信息)
  4. 特殊场景快速捕获(业务异常/操作性异常)

    • 通过拓展托管catch块类型,可以极简分为业务异常与系统异常,实现“无栈捕获”,加速高频捕获型异常(如EndOfData、ParseError等流控制型异常)
  5. 异步异常统一延迟捕获传递

    • 在没有用户自定义try块的async方法中,捕获异常仅保存,真正抛出延迟到非异常主流程结束前即可。这将极大降低状态机驱动的抛出/捕获次数。

六、总结展望

.NET 9通过彻底拥抱NativeAOT极简式的托管异常处理体系,把历史包袱(OS-Specific/C++ Exception Bridge/冗余链表&锁/多次catch-rethrow)一举清除,大幅释放了异常路径的性能潜力。这一变革支撑了.NET在微服务、云原生、异步并发等新主流场景下的顶级运行时表现。未来,随着堆栈展开、元数据cache自适应等不断迭代,.NET有望成为托管平台的异常处理性能“天花板”。


附录与参考文献

  • .NET Runtime Issue #77568: Exception handling performance
  • .Net Exception performance vs JAVA
  • Port NativeAOT exception handling to CoreCLR #88034

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mac 安装git心路历程(心累版)

省流版&#xff1a;直接安装Xcode命令行工具即可&#xff0c;不用安Xcode。 git下载官网 第一部分 上网初步了解后&#xff0c;打算直接安装Binary installer&#xff0c;下载完安装时&#xff0c;苹果还阻止安装&#xff0c;只好在“设置–安全性与隐私”最下面的提示进行安…

计算机网络第2章(下):物理层传输介质与核心设备全面解析

目录 一、传输介质1.1 传输介质的分类1.2 导向型传输介质1.2.1 双绞线&#xff08;Twisted Pair&#xff09;1.2.2 同轴电缆&#xff08;Coaxial Cable&#xff09;1.2.3 光纤&#xff08;Optical Fiber&#xff09;1.2.4 以太网对有线传输介质的命名规则 1.3 非导向型传输介质…

C# 类和继承(扩展方法)

扩展方法 在迄今为止的内容中&#xff0c;你看到的每个方法都和声明它的类关联。扩展方法特性扩展了这个边 界&#xff0c;允许编写的方法和声明它的类之外的类关联。 想知道如何使用这个特性&#xff0c;请看下面的代码。它包含类MyData&#xff0c;该类存储3个double类型 的…

MySQL复杂SQL(多表联查/子查询)详细讲解

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 MySQL复杂SQL&#xff08;多表联查/子查询&a…

STM32使用土壤湿度传感器

1.1 介绍&#xff1a; 土壤湿度传感器是一种传感装置&#xff0c;主要用于检测土壤湿度的大小&#xff0c;并广泛应用于汽车自动刮水系统、智能灯光系统和智能天窗系统等。传感器采用优质FR-04双料&#xff0c;大面积5.0 * 4.0厘米&#xff0c;镀镍处理面。 它具有抗氧化&…

Windows平台RTSP/RTMP播放器C#接入详解

大牛直播SDK在Windows平台下的RTSP、RTMP播放器模块&#xff0c;基于自研高性能内核&#xff0c;具备极高的稳定性与行业领先的超低延迟表现。相比传统基于FFmpeg或VLC的播放器实现&#xff0c;SmartPlayer不仅支持RTSP TCP/UDP自动切换、401鉴权、断网重连等网络复杂场景自适应…

从 JDK 8 到 JDK 17:Swagger 升级迁移指南

点击上方“程序猿技术大咖”&#xff0c;关注并选择“设为星标” 回复“加群”获取入群讨论资格&#xff01; 随着 Java 生态向 JDK 17 及 Jakarta EE 的演进&#xff0c;许多项目面临从 JDK 8 升级的挑战&#xff0c;其中 Swagger&#xff08;API 文档工具&#xff09;的兼容性…

使用 Coze 工作流一键生成抖音书单视频:全流程拆解与技术实现

使用 Coze 工作流一键生成抖音书单视频&#xff1a;全流程拆解与技术实现&#xff08;提供工作流&#xff09; 摘要&#xff1a;本文基于一段关于使用 Coze 平台构建抖音爆火书单视频的详细讲解&#xff0c;总结出一套完整的 AI 视频自动化制作流程。内容涵盖从思路拆解、节点配…

【发布实录】云原生+AI,助力企业全球化业务创新

5 月 22 日&#xff0c;在最新一期阿里云「飞天发布时刻」&#xff0c;阿里云云原生应用平台产品负责人李国强重磅揭晓面向 AI 场景的云原生产品体系升级&#xff0c;通过弹性智能的一体化架构、开箱即用的云原生 AI 能力&#xff0c;为中国企业出海提供新一代技术引擎。 发布会…

LabVIEW主轴故障诊断案例

LabVIEW 开发主轴机械状态识别与故障诊断系统&#xff0c;适配工业场景主轴振动监测需求。通过整合品牌硬件与软件算法&#xff0c;实现从信号采集到故障定位的全流程自动化&#xff0c;为设备维护提供数据支撑&#xff0c;提升数控机床运行可靠性。 ​ 面向精密制造企业数控机…

计算机组成与体系结构:补码数制二(Complementary Number Systems)

目录 4位二进制的减法 补码系统 &#x1f9e0;减基补码 名字解释&#xff1a; 减基补码有什么用&#xff1f; 计算方法 ❓为什么这样就能计算减基补码 &#x1f4a1; 原理揭示&#xff1a;按位减法&#xff0c;模拟总减法&#xff01; 那对于二进制呢&#xff1f;&…

C#使用MindFusion.Diagramming框架绘制流程图(2):流程图示例

上一节我们初步介绍MindFusion.Diagramming框架 C#使用MindFusion.Diagramming框架绘制流程图(1):基础类型-CSDN博客 这里演示示例程序: 新建Windows窗体应用程序FlowDiagramDemo,将默认的Form1重命名为FormFlowDiagram. 右键FlowDiagramDemo管理NuGet程序包 输入MindFusio…

【物联网-ModBus-RTU

物联网-ModBus-RTU ■ 优秀博主链接■ ModBus-RTU介绍■&#xff08;1&#xff09;帧结构■&#xff08;2&#xff09;查询功能码 0x03■&#xff08;3&#xff09;修改单个寄存器功能码 0x06■&#xff08;4&#xff09;Modbus RTU 串口收发数据分析 ■ 优秀博主链接 Modbus …

Java应用10(客户端与服务器通信)

Java客户端与服务器通信 Java提供了多种方式来实现客户端与服务器之间的通信&#xff0c;下面我将介绍几种常见的方法&#xff1a; 1. 基于Socket的基本通信 服务器端代码 import java.io.*; import java.net.*;public class SimpleServer {public static void main(String…

Python_day47

作业&#xff1a;对比不同卷积层热图可视化的结果 一、不同卷积层的特征特性 卷积层类型特征类型特征抽象程度对输入的依赖程度低层卷积层&#xff08;如第 1 - 3 层&#xff09;边缘、纹理、颜色、简单形状等基础特征低高&#xff0c;直接与输入像素关联中层卷积层&#xff08…

如何在mac上安装podman

安装 Podman 在 macOS 上 在 macOS 上安装 Podman 需要使用 Podman 的桌面客户端工具 Podman Desktop 或通过 Homebrew 安装命令行工具。 使用 Homebrew 安装 Podman&#xff1a; (base) ninjamacninjamacdeMacBook-Air shell % brew install podman > Auto-updating Hom…

小黑一层层削苹果皮式大模型应用探索:langchain中智能体思考和执行工具的demo

引言 小黑黑通过探索langchain源码&#xff0c;设计了一个关于agent使用工具的一个简化版小demo&#xff08;代码可以跑通&#xff09;&#xff0c;主要流程&#xff1a; 1.问题输入给大模型。 2.大模型进行思考&#xff0c;输出需要执行的action和相关思考信息。 3.通过代理&…

阿里云ACP云计算备考笔记 (4)——企业应用服务

目录 第一章 企业应用概览 第二章 云解析 1、云解析基本概念 2、域名管理流程 3、云解析记录类型 4、域名管理 ① 开启注册局安全锁 ② 域名赎回 第二章 内容分发网络CDN 1、CDN概念 2、使用CDN前后对比 3、使用CDN的优势 4、阿里云CDN的优势 5、配置网页性能优化…

ARM SMMUv3简介(一)

1.概述 SMMU&#xff08;System Memory Management Unit&#xff0c;系统内存管理单元&#xff09;是ARM架构中用于管理设备访问系统内存的硬件模块。SMMU和MMU的功能类似&#xff0c;都是将虚拟地址转换成物理地址&#xff0c;不同的是MMU转换的虚拟地址来自CPU&#xff0c;S…

hadoop集群datanode启动显示init failed,不能解析hostname

三个datanode集群&#xff0c;有一个总是起不起来。去查看log显示 Initialization failed for Block pool BP-1920852191-192.168.115.154-1749093939738 (Datanode Uuid 89d9df36-1c01-4f22-9905-517fee205a8e) service to node154/192.168.115.154:8020 Datanode denied com…