溫故之.NET效能分析

JameLee發表於2018-06-20

此文包含以下內容

  • 執行時分析
  • 效能計數器
  • 其他效能分析工具

執行時分析

使用perfmon.exe跟蹤效能,即效能監視器(Performance Monitor)

它是Windows自帶的一款分析.NET應用程式的工具,它以圖形的方式來表示從記憶體管理到JIT效能的方方面面。通過它,我們也可以知道我們應用程式所使用的資源的情況。

通過 Win + R 的方式,喚出命令提示符工具。在輸入框中輸入 perfmon.exe(在Windows10裡面,如果在開始選單出輸入,一定要輸入全部字元,否則敲回車時可能執行的是系統推薦的應用),然後回車即可,如下:

Win + R

回車後:

效能監視器
然後,點選左側欄中【監視工具】下的【效能監視器】, 得到如下圖形:

效能監視器

點選右側皮膚中的“綠色加號”,以新增計數器,如下

新增計數器

可以看到,.NET Framework 應用程式有許多預定義的物件,包括用於記憶體管理(.NET CLR Memory)、互操作性(.NET CLR Interop)、異常處理(.NET CLR Exceptions)以及多執行緒處理(.NET CLR LocksAndThreads)的計數器等多種工具。

點選列表中專案右邊的“向下箭頭”,可以檢視該效能物件能夠支援的計數器

單擊要檢視的效能計數器。

在《選定物件的例項》列表框中,單擊<所有例項>,指定要在全域性(也就是在整個系統範圍內)監視CLR的效能計數器。也可以在《選定物件的例項》列表框中,單擊要監視該應用程式的效能計數器的應用程式的名稱。

選擇了物件例項之後,點選對話方塊右下方的【確定】按鈕,既可以完成新增。

步驟如下圖:

步驟

以程式設計方式讀取和建立效能計數器

除了以上通過其他工具的方式外,我們也可以在某些情況下(比如只需要測試某一段程式碼的效能的時候)使用程式碼的方式來處理,這種情況下,用程式碼或許更加高效。

.NET 為我們提供了以下類(名稱空間System.Diagnostics)來做這些事情

  • PerformanceCounter:表示 Windows NT 效能計數器元件。使用此類可讀取現有的計數器,或自定義更合適的計數器。也可以向自定義計數器寫入效能資料
  • PerformanceCounterCategory: 提供與計數器互動的一些方法
  • PerformanceCounterInstaller:用於指定 PerformanceCounter 元件的安裝程式
  • PerformanceCounterType:指定PerformanceCounter計算下一個值(NextValue)使用的公式

示例程式碼如下:

using System;
using System.Collections;
using System.Diagnostics;

public class App {
    private static PerformanceCounter avgCounter64Sample;
    private static PerformanceCounter avgCounter64SampleBase;

    public static void Main() {
        ArrayList samplesList = new ArrayList();

        /// 用於建立目錄/類別
        /// 如果不存在,則需要建立,此時不能立即建立計數器,需要過一會兒才能使用
        if (SetupCategory())
            return;
        CreateCounters();
        CollectSamples(samplesList);
        CalculateResults(samplesList);
    }

    private static bool SetupCategory() {
        if (!PerformanceCounterCategory.Exists("AverageCounter64SampleCategory")) {
            CounterCreationDataCollection counterDataCollection = new CounterCreationDataCollection();
            // 為目錄新增計數器
            CounterCreationData averageCount64 = new CounterCreationData {
                CounterType = PerformanceCounterType.AverageCount64,
                CounterName = "AverageCounter64Sample"
            };
            counterDataCollection.Add(averageCount64);
            CounterCreationData averageCount64Base = new CounterCreationData {
                CounterType = PerformanceCounterType.AverageBase,
                CounterName = "AverageCounter64SampleBase"
            };
            counterDataCollection.Add(averageCount64Base);

            // 建立目錄
            PerformanceCounterCategory.Create("AverageCounter64SampleCategory",
                "Demonstrates usage of the AverageCounter64 performance counter type.",
                PerformanceCounterCategoryType.SingleInstance, counterDataCollection);

            return true;
        } else {
            // 目錄已經存在,因此我們可以進行後續步驟
            return false;
        }
    }

    private static void CreateCounters() {
        // 建立計數器
        avgCounter64Sample = new PerformanceCounter("AverageCounter64SampleCategory", "AverageCounter64Sample", false);
        avgCounter64SampleBase = new PerformanceCounter("AverageCounter64SampleCategory", "AverageCounter64SampleBase", false);
        avgCounter64Sample.RawValue = 0;
        avgCounter64SampleBase.RawValue = 0;
    }
    private static void CollectSamples(ArrayList samplesList) {
        Random r = new Random(DateTime.Now.Millisecond);
        for (int j = 0; j < 100; j++) {
            int value = r.Next(1, 10);
            Console.Write(j + " = " + value);

            avgCounter64Sample.IncrementBy(value);
            avgCounter64SampleBase.Increment();

            if ((j % 10) == 9) {
                OutputSample(avgCounter64Sample.NextSample());
                samplesList.Add(avgCounter64Sample.NextSample());
            } else
                Console.WriteLine();

            System.Threading.Thread.Sleep(50);
        }
    }

    private static void CalculateResults(ArrayList samplesList) {
        for (int i = 0; i < (samplesList.Count - 1); i++) {
            // 輸出樣本資訊
            OutputSample((CounterSample)samplesList[i]);
            OutputSample((CounterSample)samplesList[i + 1]);

            // 通過.NET自帶方法,計算計數器的值
            float counterValue = CounterSampleCalculator.ComputeCounterValue((CounterSample)samplesList[i], (CounterSample)samplesList[i + 1]);
            Console.WriteLine($".NET computed counter value = {counterValue}");
        }
    }
    
    private static void OutputSample(CounterSample s) {
        Console.WriteLine("\r\nSample values - \r\n");
        Console.WriteLine($"   BaseValue        = {s.BaseValue}");
        Console.WriteLine($"   CounterFrequency = {s.CounterFrequency}");
        Console.WriteLine($"   CounterTimeStamp = {s.CounterTimeStamp}");
        Console.WriteLine($"   CounterType      = {s.CounterType}");
        Console.WriteLine($"   RawValue         = {s.RawValue}");
        Console.WriteLine($"   SystemFrequency  = {s.SystemFrequency}");
        Console.WriteLine($"   TimeStamp        = {s.TimeStamp}");
        Console.WriteLine($"   TimeStamp100nSec = {s.TimeStamp100nSec}\r\n");
    }
}
複製程式碼

效能計數器

這個小節將對這個計數器列表中很常用的計數器進行簡要說明

計數器

包括以下計數器:

  • 異常效能計數器
  • 互操作效能計數器
  • JIT 效能計數器
  • 載入效能計數器
  • 鎖定和執行緒效能計數器
  • 記憶體效能計數器
  • 聯網效能計數器
  • 安全效能計數器

異常效能計數器

.NET CLR Exceptions類別包含的計數器提供應用程式引發的異常的相關資訊

  • 引發的異常數: 從應用程式啟動以來引發的異常總數。此數值包括.NET異常和轉換為.NET異常的非託管異常(例如,從非託管程式碼返回的HRESULT在託管程式碼中會被轉換為異常)。
  • 引發的異常數/秒: 顯示每秒引發的異常數,它包括已處理和未經處理的異常。此計數器是一個潛在效能問題(如果引發較多數目 >100 的異常)的指示器。
  • 篩選次數/秒:顯示每秒執行的 .NET 異常篩選次數。
  • Finally 數量/秒:顯示每秒執行的 finally 塊的數量。特別注意的是,此計數器只計算有異常執行的 finally 塊(即丟擲異常之後),不計算正常程式碼路徑上的 finally 塊。
  • 捕獲的深度/秒:顯示從引發異常的幀到處理該異常的幀每秒遍歷的堆疊幀數。

互操作效能計數器

.NET CLR Interop(互操作)類別包括的計數器提供應用程式與COM元件、COM+服務和外部型別庫互動的相關資訊

  • CCW 數目:它顯示非託管 COM 程式碼所引用的託管物件數。CCW 是指正在從非託管 COM 客戶端引用的託管物件的代理
  • 封送處理次數:顯示從應用程式啟動以來,將引數和返回值從託管程式碼封送至非託管程式碼(反之亦然)的總次數
  • 存根數:顯示由公共語言執行時建立的當前存根數。存根負責在 COM 互操作呼叫或P-Invoke 呼叫期間將引數和返回值從託管程式碼封送至非託管程式碼(或反之)

JIT 效能計數器

.NET CLR JIT 類別包括的計數器提供的由JIT編譯相關資訊

  • JIT編譯的IL位元組數:從應用程式啟動以來,由實時 (JIT) 編譯器編譯的微軟中間語言 (MSIL) 位元組總數
  • JIT編譯的方法數:從應用程式啟動以來JIT 編譯的方法總數。此計數器不包括預先進行JIT` 編譯的方法
  • JIT 所佔時間百分比:顯示自上次JIT編譯階段以來JIT編譯所用執行佔用時間的百分比
  • JIT每秒編譯的IL位元組數:顯示每秒JIT編譯的MSIL位元組數
  • JIT失敗數:自應用程式啟動以來JIT編譯器編譯失敗的方法的高峰數量

載入效能計數器

.NET CLR Loading(載入)類別包括的計數器提供已載入的程式集、類和應用程式域的相關資訊

  • 載入程式堆中的位元組數:所有應用程式域中類載入程式當前提交記憶體大小(以位元組為單位)
  • 當前 AppDomain:應用程式中載入的應用程式域數量
  • 當前程式集數:當前執行的應用程式中所有應用程式域範圍內已載入的程式集數量。如果程式集以非特定於域的形式從多個應用程式域中載入,則此計數器只遞增一次
  • 當前已載入的類:所有程式集中已載入類的數量
  • AppDomain 速率:每秒載入的應用程式域的數量
  • 解除安裝 AppDomain 的速率:每秒解除安裝的應用程式域的數量
  • 程式集速率:所有應用程式域範圍內每秒載入的程式集數量。如果程式集以非特定於域的形式從多個應用程式域中載入,則此計數器只遞增一次
  • 載入類的速率:所有程式集中每秒載入的類的數量
  • 載入失敗的速率:每秒載入失敗的類的數量。載入失敗的原因有很多,例如安全性不夠或格式無效等等
  • 載入失敗總數:自應用程式啟動以來載入失敗的類的峰值
  • AppDomain 總數:自應用程式啟動以來已載入的應用程式域的峰值。
  • 解除安裝的 AppDomain 總數:顯示自應用程式啟動以來解除安裝的應用程式的峰值。如果應用程式域載入和解除安裝多次,則此計數器將在每次解除安裝應用程式域時遞增。
  • 程式集總數:自應用程式啟動以來載入的程式集總數
  • 已載入的類總數:自應用程式啟動以來所有程式集中載入的類的累計數量。

鎖定和執行緒效能計數器

.NET CLR LocksAndThreads(鎖和執行緒)類別包括的計數器提供應用程式所使用的託管鎖和託管執行緒的相關資訊

  • 當前邏輯執行緒數目:應用程式中當前託管的執行緒數。此計數器包括將正在執行和已停止的執行緒
  • 當前物理執行緒數目CLR建立和擁有的,用作託管執行緒的基礎執行緒,的本機作業系統執行緒數。此計數器的值不包括CLR在其內部操作中使用的執行緒;它是作業系統程式中執行緒的子集。
  • 當前已識別的執行緒數目:當前已由CLR識別的執行緒數。這些執行緒與對應的託管執行緒物件相關聯。CLR不建立這些執行緒,但它們在CLR內至少會執行一次。具有相同執行緒 ID 的執行緒不會進行兩次計數
  • 已識別的執行緒總數:自應用程式啟動以來已由CLR識別的執行緒總數
  • 競爭率/秒:執行時中執行緒嘗試獲取託管鎖的失敗率。
  • 當前佇列長度:當前正在等待獲取應用程式託管鎖的執行緒總數
  • 佇列長度/秒:每秒內等待獲取應用程式鎖的執行緒數目
  • 佇列長度峰值:自應用程式啟動以來等待獲取託管鎖的執行緒總數峰值。
  • 已識別執行緒的速率/秒:每秒內由CLR識別的執行緒數
  • 爭用總數:執行中執行緒嘗試獲取託管鎖失敗的總次數

記憶體效能計數器

.NET CLR Memory(記憶體)類別包括的計數器提供GC的相關資訊

  • 所有堆中的位元組數:第 1 代堆、第 2 代堆和大型物件堆的總和,即垃圾回收堆上分配的當前記憶體(以位元組為單位)
  • GC控制程式碼數:正在使用的垃圾回收控制程式碼的數量。垃圾回收控制程式碼:CLR和託管環境外部的資源的控制程式碼
  • 0代回收次數:自應用程式啟動以來第0代物件(即最年輕、最近分配的物件)進行垃圾回收的次數
  • 1 代回收次數:自應用程式啟動以來第 1 代物件進行垃圾回收的次數
  • 2 代回收次數:自應用程式啟動以來第 2 代物件進行垃圾回收的次數
  • 已引發 GC:因顯式呼叫 GC.Collect 而執行的垃圾回收次數峰值
  • 固定物件數目:在上一次垃圾回收中遇到的固定物件(垃圾回收器不能移入記憶體的物件)的數目
  • 正在使用的同步塊數目:正在使用的同步塊的數量。 同步塊是用於儲存同步資訊而為每個物件分配的資料結構,但它不限於只儲存同步資訊,也可以儲存 COM 互操作後設資料
  • 已提交的位元組總數GC當前已提交的虛擬記憶體總量(以位元組為單位)
  • 已保留的位元組總數GC當前保留的虛擬記憶體量(以位元組為單位)
  • GC 所佔時間百分比:執行上次垃圾回收與執行垃圾回收所用時間的百分比。此計數器通常指示GC代表應用程式收集和壓縮記憶體所執行的作業
  • 分配的位元組數/秒:堆上每秒分配的位元組數。注意,此計數器在每次垃圾回收結束時(而非每次分配時)更新
  • 最終存活物件:在終結(Finalize)任務後,仍然存在垃圾回收物件數。 如果這些物件具有對其他物件的引用,則那些物件也會存在,但是不計入此計數器內
  • 0 代堆大小:第 0 代中可以分配的最大位元組數;但它不能確定第 0 代中已分配的位元組數
  • 0 代提升的位元組數/秒:每秒從第 0 代提升到第 1 代的位元組數
  • 1 代堆大小:第 1 代中的當前位元組數(切記,此代中的物件不是直接分配的;這些物件是從以前的第 0 代垃圾回收提升的)
  • 1 代提升的位元組數/秒:每秒從第 1 代提升到第 2 代的位元組數
  • 2 代堆大小:第 2 代中的當前位元組數,不包括大物件,切記:此代中的物件(不包括大物件)不是直接分配的
  • 大型物件堆大小:大型物件堆的當前大小。垃圾回收器將大於 85000 位元組左右的物件視作大物件並且直接在大物件堆中分配;它們不按照級別來提升
  • 程式 ID:顯示被監視的 CLR 程式例項的程式 ID。
  • 從第 0 代提升的終止記憶體:由於等待終結而從第 0 代提升到第 1 代的記憶體位元組數
  • 從第 0 代提升的記憶體:垃圾回收後仍存在並從第 0 代提升到第 1 代的記憶體位元組數
  • 從第 1 代提升的記憶體:顯示垃圾回收後仍存在並從第 1 代提升到第 2 代的記憶體位元組數

聯網效能計數器

.NET CLR Networking(網路)類別包括的計數器提供應用程式通過網路傳送和接收的資料的相關資訊

  • 已接收的位元組數:自程式啟動以來,AppDomain 中的所有 Socket 物件接收到的位元組的總數。此資料包括未定義的任何協議資訊的TCP/IP資料
  • 已傳送的位元組數:自程式啟動以來,AppDomain 中的所有 Socket 物件已傳送的位元組的累積總數。此資料包括未定義的任何協議資訊的TCP/IP資料
  • 已建立的連線:自程式啟動以來,AppDomain 中已經連線的 Socket 物件的累積總數
  • 已接收的資料包:自程式啟動以來,AppDomain 中的所有 Socket 物件接收到的資料包包的總數
  • 已傳送的資料包:自程式啟動以來,AppDomain 中的所有 Socket 物件已傳送的資料包包的總數
  • HttpWebRequest 平均生存期:自程式啟動以來,AppDomain 中在上一個間隔中結束的所有HttpWebRequest物件的平均時間
  • HttpWebRequest 平均排隊時間:自程式啟動以來,AppDomain 中在上一個間隔中結束的所有 HttpWebRequest 物件的平均排隊時間
  • 建立的 HttpWebRequest/秒AppDomain 中每秒建立的 HttpWebRequest 物件的數目
  • 已排隊的 HttpWebRequest/秒AppDomain 中每秒新增到佇列的 HttpWebRequest 物件的數量
  • 已中止的 HttpWebRequest/秒AppDomain 中應用程式每秒呼叫 Abort 方法的 HttpWebRequest 物件的數量。
  • 失敗的 HttpWebRequest/秒AppDomain 中每秒從伺服器接收失敗狀態碼的 HttpWebRequest 物件的數量。

其他的網路效能計數器,如:

  • 事件計數器:用於測量某些事件的發生次數
  • 資料計數器:用於測量已傳送或已接收的資料量
  • 持續時間計數器:測量不同程式花費的時間。測量物件每個間隔(通常以秒計)退出不同狀態後的次數
  • 每間隔計數器:用於測量每個間隔(通常以秒計)中正在進行特定轉換的物件數

網路效能計數器包含在兩個類別中:

  • .NET CLR 網路:.NET Framework 2 上引入且在 .NET Framework 2 及更高版本上受支援的原始效能計數器。
  • .NET CLR 網路 4:所有上述套接計數器和 .NET Framework 4 及更高版本上受支援的新的效能計數器

安全效能計數器

.NET CLR Security(安全性)類別包括的計數器提供公共語言執行時針對應用程式執行的安全檢查的相關資訊

  • 連結時檢查次數:自應用程式啟動以來連結時程式碼(link-time code)訪問安全檢查的總次數。 當呼叫方要求實時 (JIT) 編譯時的特定許可權時,執行連結時程式碼訪問安全檢查
  • RT 檢查所佔的時間百分比:自上一次取樣以來執行執行時程式碼訪問安全檢查所用執行時間的百分比
  • 堆疊稽核深度:在上次執行時程式碼訪問安全檢查期間的堆疊深度
  • 執行時檢查總數:自應用程式啟動以來執行的執行時程式碼訪問安全檢查的總數。當呼叫方要求特定許可權時,執行執行時程式碼訪問安全檢查。 執行時檢查在呼叫方每次呼叫時都會執行,並會檢查呼叫方的當前執行緒堆疊。 此計數器與“堆疊審閱深度”計數器一起使用時可指示安全檢查出現的效能損失。

其他效能分析工具

  1. JetBrains dotTrace:它可以幫助你優化應用程式效能指標,支援.NET1.0版本到4.5,快速分析程式瓶頸,找出影響效率的程式碼。官方網站上有10天試用版
  2. ANTS Performance ProfilerANTS效能分析器是一種用於分析.NET框架支援的用任何語言編寫的應用程式的工具。ANTS效能分析器能分析所有.NET應用程式,包括ASP.NET網路應用程式、Windows服務和COM+應用程式。ANTS效能分析器能在幾分鐘內識別效能瓶頸,執行非常快速,且響應時,對程式的執行具有最低影響。
  3. NET Memory Profiler:一款非常深入分析.NET記憶體的優化工具,快速發現記憶體洩漏問題,並且自動進行記憶體檢測
  4. VS自帶的效能分析工具:如圖
    Analyze
    點選之後,如下圖
    Analyze Options
    可以看到,由CPUGPU、記憶體及效能嚮導四個選項,選擇您想要進行的效能分析,點選【Start/開始】就可以了

至此,本節內容講解完畢。歡迎關注公眾號【嘿嘿的學習日記】,所有的文章,都會在公眾號首發,Thank you~

公眾號二維碼

相關文章