.NET 9中的异常处理性能提升分析:为什么过去慢,未来快
一、为什么要关注.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%提升)
.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堆栈查找、回调和上下文切换中,优化空间很小
Name Exc % Exc Inc % Inc ntdll!RtlpxLookupFunctionTable 11.4 4,525 11.4 4,525 ntdll!RtlpUnwindPrologue 11.2 4,441 11.2 4,441 ntdll!RtlLookupFunctionEntry 7.2 2,857 28.4 11,271 ntdll!RtlpxVirtualUnwind 6.5 2,579 17.7 7,020 ntdll!RtlpLookupDynamicFunctionEntry 3.6 1,425 9.8 3,889 coreclr!EEJitManager::JitCodeToMethodInfo 2.9 1,167 2.9 1,167 ntdll!RtlVirtualUnwind 2.9 1,137 17.9 7,099 ntoskrnl!EtwpWriteUserEvent 2.5 990 4.3 1,708 coreclr!ExceptionTracker::ProcessManagedCallFrame 2.4 941 18.7 7,405 coreclr!ProcessCLRException 2.4 938 93.3 36,969 ntdll!LdrpDispatchUserCallTarget 2.2 871 2.2 871 coreclr!ExecutionManager::FindCodeRangeWithLock 2.2 868 2.2 868 coreclr!memset 2.0 793 2.0 793 coreclr!ExceptionTracker::ProcessOSExceptionNotification 1.9 742 31.9 12,622 coreclr!SString::Replace 1.8 720 1.8 720 ntoskrnl!EtwpReserveTraceBuffer 1.8 718 1.8 718 coreclr!FillRegDisplay 1.8 709 1.8 709 ntdll!NtTraceEvent 1.7 673 7.1 2,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恶性竞争,栈查找速度极慢
Overhead Shared Object Symbol + 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以前多线程和单线程异常抛出耗时,可以看到随着堆栈深度的增加,抛出异常要花费的世界越来越长。
四、技术极客视角:.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上。主要技术变更包括:
- 将异常展开、catch/finally分派等环节全部搬到托管主流程
- native helper只做最小的stack frame展开,与垃圾回收栈遍历接口复用(易于维护)
- 强化托管级缓存与元数据管理。关键链表遍历全部升级成缓存/高速哈希表,一举解决了多线程、深栈、频繁异常场景下的scalability困境
- 钉死所有多余的C++ throw/catch——对Unix/Windows都生效
- 为Async/Await生成优化代码路径,避免多次重复抛出/捕获
工程落地与效果
- 性能测试实测,异常处理耗时降幅约76%~80%,多线程/高并发效果更好
- 性能剖析热点:主要耗时已缩小到stack walker和关键数据结构哈希效率上,其他已近极致
- 全平台统一,无历史特殊兼容路径、包袱
真实图片示例
- 单线程性能提升图:
- 多线程性能提升图:
(三)可进一步优化的场景与细节
-
热点分支profile(PGO)
- 异常的“常用路径”可被profile,按pgo机制热路径内联/重编排逻辑
- 比如async await状态机里常抛异常的分支inline获得最佳cache局部性
-
Unwind Section缓存/优化
- 比如在libunwind的findUnwindSections过程中用cache提升多线程吞吐,已实测可提速近7倍
- 类似样板代码见:https://gist.github.com/filipnavara/9dca9d78bf2a768a9512afe9233d4cbe
-
双检省栈trace与细粒度采集
- 支持仅按需采集stacktrace(避免捕获所有调试信息)
-
特殊场景快速捕获(业务异常/操作性异常)
- 通过拓展托管catch块类型,可以极简分为业务异常与系统异常,实现“无栈捕获”,加速高频捕获型异常(如EndOfData、ParseError等流控制型异常)
-
异步异常统一延迟捕获传递
- 在没有用户自定义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
相关文章:

.NET 9中的异常处理性能提升分析:为什么过去慢,未来快
一、为什么要关注.NET异常处理的性能 随着现代云原生、高并发、分布式场景的大量普及,异常处理(Exception Handling)早已不再只是一个冷僻的代码路径。在高复杂度的微服务、网络服务、异步编程环境下,服务依赖的外部资源往往不可…...

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

计算机网络第2章(下):物理层传输介质与核心设备全面解析
目录 一、传输介质1.1 传输介质的分类1.2 导向型传输介质1.2.1 双绞线(Twisted Pair)1.2.2 同轴电缆(Coaxial Cable)1.2.3 光纤(Optical Fiber)1.2.4 以太网对有线传输介质的命名规则 1.3 非导向型传输介质…...
Qt Creator 11.0创建ROS2 Humble工程
Qt Creator 11.0创建ROS2 Humble项目工程 安装ROSProjectManager插件创建ROS2项目在src下添加packagegit clone ROS2功能包编译运行安装ROSProjectManager插件 安装ROSProjectManager的主要流程参考官方的流程,地址(ros_qtc_plugin)。 此处采用二进制安装: sudo apt inst…...

C# 类和继承(扩展方法)
扩展方法 在迄今为止的内容中,你看到的每个方法都和声明它的类关联。扩展方法特性扩展了这个边 界,允许编写的方法和声明它的类之外的类关联。 想知道如何使用这个特性,请看下面的代码。它包含类MyData,该类存储3个double类型 的…...
机器学习复习3--模型的选择
选择合适的机器学习模型是机器学习项目成功的关键一步。这通常不是一个一蹴而就的过程,而是需要综合考虑多个因素,并进行实验和评估。 1. 理解问题本质 这是模型选择的首要步骤。需要清晰地定义试图解决的问题类型: 监督学习 : 数据集包含…...

MySQL复杂SQL(多表联查/子查询)详细讲解
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 MySQL复杂SQL(多表联查/子查询&a…...

STM32使用土壤湿度传感器
1.1 介绍: 土壤湿度传感器是一种传感装置,主要用于检测土壤湿度的大小,并广泛应用于汽车自动刮水系统、智能灯光系统和智能天窗系统等。传感器采用优质FR-04双料,大面积5.0 * 4.0厘米,镀镍处理面。 它具有抗氧化&…...
在C++中,头文件(.h或.hpp)的标准写法
目录 1.头文件保护(Include Guards)2.包含必要的标准库头文件3.前向声明(Forward Declarations)4.命名空间5.注释示例1:基础头文件示例2:包含模板和内联函数的头文件示例3:C11风格的枚举类头文件…...
Axios学习笔记
Axios简介 axios前端异步请求库类似JQuery ajax技术, ajax用来在页面发起异步请求到后端服务,并将后端服务响应数据渲染到页面上, jquery推荐ajax技术,但vue里面并不推荐在使用jquery框架,vue推荐使用axios异步请求库。…...
Langchain学习笔记(十一):Chain构建与组合技巧
注:本文是Langchain框架的学习笔记;不是教程!不是教程!内容可能有所疏漏,欢迎交流指正。后续将持续更新学习笔记,分享我的学习心得和实践经验。 前言 在LangChain的发展过程中,API设计经历了重…...
【判断既约分数】2022-4-3
缘由既约分数,除了辗转相除法-编程语言-CSDN问答 void 判断既约分数() {int a 1, b 2020, aa b, y 2, gs 0;while (aa){while (a < b){while (y < a && y < aa)if (a%y 0 && aa%y 0)a, y 2;elsey;if (a < b)gs; else;a, y 2;…...

Windows平台RTSP/RTMP播放器C#接入详解
大牛直播SDK在Windows平台下的RTSP、RTMP播放器模块,基于自研高性能内核,具备极高的稳定性与行业领先的超低延迟表现。相比传统基于FFmpeg或VLC的播放器实现,SmartPlayer不仅支持RTSP TCP/UDP自动切换、401鉴权、断网重连等网络复杂场景自适应…...
深圳SMT贴片工艺优化关键步骤
内容概要 深圳SMT贴片工艺优化作为现代电子制造的核心环节,聚焦于提升生产精度与稳定性。其技术框架围绕三大核心维度展开:温度动态调控、设备协同适配与工艺缺陷预判。通过精密温度曲线控制系统,实现回流焊环节的热能梯度精准匹配ÿ…...

从 JDK 8 到 JDK 17:Swagger 升级迁移指南
点击上方“程序猿技术大咖”,关注并选择“设为星标” 回复“加群”获取入群讨论资格! 随着 Java 生态向 JDK 17 及 Jakarta EE 的演进,许多项目面临从 JDK 8 升级的挑战,其中 Swagger(API 文档工具)的兼容性…...
配置git命令缩写
以下是 Git 命令缩写的配置方法及常用方案,适用于 Linux/macOS/Windows 系统: 🔧 一、配置方法 1. 命令行设置(推荐) # 基础命令缩写 git config --global alias.st status git config --global alias.co che…...
Redis 缓存问题及其解决方案
1. 缓存雪崩 概念:缓存雪崩是指在缓存层出现大范围缓存失效或缓存服务器宕机的情况下,大量请求直接打到数据库,导致数据库压力骤增,甚至可能引发数据库宕机。 影响:缓存雪崩会导致系统性能急剧下降,甚至导…...

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

【发布实录】云原生+AI,助力企业全球化业务创新
5 月 22 日,在最新一期阿里云「飞天发布时刻」,阿里云云原生应用平台产品负责人李国强重磅揭晓面向 AI 场景的云原生产品体系升级,通过弹性智能的一体化架构、开箱即用的云原生 AI 能力,为中国企业出海提供新一代技术引擎。 发布会…...
vue中的派发事件与广播事件,及广播事件应用于哪些场景和一个表单验证例子
在 Vue 2.X 中,$dispatch 和 $broadcast 方法已经被废弃。官方认为基于组件树结构的事件流方式难以理解,并且在组件结构扩展时容易变得脆弱。因此,Vue 2.X 推荐使用其他方式来实现组件间的通信,例如通过 $emit 和 $on 方法&#x…...
DeepSeek 赋能智能养老:情感陪伴机器人的温暖革新
目录 一、引言二、智能养老情感陪伴机器人的市场现状与需求2.1 市场现状2.2 老年人情感陪伴需求分析 三、DeepSeek 技术详解3.1 DeepSeek 的技术特点3.2 与其他类似技术的对比优势 四、DeepSeek 在智能养老情感陪伴机器人中的具体应用4.1 自然语言处理与对话交互4.2 情感识别与…...

LabVIEW主轴故障诊断案例
LabVIEW 开发主轴机械状态识别与故障诊断系统,适配工业场景主轴振动监测需求。通过整合品牌硬件与软件算法,实现从信号采集到故障定位的全流程自动化,为设备维护提供数据支撑,提升数控机床运行可靠性。 面向精密制造企业数控机…...
gRPC 的四种通信模式完整示例
gRPC 的四种基本通信模式,包括完整的 .proto 文件定义和 Go 语言实现代码: 1. 简单 RPC (Unary RPC) - 请求/响应模式 客户端发送单个请求,服务端返回单个响应 calculator.proto protobuf syntax "proto3";package calculato…...
C#中Struct与IntPtr转换:实用扩展方法
C#中Struct与IntPtr转换:实用扩展方法 在 C# 编程的世界里,我们常常会遇到需要与非托管代码交互,或者进行一些底层内存操作的场景。这时,IntPtr类型就显得尤为重要,它可以表示一个指针或句柄,用来指向非托…...
Web安全:XSS、CSRF等常见漏洞及防御措施
Web安全:XSS、CSRF等常见漏洞及防御措施 一、XSS(跨站脚本攻击) 定义与原理 XSS攻击指攻击者将恶意脚本(如JavaScript、HTML标签)注入到Web页面中,当用户访问该页面时,脚本在浏览器端执行&…...
Java基础之数组(附带Comparator)
文章目录 基础概念可变参数组数组与ListComparator类1,基本概念2,使用Comparator的静态方法(Java 8)3,常用Comparator方法4,例子 排序与查找数组复制其他 基础概念 int[] anArray new int[10];只有创建对象时才会使用new关键字,所以数组是个…...

计算机组成与体系结构:补码数制二(Complementary Number Systems)
目录 4位二进制的减法 补码系统 🧠减基补码 名字解释: 减基补码有什么用? 计算方法 ❓为什么这样就能计算减基补码 💡 原理揭示:按位减法,模拟总减法! 那对于二进制呢?&…...

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

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

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