关于C#:DateTime.Now是测量函数性能的最佳方法吗?

Is DateTime.Now the best way to measure a function's performance?

我需要找到一个瓶颈,并需要尽可能准确地测量时间。

下面的代码片段是衡量性能的最佳方法吗?

1
2
3
4
5
6
DateTime startTime = DateTime.Now;

// Some execution process

DateTime endTime = DateTime.Now;
TimeSpan totalTimeTaken = endTime.Subtract(startTime);


不,不是。使用秒表(在System.Diagnostics中)

1
2
3
4
5
Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();

Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);

秒表自动检查是否存在高精度计时器。

值得一提的是,DateTime.Now通常比DateTime.UtcNow慢得多,因为必须使用时区、DST等进行工作。

datetime.utcnow的分辨率通常为15 ms。请参阅John Chapman关于DateTime.Now精度的博客文章,了解一个很好的摘要。

有趣的小窍门:如果你的硬件不支持高频计数器,秒表就会落在DateTime.UtcNow上。您可以通过查看静态字段秒表.ishighresolution来检查秒表是否使用硬件来实现高精度。


如果你想要快速和肮脏的东西,我建议使用秒表代替更高的精度。

1
2
3
4
5
6
Stopwatch sw = new Stopwatch();
sw.Start();
// Do Work
sw.Stop();

Console.WriteLine("Elapsed time: {0}", sw.Elapsed.TotalMilliseconds);

或者,如果你需要一些更复杂的东西,你应该考虑使用第三方剖析器,比如蚂蚁。


本文指出,首先需要比较三种备选方案:StopwatchDateTime.NowDateTime.UtcNow

它还显示,在某些情况下(性能计数器不存在时),秒表正在使用datetime.utcnow+一些额外的处理。因此,很明显,在这种情况下,datetime.utcnow是最佳选择(因为其他人使用它+一些处理)

然而,事实证明,计数器几乎总是存在的——见关于高分辨率性能计数器及其存在与.NET秒表相关的解释?.

这是一个性能图。请注意,与其他替代方案相比,UTCNOW的性能成本有多低:

Enter image description here

X轴是样本数据大小,Y轴是示例的相对时间。

Stopwatch的一个优点是它提供了更高的分辨率时间测量。另一个是它更OO的性质。然而,围绕UtcNow创建一个OO包装器并不难。


将基准代码推送到实用程序类/方法中是很有用的。错误时,StopWatch类不需要是DisposedStopped类。所以,对某些动作计时的最简单代码是

1
2
3
4
5
6
7
8
9
10
public partial class With
{
    public static long Benchmark(Action action)
    {
        var stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

示例调用代码

1
2
3
4
5
public void Execute(Action action)
{
    var time = With.Benchmark(action);
    log.DebugFormat("Did action in {0} ms.", time);
}

这是扩展方法版本

1
2
3
4
5
6
7
public static class Extensions
{
    public static long Benchmark(this Action action)
    {
        return With.Benchmark(action);
    }
}

和示例调用代码

1
2
3
4
5
public void Execute(Action action)
{
    var time = action.Benchmark()
    log.DebugFormat("Did action in {0} ms.", time);
}


秒表功能更好(精度更高)。不过,我还建议您下载一个流行的分析程序(dottrace和ants是我使用最多的分析程序)。dottrace的免费试用是完全功能性的,不像其他的一些那样唠叨。


使用System.Diagnostics.StopWatch类。

1
2
3
4
5
6
7
8
Stopwatch sw = new Stopwatch();
sw.Start();

// Do some code.

sw.Stop();

// sw.ElapsedMilliseconds = the time your"do some code" took.


和秒表一样,它更好。

关于性能度量,您还应该检查您的"//某些执行过程"是否是一个非常短的过程。

还要记住,第一次运行"//some execution process"可能比随后的运行慢得多。

我通常通过在一个循环中运行1000次或1000000次来测试一个方法,我得到的数据比运行一次要准确得多。


这些都是衡量时间的好方法,但这只是找到瓶颈的一种非常间接的方法。

在线程中找到僵尸的最直接的方法是让它运行,当它做任何让您等待的事情时,用暂停或中断键停止它。这样做几次。如果瓶颈占用X%的时间,则X%是在每个快照的操作中捕获它的概率。

下面是一个更完整的解释,说明了它是如何工作的以及为什么工作的。


@ Sean Chambers

仅供参考,.NET计时器类不用于诊断,它以预设的间隔生成事件,如(来自msdn):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的设计器工具箱中找到它。


这是正确的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

有关更多信息,请使用秒表而不是数据时间来获得准确的性能计数器。


Visual Studio团队系统具有一些可能有助于解决此问题的功能。本质上,您可以编写单元测试,并将它们混合在不同的场景中,以作为压力测试或负载测试的一部分针对您的软件运行。这可能有助于识别对应用程序性能影响最大的代码区域。

微软的模式和实践小组在Visual Studio团队系统性能测试指南中有一些指导。


我刚刚在万斯莫里森的博客上发现了一篇关于他写的一个代码计时器类的文章,它使使用StopWatch变得更容易,并且在旁边做了一些简单的工作。


这不够专业:

1
2
3
4
5
Stopwatch sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();

Console.WriteLine("Time taken: {0}ms", sw.Elapsed.TotalMilliseconds);

更可靠的版本是:

1
2
3
4
5
6
7
8
9
10
11
12
13
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配置文件中轻松地分隔不同的代码间隔。


我在程序中使用的方法是使用这里所示的秒表类。

1
2
3
4
5
6
7
Stopwatch sw = new Stopwatch();
sw.Start();


// Critical lines of code

long elapsedMs = sw.Elapsed.TotalMilliseconds;


我几乎没有做过这种性能检查(我倾向于认为"这很慢,速度更快"),所以我总是这样做。

谷歌确实为性能检查提供了大量的资源/文章。

很多人提到使用pinvoke获取性能信息。我学习的很多材料都只提到使用Perfmon。

编辑:

看了秒表的谈话……好极了!我学到了一些东西:)

这篇文章看起来不错