如何衡量.NET中的代码性能?

时间:2009-01-19 13:44:27

标签: .net performance benchmarking

我正在使用DateTime对一行C#代码进行一些真正快速而又脏的基准测试:

long lStart = DateTime.Now.Ticks;
// do something
long lFinish = DateTime.Now.Ticks;

问题在于结果:

Start Time [633679466564559902]
Finish Time [633679466564559902]

Start Time [633679466564569917]
Finish Time [633679466564569917]

Start Time [633679466564579932]
Finish Time [633679466564579932]

......等等。

鉴于开始和结束时间相同,Ticks显然不够精细。<​​/ p>

那么,我怎样才能更好地衡量绩效呢?

18 个答案:

答案 0 :(得分:62)

从.NET 2.0开始提供的Stopwatch类是最好的方法。它是一个非常高性能的计数器,精确到几毫秒。 看看MSDN documentation,这很清楚。

编辑:如前所述,为了获得合理的平均时间,建议多次运行代码。

答案 1 :(得分:11)

重复执行您的代码。问题似乎是您的代码执行速度比测量仪器的粒度快得多。最简单的解决方案是执行许多次(数千次,数百万次)代码,然后计算平均执行时间。

编辑:此外,由于当前优化编译器(以及CLR和JVM等虚拟机)的性质,测量单行代码的执行速度可能会产生误导,因为测量会影响速度非常多。一个更好的方法是分析整个系统(或至少更大的块)并检查瓶颈在哪里。

答案 2 :(得分:8)

只是添加其他人已经说过的关于使用秒表和测量平均值的内容。

确保在测量前调用您的方法。否则,您将测量JIT编译代码所需的时间。这可能会大大扭曲你的数字。

此外,请确保测量发布模式代码,因为默认情况下调试版本的优化已关闭。调整调试代码是毫无意义的。

并确保您正在测量您实际想要测量的内容。当优化启动时,编译器/ JIT编译器可能会重新排列代码或将其完全删除,因此您最终可能会测量与预期稍有不同的内容。至少看一下生成的代码,确保代码没有被剥离。

根据您要测量的内容,请记住,真正的系统会比典型的测试应用程序更不同于运行时。一些性能问题与例如对象如何被垃圾收集。这些问题通常不会出现在简单的测试应用程序中。

实际上,最好的建议是使用真实数据来测量真实系统,因为沙箱测试可能会变得非常不准确。

答案 3 :(得分:8)

我发现这些有用的

http://accelero.codeplex.com/SourceControl/changeset/view/22633#290971 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290973 http://accelero.codeplex.com/SourceControl/changeset/view/22633#290972

TickTimer是Stopwatch的缩减副本,在构建时启动,不支持重新启动。如果当前硬件不支持高分辨率计时(秒表吞下此问题),它也会通知您

所以这个

var tickTimer = new TickTimer();
//call a method that takes some time
DoStuff();
tickTimer.Stop();
Debug.WriteLine("Elapsed HighResElapsedTicks " + tickTimer.HighResElapsedTicks);
Debug.WriteLine("Elapsed DateTimeElapsedTicks " + tickTimer.DateTimeElapsedTicks);
Debug.WriteLine("Elapsed ElapsedMilliseconds " + tickTimer.ElapsedMilliseconds);
Debug.WriteLine("Start Time " + new DateTime(tickTimer.DateTimeUtcStartTicks).ToLocalTime().ToLongTimeString());

将输出此

Elapsed HighResElapsedTicks 10022886
Elapsed DateTimeElapsedTicks 41896
Elapsed ElapsedMilliseconds 4.18966178849554
Start Time 11:44:58

DebugTimer是TickTimer的包装器,它将结果写入Debug。 (注意:它支持一次性模式)

所以这个

using (new DebugTimer("DoStuff"))
{
    //call a method that takes some time
    DoStuff();
}

将其输出到调试窗口

DoStuff: Total 3.6299 ms

IterationDebugTimer用于计算多次运行操作并将结果写入Debug所需的时间。它还将执行未包含的初始运行,以忽略启动时间。 (注意:它支持一次性模式)

所以这个

int x;
using (var iterationDebugTimer = new IterationDebugTimer("Add", 100000))
{
    iterationDebugTimer.Run(() =>
    {
        x = 1+4;
    });
}

将输出此

Add: Iterations 100000 
Total 1.198540 ms 
Single 0.000012 ms

答案 4 :(得分:4)

使用真正的分析器,例如dotTrace。

答案 5 :(得分:4)

您可以使用Stopwatch,假设您使用的是.NET 2.0或更高版本。

System.Diagnostics.Stopwatch.StartNew();

Stopwatch课程还有公开的只读字段IsHighResolution,可让您知道秒表是否基于高分辨率的效果计数器。如果不是,它基于系统计时器。

我不确定秒表是基于高分辨率性能计数器的。有一些API调用,但我想如果秒表不使用高分辨率,那么API可能不存在。

答案 6 :(得分:4)

Stopwatch

的示例
    using System.Diagnostics;
    ......
    ...
    ..
    Stopwatch sw = new Stopwatch();
    sw.Start();
    //Your Code Here

    sw.Stop();
    Console.WriteLine("Elapsed={0}",sw.Elapsed);

答案 7 :(得分:3)

请参阅Is DateTime.Now the best way to measure a function’s performance?的答案以获取解释或阅读我的blog post about high performance measurement

问题是DateTime的分辨率大约是15ms,它不能比那更精确。但是,秒表可以。

答案 8 :(得分:3)

以下是关于如何Implement a Continuously Updating, High-Resolution Time Provider for Windows

在MSDN上的一篇很好的文章

这是文章(C ++)的sample source code

答案 9 :(得分:3)

https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/microbenchmarking.html

https://github.com/PerfDotNet/BenchmarkDotNet

&#34;确实,微博标记很难。如果操作需要10-100ns,则操作测量是一个很大的挑战。我建议你使用BenchmarkDotNet作为你的基准测试。这是一个图书馆,可以帮助您制定一个诚实的基准,并获得精确的测量。当然,您可以编写自己的基准而无需任何其他库。在本节中,我们将讨论为什么它可能是一个坏主意以及在开始之前你应该知道什么。&#34;

答案 10 :(得分:1)

我的偏好适用于@Evgeniy提到的BenchmarkDotNet。可能因为没有代码片段而绕开了他的答复,但是由于这是一个复杂的项目,因此至少值得先看看库,然后再开始定制。

由于某些代码总是引人注目,因此这是引用网站中的示例:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    [ClrJob(baseline: true), CoreJob, MonoJob, CoreRtJob]
    [RPlotExporter, RankColumn]
    public class Md5VsSha256
    {
        private SHA256 sha256 = SHA256.Create();
        private MD5 md5 = MD5.Create();
        private byte[] data;

        [Params(1000, 10000)]
        public int N;

        [GlobalSetup]
        public void Setup()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Md5VsSha256>();
        }
    }
}

答案 11 :(得分:1)

一次性样式Stopwatch最适合我。

class VWatch : IDisposable {
    Stopwatch watch = new Stopwatch();
    public VWatch() {
        this.watch.Start();
    }
    public void Dispose() {
        this.watch.Stop();
        Console.WriteLine("Finished. Elapsed={0}", this.watch.Elapsed);
    }
}

然后:

using (new VWatch()) {
    /// do something for time measurement
}

答案 12 :(得分:1)

另一种选择是使用Fody自动插入计时器代码。这使您的代码更容易阅读,因为它分离了您的跨领域问题。我认为这与所谓的Aspect Oriented Programming很接近,但是在编译后的时间完成。

有关进行方法计时的fody插件,请参阅https://github.com/Fody/MethodTimer

引用自述文件:

使用拦截器,在程序集中的某个位置:

public static class MethodTimeLogger {
  public static void Log(MethodBase methodBase, long milliseconds)
  {
    //Do some logging here
  } 
}

您的代码,

public class MyClass
{
    [Time]
    public void MyMethod()
    {
        //Some code u are curious how long it takes
        Console.WriteLine("Hello");
    }
}

编译为:

public class MyClass
{
    public void MyMethod()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            //Some code u are curious how long it takes
            Console.WriteLine("Hello");
        }
        finally
        {
            stopwatch.Stop();
            MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.ElapsedMilliseconds);
        }
    }
}

答案 13 :(得分:1)

此代码项目文章介绍了如何使用高性能计时器来测量代码的执行速度:

http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx

在这里您可以找到许多开源C#剖析器:

http://csharp-source.net/open-source/profilers

答案 14 :(得分:0)

我做了一个从ticks返回毫秒的扩展名。

public static int GetTotalRunningTimeInMilliseconds(this DateTime start)
{
    var endTicks = DateTime.Now.Ticks - start.Ticks;
    return TimeSpan.FromTicks(endTicks).Milliseconds;
}

用法:

 var start = DateTime.Now;

 //...your long running code here

 var endTime = start.GetTotalRunningTimeInMilliseconds();

答案 15 :(得分:0)

我已经做了一个非常简单的方法来测量Action的执行速度,这对我来说有好处,我可以在需要时重复使用它,以及我必须测量的任何代码。

对我来说,DateTime就足够了,但它很容易适应从DateTime到Stopwatch

public static TimeSpan MeasureTime(Action action)
{
    DateTime start = DateTime.Now;

    if (action == null)
    {
        throw new ArgumentNullException("action");
    }

    try
    {
        action();
    }
    catch (Exception ex)
    {
        Debugger.Log(1, "Measuring",ex.ToString());
    }

    return DateTime.Now - start;
}

如何使用?:

private static void StressTest()
{
    List<TimeSpan> tss = new List<TimeSpan>();

    for (int i = 0; i < 100; i++)
    {
        // here is the measuring:
        var ts = MeasureTime(() => instance.Method("param1"));

        tss.Add(ts);
    }

    Console.WriteLine("Max: {0}", tss.Max());
    Console.WriteLine("Min: {0}", tss.Min());
    Console.WriteLine("Avg: {0}", TimeSpan.FromMilliseconds(tss.Average(i => i.TotalMilliseconds)));
}

或:

var ts = MeasureTime(() =>
                        {
                            // Some intensive stuff here
                            int a = 1;

                            // more here
                            int b = 2;

                            // and so on
                        });

答案 16 :(得分:0)

有时最好查看为什么需要计时操作?它运行缓慢吗?或者你只是好奇吗?优化的第一条规则是“不要这样做”。因此,根据您实际测量的内容,可能会改变对哪种工具最适合该任务的意见。

答案 17 :(得分:-1)

为了测量测量之间差异的性能,我使用这个类。 StopWatch类没有Split方法。

/// <summary>
/// Stopwatch like class that keeps track of timelapses.
/// Probably low-res because of the usage of DateTime.
/// </summary>
public class ChronoMeter
{
    /// <summary>
    /// The name given when the Chronometer was constructed.
    /// </summary>
    public string Name { get; private set; }
    /// <summary>
    /// The moment in time Start was called.
    /// </summary>
    public DateTime Started { get; private set; }

    /// <summary>
    /// All time recordings are added to this list by calling Split and Stop.
    /// </summary>
    public List<ChronoRecord> Records { get; private set; }

    private readonly Stopwatch _stopWatch = new Stopwatch();

    private bool _hasBeenStopped = false;

    /// <summary>
    /// Constrcutor
    /// </summary>
    /// <param name="pName">The name is used in logging</param>
    /// <param name="pLoggingType">The type of logging appriate for the information yielded by this time recording.</param>
    public ChronoMeter(string pName)
    {
        Name = pName;
        Records = new List<ChronoRecord>();
    }

    /// <summary>
    /// Not calling Stop is bad practise. Therefore a little safety net zo the end is still recorderd.
    /// Keep in mind that the garbase collector invokes the destructor, so the moment of time probably doesn't make much sense.
    /// It is more to notify that you should have used Stop for the latest split.
    /// </summary>
    ~ChronoMeter()
    {
        if (!_hasBeenStopped)
        {
            Stop("Destructor safety net");
        }
    }

    /// <summary>
    /// TimeElapsedSinceStart of a ChronoRecord is relative to the moment ChronoMeter was started by calling this function.
    /// </summary>
    public void Start()
    {
        _stopWatch.Start();
        Started = DateTime.Now;
    }

    /// <summary>
    /// Splits the timerecording and add a record of this moment to the list of split records.
    /// </summary>
    /// <param name="pSplitName"></param>
    public void Split(string pSplitName)
    {
        _stopWatch.Stop();
        var created = Started + _stopWatch.Elapsed;
        var previousRecord = Records.LastOrDefault();
        Records.Add(new ChronoRecord(pSplitName, Started, created, previousRecord));
        _stopWatch.Start();
    }

    /// <summary>
    /// Indicates you are done and the records will be written to the log.
    /// </summary>
    public void Stop(string pSplitName)
    {
        Split(pSplitName);
        _stopWatch.Stop();
        _hasBeenStopped = true;
    }

    public class ChronoRecord
    {
        public string Name { get; private set; }
        public TimeSpan TimeElapsedSinceStart { get; private set; }
        public TimeSpan TimeElapsedSincePrevious { get; private set; }
        public DateTime Start { get; private set; }
        public DateTime Created { get; private set; }

        public ChronoRecord(string pName, DateTime pStartDateTime, DateTime pCreated, ChronoRecord pPreviousRecord=null)
        {
            if (pCreated == default(DateTime)) //Ignore DefaultDateTimeComparison
            {
                pCreated = DateTime.Now;
            }
            Created = pCreated;
            Name = pName;
            Start = pStartDateTime;

            TimeElapsedSinceStart = Created - Start;
            if (pPreviousRecord != null)
            {
                TimeElapsedSincePrevious = Created - pPreviousRecord.Created;
            }
            else
            {
                TimeElapsedSincePrevious = TimeElapsedSinceStart;
            }
        }
    }
}