LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

.NET 9中的異常處理性能提升分析:為什么過去慢,未來快

freeflydom
2025年6月6日 10:17 本文熱度 58

一、為什么要關注.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系統的極限性能,無論架構設計還是細節實現都欠缺優化,反映在:

  • 內部優先保證兼容性和健壯性,而不是高性能
  • 代碼中凡是熱路徑,都讓開發者“自覺避開異?!?/li>

近年來,現代服務常常:

  • 依賴于“不可靠資源” (如網絡、外部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分支。多線程下,本地堆?;ゲ魂P聯,所有棧回溯、元數據查找都需走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上。主要技術變更包括:

  1. 將異常展開、catch/finally分派等環節全部搬到托管主流程
  2. native helper只做最小的stack frame展開,與垃圾回收棧遍歷接口復用(易于維護)
  3. 強化托管級緩存與元數據管理。關鍵鏈表遍歷全部升級成緩存/高速哈希表,一舉解決了多線程、深棧、頻繁異常場景下的scalability困境
  4. 釘死所有多余的C++ throw/catch——對Unix/Windows都生效
  5. 為Async/Await生成優化代碼路徑,避免多次重復拋出/捕獲

工程落地與效果

  • 性能測試實測,異常處理耗時降幅約76%~80%,多線程/高并發效果更好
  • 性能剖析熱點:主要耗時已縮小到stack walker和關鍵數據結構哈希效率上,其他已近極致
  • 全平臺統一,無歷史特殊兼容路徑、包袱

真實圖片示例

  • 單線程性能提升圖:

  • 多線程性能提升圖:


(三)可進一步優化的場景與細節

  1. 熱點分支profile(PGO)

    • 異常的“常用路徑”可被profile,按pgo機制熱路徑內聯/重編排邏輯
    • 比如async await狀態機里常拋異常的分支inline獲得最佳cache局部性
  2. Unwind Section緩存/優化

  3. 雙檢省棧trace與細粒度采集

    • 支持僅按需采集stacktrace(避免捕獲所有調試信息)
  4. 特殊場景快速捕獲(業務異常/操作性異常)

    • 通過拓展托管catch塊類型,可以極簡分為業務異常與系統異常,實現“無棧捕獲”,加速高頻捕獲型異常(如EndOfData、ParseError等流控制型異常)
  5. 異步異常統一延遲捕獲傳遞

    • 在沒有用戶自定義try塊的async方法中,捕獲異常僅保存,真正拋出延遲到非異常主流程結束前即可。這將極大降低狀態機驅動的拋出/捕獲次數。

六、總結展望

.NET 9通過徹底擁抱NativeAOT極簡式的托管異常處理體系,把歷史包袱(OS-Specific/C++ Exception Bridge/冗余鏈表&鎖/多次catch-rethrow)一舉清除,大幅釋放了異常路徑的性能潛力。這一變革支撐了.NET在微服務、云原生、異步并發等新主流場景下的頂級運行時表現。未來,隨著堆棧展開、元數據cache自適應等不斷迭代,.NET有望成為托管平臺的異常處理性能“天花板”。

轉自https://www.cnblogs.com/InCerry/p/-/dotnet-9-exception-pref-improve


該文章在 2025/6/6 10:18:35 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
中文字幕不卡高清视频在线 | 欧亚一区二区三区在线看日韩 | 又紧又爽精品一区二区 | 亚洲日本337视频大全 | 亚洲欧美中日韩中文字幕 | 中文字幕在线观看日韩少妇 |