从入门到进阶:C#.NET Stopwatch 计时与性能测量全攻略

简介

Stopwatch.NET 提供的高精度计时器,位于 System.Diagnostics 命名空间。

底层依赖操作系统的高分辨率性能计数器(Performance Counter),当硬件支持时,其精度可达纳秒级;在不支持时退回到环境时钟(毫秒级)。

主要用途包括:

  • 性能测量(Benchmark

  • 代码片段耗时分析

  • 定时任务、超时检测

核心原理与特性

工作原理

Stopwatch 使用底层硬件计时器(通常是 CPU 的高分辨率性能计数器)提供纳秒级精度的时间测量:

graph LR A[应用程序] --> B[Stopwatch] B --> C[QueryPerformanceCounter API] C --> D[硬件计时器] D --> E[高精度时间测量]

关键特性

  • 高精度:最高可达纳秒级(取决于硬件)

  • 低开销:专门优化的计时实现

  • 独立计时:不受系统时间调整影响

  • 线程安全:可在多线程环境中安全使用

核心属性

属性 类型 描述
IsRunning bool 指示计时器是否正在运行
Elapsed TimeSpan 当前测量的总时间
ElapsedMilliseconds long 总毫秒数(整数)
ElapsedTicks long 计时器滴答计数
Frequency static long 每秒的计时器滴答数

核心 API

成员 说明
Start() 启动或继续计时(若已停止,则接着累加)。
Stop() 暂停计时,保留已累计时间。
Reset() 重置累计时间为零,但不改变运行状态(运行中依旧在计时)。
Restart() 等同于 Reset() + Start(),常用于重复测量。
IsRunning (bool) 当前是否在运行状态。
Elapsed 返回 TimeSpan 类型的累计运行时间。
ElapsedMilliseconds 累计运行总毫秒(取整)。
ElapsedTicks 累计运行计数器刻度数(与硬件计时器相关)。
静态属性 Frequency 性能计数器频率(每秒刻度数),用于将刻度转换为时间; IsHighResolution 表示是否为高分辨率计数器。

典型用法

基本测量

csharp 复制代码
var sw = Stopwatch.StartNew();    // 等同于 new Stopwatch(); sw.Start();
DoWork();
sw.Stop();
Console.WriteLine($"耗时:{sw.Elapsed.TotalMilliseconds} ms");

分段测量(Laps)

csharp 复制代码
var sw = new Stopwatch();
sw.Start();

// 第一段
DoStep1();
Console.WriteLine($"Step1: {sw.ElapsedMilliseconds} ms");

// 第二段
DoStep2();
Console.WriteLine($"Step2: {sw.ElapsedMilliseconds} ms");

// 重置累计,再测
sw.Restart();
DoOther();
Console.WriteLine($"Other: {sw.Elapsed.TotalMilliseconds} ms");

多次重复测量

csharp 复制代码
var sw = new Stopwatch();
for (int i = 0; i < 5; i++)
{
    sw.Restart();
    DoWork();
    Console.WriteLine($"Run {i}: {sw.ElapsedMilliseconds} ms");
}

精确时间计算

csharp 复制代码
// 纳秒级计算
long start = Stopwatch.GetTimestamp();
// 执行操作...
long end = Stopwatch.GetTimestamp();
double elapsedNs = (end - start) * 1_000_000_000 / (double)Stopwatch.Frequency;

进阶技巧

测量异步方法

csharp 复制代码
var sw = Stopwatch.StartNew();
await SomeAsyncOperation();
sw.Stop();
Console.WriteLine($"Async 耗时:{sw.ElapsedMilliseconds} ms");

嵌套测量

csharp 复制代码
var swTotal = Stopwatch.StartNew();
var swA = new Stopwatch(); swA.Start();
DoA(); swA.Stop();
var swB = Stopwatch.StartNew();
DoB(); swB.Stop();
swTotal.Stop();
Console.WriteLine($"A: {swA.Elapsed}  B: {swB.Elapsed}  Total: {swTotal.Elapsed}");

避免 GC 干扰

  • 对短时测量可先进行一次"热身"(预调用方法和计时器)以加载 JIT、内存分配等。

  • 可以在测量前后调用 GC.Collect(); GC.WaitForPendingFinalizers();,以减少 GC 的随机停顿对结果的影响。

性能测试框架

csharp 复制代码
public static TimeSpan MeasurePerformance(Action action, int iterations = 1000)
{
    // 预热
    action();
    GC.Collect();
    GC.WaitForPendingFinalizers();
    
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < iterations; i++)
    {
        action();
    }
    sw.Stop();
    
    return TimeSpan.FromTicks(sw.ElapsedTicks / iterations);
}

// 使用示例
var avgTime = MeasurePerformance(() => 
{
    // 测试的代码块
    var result = Enumerable.Range(0, 1000).Sum();
}, 10000);

Console.WriteLine($"平均执行时间: {avgTime.TotalMilliseconds:F6} ms");

高级应用场景

性能监控中间件

csharp 复制代码
public class PerformanceMonitorMiddleware
{
    private readonly RequestDelegate _next;
    
    public PerformanceMonitorMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        
        await _next(context);
        
        sw.Stop();
        context.Response.Headers["X-Execution-Time"] = sw.ElapsedMilliseconds.ToString();
        
        // 记录到日志系统
        LogPerformance(context.Request.Path, sw.Elapsed);
    }
}

// 在Startup中注册
app.UseMiddleware<PerformanceMonitorMiddleware>();

算法复杂度分析

csharp 复制代码
public void AnalyzeAlgorithmComplexity()
{
    for (int n = 1000; n <= 1000000; n *= 10)
    {
        int[] data = Enumerable.Range(0, n).ToArray();
        
        var sw = Stopwatch.StartNew();
        Array.Sort(data); // 测试排序算法
        sw.Stop();
        
        Console.WriteLine($"n={n}, 时间={sw.ElapsedTicks} ticks");
    }
}
相关推荐
大空大地20264 小时前
流程控制语句--switch多分支语句使用、while循环语句的使用、do...while语句、for循环
c#
kylezhao20195 小时前
C#序列化与反序列化详细讲解与应用
c#
JQLvopkk5 小时前
C# 实践AI :Visual Studio + VSCode 组合方案
人工智能·c#·visual studio
故事不长丨5 小时前
C#线程同步:lock、Monitor、Mutex原理+用法+实战全解析
开发语言·算法·c#
kingwebo'sZone5 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
大空大地20266 小时前
表达式与运算符
c#
向上的车轮7 小时前
为什么.NET(C#)转 Java 开发时常常在“吐槽”Java:checked exception
java·c#·.net
心疼你的一切8 小时前
Unity异步编程神器:Unitask库深度解析(功能+实战案例+API全指南)
深度学习·unity·c#·游戏引擎·unitask
波波0078 小时前
每日一题:.NET 的 GC是如何分代工作的?
算法·.net·gc
.房东的猫19 小时前
ERP(金蝶云星空)开发【安装篇】
c#