我需要找到一个瓶颈,并需要尽可能准确地测量时间。
以下代码片段是衡量性能的最佳方法吗?
DateTime startTime = DateTime.Now;
// Some execution process
DateTime endTime = DateTime.Now;
TimeSpan totalTimeTaken = endTime.Subtract(startTime);
我需要找到一个瓶颈,并需要尽可能准确地测量时间。
以下代码片段是衡量性能的最佳方法吗?
DateTime startTime = DateTime.Now;
// Some execution process
DateTime endTime = DateTime.Now;
TimeSpan totalTimeTaken = endTime.Subtract(startTime);
Answers:
不,这不对。使用秒表(在中System.Diagnostics
)
Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();
Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);
秒表会自动检查是否存在高精度计时器。
值得一提的是,由于要进行时区,DST等工作,DateTime.Now
通常速度要慢得多。DateTime.UtcNow
DateTime.UtcNow通常具有15毫秒的分辨率。有关精确度的信息,请参见John Chapman的博客文章DateTime.Now
。
有趣的琐事:DateTime.UtcNow
如果您的硬件不支持高频计数器,秒表将重新显示。您可以通过查看静态字段Stopwatch.IsHighResolution来检查Stopwatch是否使用硬件来实现高精度。
PerformWork()
的电话很短,则可以重复调用它并计算一批呼叫的平均值。此外,请对整个呼叫进行计时,而不要开始/停止您的通话,Stopwatch
以避免产生频闪效应,这种频闪效应会使您的计时测量变得混乱。
这篇文章说,首先你需要比较三个选择,Stopwatch
,DateTime.Now
和DateTime.UtcNow
。
它还显示在某些情况下(不存在性能计数器时),秒表正在使用DateTime.UtcNow +一些额外的处理。因此,在这种情况下,显然DateTime.UtcNow是最好的选择(因为其他人使用它+一些处理)
但是,事实证明,该计数器几乎总是存在-请参见有关高分辨率性能计数器及其与.NET Stopwatch相关的解释?。
这是一个性能图。请注意,与替代产品相比,UtcNow具有较低的性能成本:
X轴是示例数据大小,Y轴是示例的相对时间。
Stopwatch
更好的一件事是它提供了更高分辨率的时间测量。另一个是其更面向对象的性质。但是,围绕创建OO包装器UtcNow
并不难。
将基准测试代码放入实用程序类/方法中很有用。本StopWatch
类并不需要是Disposed
或Stopped
出错。因此,计时某些动作的最简单代码是
public partial class With
{
public static long Benchmark(Action action)
{
var stopwatch = Stopwatch.StartNew();
action();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}
样本调用代码
public void Execute(Action action)
{
var time = With.Benchmark(action);
log.DebugFormat(“Did action in {0} ms.”, time);
}
这是扩展方法版本
public static class Extensions
{
public static long Benchmark(this Action action)
{
return With.Benchmark(action);
}
}
和示例调用代码
public void Execute(Action action)
{
var time = action.Benchmark()
log.DebugFormat(“Did action in {0} ms.”, time);
}
Elapsed.TotalMilliseconds
更高的精度。看到这个问题太stackoverflow.com/questions/8894425/...
同上秒表,那就更好了。
关于性能评估,您还应该检查“ //某些执行过程”是否是很短的过程。
还请记住,“ //某些执行过程”的第一次运行可能比随后的运行慢。
我通常通过在一个循环中运行1000次或1000000次来测试一种方法,与运行一次相比,我得到的数据要准确得多。
这些都是衡量时间的好方法,但这只是发现瓶颈的一种非常间接的方法。
在线程中查找瓶颈的最直接方法是使其运行,并且在执行任何使您等待的事情时,请使用暂停键或Break键将其暂停。这样做几次。如果瓶颈花费了X%的时间,则X%是您在每个快照的动作中捕获它的概率。
@ 肖恩·钱伯斯
仅供参考,.NET Timer类不适用于诊断,它以预设的时间间隔生成事件,如下所示(来自MSDN):
System.Timers.Timer aTimer;
public static void Main()
{
// Create a timer with a ten second interval.
aTimer = new System.Timers.Timer(10000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
// Set the Interval to 2 seconds (2000 milliseconds).
aTimer.Interval = 2000;
aTimer.Enabled = true;
Console.WriteLine("Press the Enter key to exit the program.");
Console.ReadLine();
}
// Specify what you want to happen when the Elapsed event is
// raised.
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}
因此,这确实并不能帮助您知道花费了多长时间,而是花了多长时间。
计时器也作为System.Windows.Forms中的控件公开...您可以在VS05 / VS08的设计器工具框中找到它
这是正确的方法:
using System;
using System.Diagnostics;
class Program
{
public static void Main()
{
Stopwatch stopWatch = Stopwatch.StartNew();
// some other code
stopWatch.Stop();
// this not correct to get full timer resolution
Console.WriteLine("{0} ms", stopWatch.ElapsedMilliseconds);
// Correct way to get accurate high precision timing
Console.WriteLine("{0} ms", stopWatch.Elapsed.TotalMilliseconds);
}
}
有关更多信息,请通过使用秒表而不是DataTime来获取准确的性能计数器。
Visual Studio Team System具有一些有助于解决此问题的功能。本质上,您可以编写单元测试并将其混合在不同的场景中,以作为压力测试或负载测试的一部分针对您的软件运行。这可能有助于确定对应用程序性能影响最大的代码区域。
Microsoft的Patterns and Practices组在Visual Studio Team System性能测试指南中提供了一些指南。
我刚刚在Vance Morrison的博客中找到有关他编写的CodeTimer类的文章,该类使使用StopWatch
变得更容易,并且在侧面做了一些整洁的事情。
这还不够专业:
Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();
Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);
一个更可靠的版本是:
PerformWork();
int repeat = 1000;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < repeat; i++)
{
PerformWork();
}
sw.Stop();
Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds / repeat);
在我的真实代码中,我将添加GC.Collect调用以将托管堆更改为已知状态,并添加Sleep调用,以便可以在ETW配置文件中轻松分隔不同的代码间隔。