用Stopwatch类来测试你的程序运行时间

本文介绍如何使用.NET Framework 2.0中的Stopwatch类来精确测量代码执行时间,以优化程序性能。通过示例展示了Stopwatch类的使用方法及其提供的各种属性。

上个月在项目中,由于我做的网页后台计算步骤多,运行时间长,成了棘手问题,如此一来就要优化性能,找出瓶颈,重构代码

同事当时的建议是使用时间戳(timespan),后来一个同事用的是诊断模式里面一个计时方法,现在看来都很别扭,因为要

把时间减来减去,感觉不方便,不好用.今天我偶然的发现了stopwatch这个宝贝,不错,这个类是在.net 2.0的时候才加入的

看来微软想的挺周到,这个类就是专门为了对付这种情况来使用的工具,并且它还有一个非常不错的功能,可以重新归零再计时

.下面是转载的内容.

软件的运行速度必须要在用户可以接受的范围内。通常,改善那些短暂的但频繁使用的例程的速度会大幅度地提高软件的整体速度。
要改善速度,当然首先要能够量度时间。好,那我们考虑一下在跑道上的情况,枪声一响,马上按下秒表开始计时,在选手到达终点那一刻结束计时,这时就可以知 道该选手所用的时间了。要开始对下一轮比赛计时前,要先将秒表归零。.NET2.0也提供了这样一个秒表:Stopwatch类,它可以比较精确地测量时 间。
速度测试:
软件的性能和可测性是一个复杂的主题。要确保应用程序能够满足用户的期望,就需要在开发周期内考虑它的性能和可测性。这在设计阶段至关重要,一个糟糕的设 计几乎肯定会导致糟糕的用户体验。然而,仅仅有好的设计也不能保证程序能够高效地运行,最终代码的质量同样重要。
量度一个运行时间较长的例程相当简单。如果一个过程会持续几分钟,只要一块腕表就可以记录它的时间了。比如一个执行时间为两分钟的过程,10%的改善能够节省12秒,这是很容易去确定的。
而如果要测量一个非常短暂的过程,就要考虑更好的精确性了。比如有一些很小的例程,它们的运行时间可能只有千分之一秒,但会被调用100万次,这样的累积 效果就明显了。在.NET framework的先前版本中,需要使用Windows API函数,而在.NET framework 2.0中,微软引入了Stopwatch(它就是我们的秒表)类来简化时间的量度任务。
Stopwatch类:
使用Stopwatch类来量度时间非常简单。跟现实生活中的秒表一样,这个类的对象也能够对计数器进行开始、停止、归零(重置)操作,不过它可比一般的秒表精确多了,它能够精确到微秒(也就是百万分之一秒)。
示例代码:
要演示Stopwatch的使用还是来段代码吧。下面是一个控制台应用程序,它将1到100万之间的所有整数累加:


using System;

namespace StopWatchClass
{
class Program
{
static void Main(string[] args)
{
long total = 0;

for (int i = 1; i <= 10000000; i++)
{
total += i;
}
}
}
}
添加Stopwatch对象:
Stopwatch类位于System.Diagnostics命名空间。下面是添加对象后的代码:
using System;
using System.Diagnostics;

namespace StopWatchClass
{
class Program
{
static void Main(string[] args)
{
Stopwatch timer = new Stopwatch();
long total = 0;

for (int i = 1; i <= 10000000; i++)
{
total += i;
}
}
}
}
控制Stopwatch对象:
Stopwatch提供了几个方法用以控制Stopwatch对象。Start方法开始一个计时操作,Stop方法停止计时。此时如果第二次使用 Start方法,将继续计时,最终的计时结果为两次计时的累加。为避免这种情况,在第二次计时前用Reset方法将对象归零。这三个方法都不需要参数。代 码是:
using System;
using System.Diagnostics;

namespace StopWatchClass
{
class Program
{
static void Main(string[] args)
{
Stopwatch timer = new Stopwatch();
long total = 0;

timer.Start();
for (int i = 1; i <= 10000000; i++)
{
total += i;
}

timer.Stop();
}
}
}
读取Stopwatch结果:
<!--[if !supportLists]--><!--[endif]--> 在结束计时后下一步就是读取计时结果了。Stopwatch类提供了一下属性:
<!--[if !supportLists]--><!--[endif]--><!--[if !supportLists]--><!--[endif]-->
Elapsed:返回一个TimeSpan对象,表示计时时间间隔;
ElapsedMilliseconds:返回计时经过的微秒数,精确度稍差,适合于稍长一点的计时;
ElapsedTicks: 返回计时经过的计时器刻度(timer tick)数。计时器刻度是Stopwatch对象可能的最小量度单位。计时器刻度时间的长度由特定的计算机和操作系统确定。Stopwatch对象的 Frequency静态字段的值表示一秒所包含的计时器刻度数。注意它与TimeSpan的Ticks属性所用的时间单位的区别。
应当根据计时任务的情况选择其中的一个属性。在我们的示例程序中,Elapsed属性提供了需要的精确度,用它来输出经过的微秒数。这也是TimeSpan的最高精确度了。
下面是最终的程序代码:


using System;
using System.Diagnostics;

namespace StopWatchClass
{
class Program
{
static void Main(string[] args)
{
Stopwatch timer = new Stopwatch();
long total = 0;

timer.Start();
for (int i = 1; i <= 10000000; i++)
{
total += i;
}

timer.Stop();

decimal micro = timer.Elapsed.Ticks / 10m;
Console.WriteLine("Execution time was {0:F1} microseconds.", micro);
}
}
}
另外,使用IsRunning属性可以查看一个Stopwatch实例是否正在计时,使用StartNew方法可以开始一个新的计时器。

<think>我们正在讨论如何使用C#中的Stopwatch来测量代码执行时间。根据引用内容,Stopwatch位于System.Diagnostics命名空间,它提供了高精度的时间测量功能。以下是使用Stopwatch的基本步骤: 1. 引入命名空间:在文件顶部添加`using System.Diagnostics;`。 2. 创建Stopwatch实例:使用`Stopwatch stopwatch = new Stopwatch();`。 3. 开始计时:调用`stopwatch.Start()`方法。 4. 执行要测量的代码。 5. 停止计时:调用`stopwatch.Stop()`方法。 6. 获取经过的时间:通过`stopwatch.Elapsed`、`stopwatch.ElapsedMilliseconds`或`stopwatch.ElapsedTicks`属性获取时间。 为了获得更准确的结果,建议采取以下措施: - 预运行代码:在正式测量前先运行一次代码,以避免JIT编译带来的额外开销。 - 多次执行取平均:对于执行时间很短的代码,可以多次执行取平均值,以减少误差。 - 控制环境干扰:关闭不必要的应用程序,减少系统其他进程的影响。在测量前可以手动触发垃圾回收(使用`GC.Collect()`和`GC.WaitForPendingFinalizers()`),以避免垃圾回收在测量期间发生。 下面是一个示例代码,展示了如何使用Stopwatch测量代码执行时间,并采取多次执行取平均的方法: ```csharp using System; using System.Diagnostics; using System.Threading; class Program { static void Main() { // 预运行一次,避免JIT编译影响 MeasureTime(1); // 正式测量,执行1000次取平均 long totalTime = MeasureTime(1000); Console.WriteLine($"平均执行时间: {totalTime / 1000.0} 毫秒"); } static long MeasureTime(int iterations) { // 创建Stopwatch实例 Stopwatch stopwatch = new Stopwatch(); // 为了避免GC干扰,可以手动触发一次GC GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // 预热:先运行一次,确保代码已经被JIT编译 ExampleMethod(); long totalElapsed = 0; for (int i = 0; i < iterations; i++) { stopwatch.Restart(); // 重新开始计时(相当于先Reset再Start) ExampleMethod(); stopwatch.Stop(); totalElapsed += stopwatch.ElapsedMilliseconds; } return totalElapsed; } static void ExampleMethod() { // 模拟一个耗时操作,例如计算斐波那契数列 Fibonacci(30); } static int Fibonacci(int n) { if (n <= 1) return n; return Fibonacci(n - 1) + Fibonacci(n - 2); } } ``` 在这个示例中: - `MeasureTime`方法负责测量指定次数的代码执行总时间。 - 在正式测量前,我们通过预运行(预热)来确保代码已经被JIT编译。 - 使用`stopwatch.Restart()`方法,它等同于先调用`Reset`再调用`Start`,这样可以在循环中重复使用同一个Stopwatch实例。 - 通过多次执行取平均来减少误差。 注意事项: - 对于非常短的操作,`ElapsedMilliseconds`可能为0,此时可以使用`ElapsedTicks`属性,然后通过`Stopwatch.Frequency`将计时周期数转换为时间(例如:`(stopwatch.ElapsedTicks * 1000.0) / Stopwatch.Frequency` 得到毫秒数)。 - 多次执行时,注意循环本身的开销。如果循环开销相对于被测代码不可忽略,可以考虑在循环外开始计时,然后多次执行被测代码,再停止计时,最后除以执行次数。但这样可能会包含循环控制的开销。因此,通常的做法是每次循环都单独计时(如示例所示),这样循环控制的开销不会计入被测代码的时间,但要注意每次循环的计时本身也有微小开销。 相关问题: 1. 如何避免垃圾回收影响Stopwatch的测量结果? 2. 除了Stopwatch,C#中还有哪些测量时间的方法? 3. 如何测量异步代码的执行时间?</think>### 使用 Stopwatch 测量 C# 代码执行时间 在 C# 中测量代码执行时间的最佳实践是使用 `Stopwatch` (位于 `System.Diagnostics` 命名空间)。以下是详细步骤和注意事项: #### 基础使用步骤 ```csharp using System.Diagnostics; // 创建 Stopwatch 实例 Stopwatch stopwatch = new Stopwatch(); // 开始计时 stopwatch.Start(); // 执行待测代码 YourCodeToMeasure(); // 停止计时 stopwatch.Stop(); // 获取执行时间(毫秒) long elapsedMs = stopwatch.ElapsedMilliseconds; Console.WriteLine($"执行时间: {elapsedMs} 毫秒"); ``` #### 专业优化建议 1. **避免 JIT 预热影响** 首次执行时预运行代码: ```csharp // 预运行一次(不计时) YourCodeToMeasure(); stopwatch.Restart(); // 重置并开始 ``` 2. **多次执行取平均值** 适用于短时操作: ```csharp int runs = 1000; long totalTime = 0; for (int i = 0; i < runs; i++) { stopwatch.Restart(); YourCodeToMeasure(); stopwatch.Stop(); totalTime += stopwatch.ElapsedTicks; // 使用 Ticks 更高精度 } double avgMs = (totalTime * 1000.0) / (runs * Stopwatch.Frequency); Console.WriteLine($"平均执行时间: {avgMs:F6} 毫秒"); ``` 3. **减少环境干扰** ```csharp GC.Collect(); // 手动触发垃圾回收 GC.WaitForPendingFinalizers(); // 等待回收完成 Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; // 提升进程优先级 ``` 4. **高精度时间转换** 使用计时周期(Ticks)转换: ```csharp double elapsedMicroseconds = (stopwatch.ElapsedTicks * 1_000_000.0) / Stopwatch.Frequency; ``` #### 完整示例 ```csharp using System; using System.Diagnostics; class Program { static void Main() { // 创建实例 Stopwatch sw = new Stopwatch(); // 环境优化 GC.Collect(); GC.WaitForPendingFinalizers(); // 预热 TestMethod(); sw.Restart(); // 正式测量(1000次取平均) const int RUNS = 1000; long totalTicks = 0; for (int i = 0; i < RUNS; i++) { sw.Restart(); TestMethod(); sw.Stop(); totalTicks += sw.ElapsedTicks; } // 计算平均时间 double avgMs = (totalTicks * 1000.0) / (RUNS * Stopwatch.Frequency); Console.WriteLine($"平均执行时间: {avgMs:F4} 毫秒"); } static void TestMethod() { // 待测试的代码 int sum = 0; for (int i = 0; i < 100000; i++) { sum += i; } } } ``` #### 注意事项 1. 使用 `ElapsedTicks` 而非 `ElapsedMilliseconds` 可获得微秒级精度 2. 避免在测量期间发生垃圾回收(通过 `GC.Collect()` 预处理) 3. 对于短时操作(<1ms),必须多次运行取平均值 4. `Stopwatch` 比 `DateTime.Now` 精确度高约 1000 倍[^1] 5. 在云环境/容器中运行时,需注意虚拟化对计时精度的影响 > 关键提示:当测量时间小于 1 毫秒时,ElapsedMilliseconds 可能返回 0,此时必须使用 ElapsedTicks 结合 Stopwatch.Frequency 进行纳秒级计算[^2]。 --- ### 相关问题 1. **如何避免垃圾回收影响 Stopwatch 的测量结果?** [引用: 在测量前手动触发 GC 并等待回收完成可减少干扰[^3]] 2. **Stopwatch 和 DateTime 在时间测量上有何本质区别?** [引用: Stopwatch 使用硬件计时器,DateTime 依赖系统时钟[^1]] 3. **在分布式系统中如何准确测量跨服务调用时间?** [引用: 需结合分布式追踪工具如 OpenTelemetry] 4. **为什么多次执行取平均能提高时间测量准确性?** [引用: 可消除 JIT 编译、CPU 调度等随机因素的影响[^2]] 5. **如何测量异步代码的执行时间?** [引用: 需在异步方法前后分别启动/停止 Stopwatch] [^1]: Stopwatch 使用高精度硬件计时器,而 DateTime 依赖系统时钟 [^2]: 当 ElapsedMilliseconds 返回 0 时需使用 Ticks 转换 [^3]: 手动触发 GC 可减少测量期间垃圾回收的干扰
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值