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

C# 高精度計時實作:Stopwatch vs QueryPerformanceCounter 深度解析

     編輯:關於C語言

為什麼需要高精度計時?

效能分析、基準測試、遊戲迴圈、即時系統、科學計算皆需微秒甚至奈秒級計時精度。錯誤的計時方式會導致優化方向錯誤、效能倒退。

.NET 內建計時 API 對比

API解析度適用場景缺點
Environment.TickCount~10-16 ms (系統時脈)粗略存活時間精度極低、49 天溢位
DateTime.Now~10-16 ms顯示當前時間系統時鐘精度、受時區/夏令時間影響
DateTime.UtcNow~10-16 ms記錄時間戳同左
Stopwatch奈秒級 (硬體支援)所有高精度測量首選極少數舊硬體無高精度計數器會退回系統時鐘
QueryPerformanceCounter (P/Invoke)奈秒級.NET 1.0/1.1 時代替代品需 P/Invoke、程式碼複雜、現代 .NET 已無必要

現代標準作法:Stopwatch 類別

基本用法

using System.Diagnostics;

var sw = Stopwatch.StartNew();
// 或
var sw = new Stopwatch();
sw.Start();

// 執行待測程式碼
Thread.Sleep(100); // 模擬工作負載

sw.Stop();
Console.WriteLine($"Elapsed: {sw.Elapsed}");           // TimeSpan
Console.WriteLine($"ElapsedMs: {sw.ElapsedMilliseconds}"); // long 毫秒
Console.WriteLine($"ElapsedTicks: {sw.ElapsedTicks}");     // long 原始刻度
Console.WriteLine($"Frequency: {Stopwatch.Frequency} Hz"); // 每秒刻度數 (唯讀)

進階用法:多段計時與重置

var sw = new Stopwatch();
sw.Start();
DoPhase1();
sw.Stop();
Console.WriteLine($"Phase1: {sw.ElapsedMilliseconds} ms");

sw.Restart(); // 歸零並重新開始
DoPhase2();
sw.Stop();
Console.WriteLine($"Phase2: {sw.ElapsedMilliseconds} ms");

// 取得總時間 (需自行累加或用兩個 Stopwatch)

高精度基準測試模板

public static double Benchmark(Action action, int warmup = 3, int iterations = 1000)
{
    // Warmup (JIT 編譯、快取預熱)
    for (int i = 0; i < warmup; i++) action();

    // 實測
    var sw = new Stopwatch();
    long totalTicks = 0;
    for (int i = 0; i < iterations; i++)
    {
        sw.Restart();
        action();
        sw.Stop();
        totalTicks += sw.ElapsedTicks;
    }
    
    double avgTicks = (double)totalTicks / iterations;
    double avgNanoseconds = avgTicks * (1_000_000_000.0 / Stopwatch.Frequency);
    return avgNanoseconds;
}

// 使用範例
double ns = Benchmark(() => 
{
    var sum = 0;
    for (int i = 0; i < 1000; i++) sum += i;
}, iterations: 10000);
Console.WriteLine($"Average: {ns:F2} ns");

歷史遺產:手動 P/Invoke QueryPerformanceCounter (僅供考古)

.NET Framework 1.0/1.1 缺乏 Stopwatch,開發者必須 P/Invoke Win32 API:

using System;
using System.Runtime.InteropServices;

public class HighResolutionTimer // .NET 1.x 時代產物
{
    private long _start, _stop, _frequency;

    public HighResolutionTimer()
    {
        QueryPerformanceFrequency(ref _frequency);
    }

    public void Start() => QueryPerformanceCounter(ref _start);
    public void Stop() => QueryPerformanceCounter(ref _stop);

    public double ElapsedSeconds => (double)(_stop - _start) / _frequency;

    [DllImport("KERNEL32.dll", CharSet = CharSet.Auto)]
    private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);

    [DllImport("KERNEL32.dll", CharSet = CharSet.Auto)]
    private static extern bool QueryPerformanceFrequency(ref long lpFrequency);
}

為何現代 .NET 不建議此寫法?

  1. Stopwatch 內部已封裝 QueryPerformanceCounter,並處理跨處理器核心同步、電源管理頻率變動等邊緣情況。
  2. P/Invoke 有轉換開銷,反而降低精度。
  3. 程式碼複雜度高,維護成本大。

硬體層面知識:TSC vs HPET vs ACPI PM Timer

計時源頻率特性現代作業系統策略
TSC (Time Stamp Counter)CPU 主頻 (GHz 級)極高精度、核心間可能不同步、變頻影響Invariant TSC (恆定頻率) + 核心同步修正
HPET (High Precision Event Timer)10-100 MHz獨立晶振、精度穩定、延遲較高備用計時源
ACPI PM Timer3.58 MHz舊標準、精度一般相容性備用

Windows 作業系統抽象層 (HAL) 自動選擇最佳可用計時源,Stopwatch.Frequency 反映實際使用的硬體頻率。

常見陷阱與最佳實踐

陷阱正確做法
用 DateTime.Now 測效能永遠用 Stopwatch
單次測量得出結論最少 1000 次迭代 + Warmup + 統計分佈 (中位數/百分位數)
忽略 JIT 編譯時間測試前先執行數次 Warmup
測試極短操作 (< 100 ns)使用迴路放大測量,除以迴圈次數
多執行緒干擾設定 Process.GetCurrentProcess().ProcessorAffinity、Thread.Priority = ThreadPriority.Highest
GC 干擾測試前 GC.Collect()、GC.WaitForPendingFinalizers()、GC.TryStartNoGCRegion() (.NET 4.6+)

專業級基準測試工具:BenchmarkDotNet

// 安裝 NuGet: BenchmarkDotNet
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class StringConcatBenchmark
{
    [Benchmark(Baseline = true)]
    public string StringConcat() => "a" + "b" + "c";

    [Benchmark]
    public string StringBuilder() => new StringBuilder().Append("a").Append("b").Append("c").ToString();

    [Benchmark]
    public string Interpolation() => $"{"a"}{"b"}{"c"}";
}

public class Program
{
    public static void Main() => BenchmarkRunner.Run();
}

BenchmarkDotNet 自動處理:Warmup、迭代次數動態調整、統計分析、記憶體分配統計、結果匯出 (Markdown/HTML/CSV)、環境資訊記錄。

總結

  1. Stopwatch 是 .NET 2.0+ 標準高精度計時器,封裝硬體效能計數器,奈秒級精度。
  2. 手動 P/Invoke QueryPerformanceCounter 屬於 .NET 1.x 歷史遺產,現代專案嚴禁使用。
  3. 嚴謹基準測試需:Warmup、大量迭代、統計分析、環境隔離 — 強烈建議直接使用 BenchmarkDotNet。
  4. 理解底層硬體計時源 (TSC/HPET) 有助於解釋極端情況下的測量異常。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved