首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

CodeTimer:高精度性能計數器 (CPU 週期/GC 代別統計/預熱/JIT 優化)

     編輯:關於.NET

Jeffrey Richter 在 .NET 技術大會演示的 **CodeTimer** 簡單而強大:測量執行時間、CPU 時鐘週期、各代 GC 回收次數。本文完整重構實現,含進程/線程優先級提升、預熱、P/Invoke 讀取線程週期計數器。

核心 API

public static class CodeTimer {
    public static void Initialize() { ... } // 提升優先級 + 預熱
    public static void Time(string name, int iteration, Action action) { ... } // 核心測量
}

完整實現代碼

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

public static class CodeTimer {
    public static void Initialize() {
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
        Time("", 1, () => { }); // 預熱 JIT
    }

    public static void Time(string name, int iteration, Action action) {
        if (string.IsNullOrEmpty(name)) return;

        // 1. 黃色輸出名稱
        ConsoleColor oldColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine(name);

        // 2. 強制 GC 基線
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        int[] gcCounts = new int[GC.MaxGeneration + 1];
        for (int i = 0; i <= GC.MaxGeneration; i++)
            gcCounts[i] = GC.CollectionCount(i);

        // 3. 執行測量
        Stopwatch sw = Stopwatch.StartNew();
        ulong startCycles = GetCycleCount();
        for (int i = 0; i < iteration; i++) action();
        ulong cpuCycles = GetCycleCount() - startCycles;
        sw.Stop();

        // 4. 恢復顏色 + 輸出時間/週期
        Console.ForegroundColor = oldColor;
        Console.WriteLine($"\tTime Elapsed:\t{sw.ElapsedMilliseconds:N0}ms");
        Console.WriteLine($"\tCPU Cycles:\t{cpuCycles:N0}");

        // 5. 輸出各代 GC 次數
        for (int i = 0; i <= GC.MaxGeneration; i++) {
            int count = GC.CollectionCount(i) - gcCounts[i];
            Console.WriteLine($"\tGen {i}:\t\t{count}");
        }
        Console.WriteLine();
    }

    // P/Invoke 讀取線程 CPU 週期 (Vista/Server 2008+)
    private static ulong GetCycleCount() {
        ulong cycles = 0;
        QueryThreadCycleTime(GetCurrentThread(), ref cycles);
        return cycles;
    }
    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime);
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentThread();
}

關鍵設計解析

環節目的關鍵點
Initialize 提升優先級減少 OS 調度干擾進程 High + 線程 Highest,需管理員權限最佳
預熱調用觸發 JIT 編譯首次 `Time` 會編譯 IL→Native,預熱使後續測量穩定
強制 GC 基線隔離測試代碼的 GC 影響測量前強制 Full GC,記錄各代計數器基值
CPU 週期計數輔助參考指標`QueryThreadCycleTime` 僅統計線程獲得 CPU 時間,**不含 Sleep/等待**
GC 代別統計直觀反映分配壓力Gen 0 頻繁 = 大量短命物件;Gen 2 = 大對象/長生命週期

實戰對比示例

1. Thread.Sleep vs 空方法:時間 vs 週期

CodeTimer.Initialize();
CodeTimer.Time("Thread Sleep", 1, () => Thread.Sleep(3000));
CodeTimer.Time("Empty Method", 10_000_000, () => { });

Sleep vs Empty

  • `Thread.Sleep(3000)`:Elapsed ~3000ms,**Cycles ≈ 0** (線程讓出 CPU)。
  • `Empty Method` 1000萬次:Elapsed ~幾百ms,**Cycles 極高** (持續佔用 CPU)。

2. 字符串拼接 vs StringBuilder:GC 壓力對比

int iter = 100_000;
string s = "";
CodeTimer.Time("String Concat", iter, () => s += "a"); // 每次新建字符串
StringBuilder sb = new StringBuilder();
CodeTimer.Time("StringBuilder", iter, () => sb.Append("a")); // 緩衝區擴展

String vs StringBuilder GC

  • `String Concat`:Gen 0 回收 **數萬次**,Gen 1/2 亦有 — 大量臨時字符串對象。
  • `StringBuilder`:Gen 0 回收 **極少** (僅擴容時) — 緩衝區複用。

適用場景與局限

  • ✅ **微基準測試**:比較演算法、數據結構、API 調用方式的相對性能。
  • ✅ **GC 行為分析**:驗證對象池、緩衝區複用、避免大對象堆 (LOH) 分配。
  • ✅ **CPU 密集型熱點識別**:週期數高說明計算密集,可針對性優化 (SIMD、並行、算法)。
  • ❌ **I/O 密集/異步操作**:`await` 導致線程釋放,週期數不反映真實耗時。
  • ❌ **生產環境監控**:侵入式、單線程、優先級修改有副作用 — 請用 ETW/PerfView/Profiler。

現代替代

.NET Core 3.0+ 推薦 **BenchmarkDotNet** — 統計學嚴謹、自動預熱/迭代、多運行時支援、豐富報表。但 CodeTimer 以 **<50 行代碼** 實現核心功能,適合快速驗證、教學演示、無依賴環境。

「有數據,有真相」 — CodeTimer 讓性能優化從憑感覺變為憑證據。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved