記一次 .NET 某藥廠業務系統 CPU爆高分析

来源:https://www.cnblogs.com/huangxincheng/archive/2023/12/20/17916751.html
-Advertisement-
Play Games

一:背景 1. 講故事 前段時間有位朋友找到我,說他們的程式出現了CPU爆高,讓我幫忙看下怎麼回事?這種問題好的辦法就是抓個dump丟給我,推薦的工具就是用 procdump 自動化抓捕。 二:Windbg 分析 1. CPU 真的爆高嗎 還是老規矩,要想找到這個答案,可以使用 !tp 命令。 0: ...


一:背景

1. 講故事

前段時間有位朋友找到我,說他們的程式出現了CPU爆高,讓我幫忙看下怎麼回事?這種問題好的辦法就是抓個dump丟給我,推薦的工具就是用 procdump 自動化抓捕。

二:Windbg 分析

1. CPU 真的爆高嗎

還是老規矩,要想找到這個答案,可以使用 !tp 命令。


0:044> !tp
logStart: 1
logSize: 200
CPU utilization: 88 %
Worker Thread: Total: 8 Running: 4 Idle: 4 MaxLimit: 1023 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 2
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 8 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 4

從卦中數據看當前cpu確實達到了 88%,接下來我們觀察下這個程式的機器cpu是否給力,可以用 !cpuid 觀察。


0:044> !cpuid
CP  F/M/S  Manufacturer     MHz
 0  6,94,3  GenuineIntel    3192
 1  6,94,3  GenuineIntel    3192
 2  6,94,3  GenuineIntel    3192
 3  6,94,3  GenuineIntel    3192

從卦中看,尼瑪也就4core,有點弱哈,好歹也是一個高利潤的藥廠,這麼摳門哈。

2. 為什麼會CPU爆高

導致 CPU 爆高的因素有很多,沒有標準答案,需要自己去找原因,首先我們觀察下這個程式的線程數量,可以使用 !t 命令即可。


0:044> !t
ThreadCount:      451
UnstartedThread:  0
BackgroundThread: 443
PendingThread:    0
DeadThread:       1
Hosted Runtime:   no
                                                                             Lock  
 DBG   ID     OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1     22b8 04CE8728     26020 Preemptive  18E5C92C:18E5E4DC 04c86c20 -00001 STA 
   3    2     17c8 04B25768     2b220 Preemptive  18CAF3A0:18CB1374 04c86c20 -00001 MTA (Finalizer) 
   4    4     238c 04C0CDD8   202b020 Preemptive  18E45D88:18E464DC 04c86c20 -00001 MTA 
   5    5     230c 0A6C37A0   202b020 Preemptive  18DAC318:18DAC47C 04c86c20 -00001 MTA 
   6    6     23a0 0A70E620   202b220 Preemptive  00000000:00000000 04c86c20 -00001 MTA 
   ...

從卦中數據看,當前有 451 個線程,其中後臺線程是 443 個,再結合剛纔的 !tp 看到的線程池線程也才 8 個,這就說明這個程式中有 400+ 的線程是直接通過 new Thread 創建的,這個信息就比較可疑了,為啥不用線程池用 Thread ,有蹊蹺。

接下來的思路就是使用 ~*e !clrstack 命令觀察下每個線程此時都在做什麼,命令一輸入,刷了好久。


0:044> ~*e !clrstack
...
OS Thread Id: 0x220c (18)
Child SP       IP Call Site
184CF614 77dd19dc [HelperMethodFrame: 184cf614] System.Threading.Thread.SleepInternal(Int32)
184CF680 141975f4 System.Threading.Thread.Sleep(Int32) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 357]
184CF694 165055b9 xxx.ActionThread`1[[xxx]].Loop()
184CF878 74467741 System.Threading.Thread+StartHelper.Callback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 42]
184CF888 7446fca1 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
184CF8C0 74466742 System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
184CFA14 74cbc29f [DebuggerU2MCatchHandlerFrame: 184cfa14] 
...

在卦中的各個線程棧上也沒有看到什麼特別明顯的業務函數,大多都是停在 Thread.SleepInternal 上進行等待,這就讓我陷入了迷茫。

3. 一朝頓悟,走出迷茫

CPU不可能無緣無故的爆高,總會是那些線程給抬起來的,但這個程式中的線程大多都在 Thread.SleepInternal 上,若說他們能把 CPU 弄爆總有點說不過去。

但問題總得要解決,在無突破口的情況也只能硬著頭皮在 Thread.SleepInternal 上強行突破了,首先用 Ctrl+F 搜下有多少線程卡在 SleepInternal 上,截圖如下:

尼瑪,幾乎所有線程都在 Sleep,一般來說有這麼多線程都在 Sleep 也是少數,接下來抽一個線程看看業務方法是怎麼進行 Sleep 的,參考代碼如下:

在這個Loop方法中我發現有很多的 Sleep(1),看到這個我突然想到了高頻的上下文切換導致的 CPU 爆高。

接下來這個代碼的指令到底停在哪個方法呢?可以反編譯 Loop 方法。


0:047> !clrstack
OS Thread Id: 0xad8 (47)
Child SP       IP Call Site
20B5F434 77dd19dc [HelperMethodFrame: 20b5f434] System.Threading.Thread.SleepInternal(Int32)
20B5F4A0 141975f4 System.Threading.Thread.Sleep(Int32) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 357]
20B5F4B4 1f123c71 xxx.ActionThread`1[[xxx].Loop()
20B5F698 74467741 System.Threading.Thread+StartHelper.Callback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 42]
20B5F6A8 1baab7da System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
20B5F6E0 74466742 System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
20B5F834 74cbc29f [DebuggerU2MCatchHandlerFrame: 20b5f834] 
0:047> !U /d 1f123c71
Normal JIT generated code
xxx.ActionThread`1[xxx].Loop()
ilAddr is 0A324040 pImport is 08AD6468
Begin 1F123C10, size abd
1f123c10 55              push    ebp
1f123c11 8bec            mov     ebp,esp
1f123c13 57              push    edi
1f123c14 56              push    esi
1f123c15 81ecd4010000    sub     esp,1D4h
1f123c1b c5f877          vzeroupper
1f123c1e c5d857e4        vxorps  xmm4,xmm4,xmm4
1f123c22 c5fa7fa524feffff vmovdqu xmmword ptr [ebp-1DCh],xmm4
1f123c2a c5fa7fa534feffff vmovdqu xmmword ptr [ebp-1CCh],xmm4
1f123c32 b850feffff      mov     eax,0FFFFFE50h
1f123c37 c5fa7f6405f4    vmovdqu xmmword ptr [ebp+eax-0Ch],xmm4
1f123c3d c5fa7f640504    vmovdqu xmmword ptr [ebp+eax+4],xmm4
1f123c43 c5fa7f640514    vmovdqu xmmword ptr [ebp+eax+14h],xmm4
1f123c49 83c030          add     eax,30h
...
1f123c5a e84115cc55      call    coreclr!JIT_DbgIsJustMyCode (74de51a0)
1f123c5f 90              nop
1f123c60 90              nop
1f123c61 e9300a0000      jmp     xxx.ActionThread<xxx>.Loop+0xa86 (1f124696)
1f123c66 90              nop
1f123c67 b901000000      mov     ecx,1
1f123c6c e87f54eaea      call    09fc90f0 (System.Threading.Thread.Sleep(Int32), mdToken: 06002D01)
>>> 1f123c71 90              nop
...

通過卦中的 >>> 可以確認很多的方法都是在 while (!base.IsTerminated) 中進行空轉,如果 Sleep(1) 的線程比較少那可能沒什麼問題,但也扛不住400多線程一起玩哈,最後高頻的上下文切換導致的 CPU 爆高。

在 Sleep(1) 內部會涉及到CPU的等待隊列,就緒隊列,以及定時器 _KTIMER 內核對象, 因為 Windows 源碼不公開,內部還是比較搞的,可以用 !pcr 命令觀察下 cpu的背包。


lkd> !pcr 0
KPCR for Processor 0 at fffff8058023c000:
    Major 1 Minor 1
	NtTib.ExceptionList: fffff80589089fb0
	    NtTib.StackBase: fffff80589088000
	   NtTib.StackLimit: 000000137e1fa158
	 NtTib.SubSystemTib: fffff8058023c000
	      NtTib.Version: 000000008023c180
	  NtTib.UserPointer: fffff8058023c870
	      NtTib.SelfTib: 000000137dfe0000

	            SelfPcr: 0000000000000000
	               Prcb: fffff8058023c180
	               Irql: 0000000000000000
                 ...

	      CurrentThread: ffff910c66906080
	         NextThread: 0000000000000000
	         IdleThread: fffff80583d27a00

	          DpcQueue: 

lkd> dt nt!_KPRCB fffff8058023c180
   +0x008 CurrentThread    : 0xffff910c`66906080 _KTHREAD
   +0x010 NextThread       : (null) 
   +0x018 IdleThread       : 0xfffff805`83d27a00 _KTHREAD
   ...
   +0x7c00 WaitListHead     : _LIST_ENTRY [ 0xffff910c`5ec30158 - 0xffff910c`628b1158 ]
   +0x7c80 DispatcherReadyListHead : [32] _LIST_ENTRY [ 0xfffff805`80243e00 - 0xfffff805`80243e00 ]

上面的[32]就是等待線程的32個優先順序的數組隊列。

有了上面的分析結果,最後就是告訴朋友做到如下兩點:

  • 減少 Thread.Sleep(1) 的線程參與數。
  • 儘量將 1 -> 50 來緩解,當然越大越好。

三:總結

這次CPU的爆高還是挺有意思,不是業務方法導致的爆高,而是大量的 Sleep(1) 導致的高頻上下文切換所致,有點意思,留下此文給大家避坑!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄前言QT小記1. 菜單欄、工具欄、狀態欄2. 自定義的對話框3. 任務管理器4. 鏈接資料庫mysql,sqlite5. Widgets Gallery Example 代碼學習:999.ControlsQT-For-Python1. DemoQT-Quick1. HelloWorld2. 簡單 ...
  • AVL樹 AVL樹是一種自平衡二叉搜索樹。在這種樹中,任何節點的兩個子樹的高度差被嚴格控制在1以內。這確保了樹的平衡,從而保證了搜索、插入和刪除操作的高效性。AVL樹是由Georgy Adelson-Velsky和Evgenii Landis在1962年發明的,因此得名(Adelson-Velsky ...
  • Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹`TabWidget`標簽組件的常用方法及靈活運用。`QTabWidget` 是Qt中用於實現標簽頁(tabbed... ...
  • JavaSE學習思維導圖 目錄1 Java語言概述1.1 Java 概述1.2 Java 語言簡史1.3 Java 之父1.4 Java 技術體系平臺2 Java 開發環境搭建2.1 JDK JRE2.2 JDK版本的選擇2.3 JDK 的下載2.4 JDK 的安裝2.5 配置path環境變數2.5 ...
  • 本文主要介紹了設計模式中的狀態模式,併在此基礎上介紹了Spring狀態機相關的概念,並根據常見的訂單流轉場景,介紹了Spring狀態機的使用方式。文中如有不當之處,歡迎在評論區批評指正。 ...
  • 優化內容 這篇不聊技術點,說一下優化後的Python機器人代碼怎麼使用,優化內容如下: 將hook庫獨立成一個庫,發佈到pypi,可使用pip安裝 將微信相關的代碼發佈成另一個庫,也可以pip安裝 git倉庫統一,以後都在這個倉庫更新,不再一篇文章一個倉庫 開始建群,根據群里反饋增加功能和修複bug ...
  • 數據的預處理是數據分析,或者機器學習訓練前的重要步驟。通過數據預處理,可以 提高數據質量,處理數據的缺失值、異常值和重覆值等問題,增加數據的準確性和可靠性 整合不同數據,數據的來源和結構可能多種多樣,分析和訓練前要整合成一個數據集 提高數據性能,對數據的值進行變換,規約等(比如無量綱化),讓演算法更加 ...
  • Qt 是一個跨平臺C++圖形界面開發庫,利用Qt可以快速開發跨平臺窗體應用程式,在Qt中我們可以通過拖拽的方式將不同組件放到指定的位置,實現圖形化開發極大的方便了開發效率,本章將重點介紹`QStyledItemDelegate`自定義代理組件的常用方法及靈活運用。在Qt中,`QStyledItemD... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...