查看什么东西(例如方法调用)花费了多长时间的最准确方法是什么?

我猜最简单,最快的方法是:

DateTime start = DateTime.Now;
{
    // Do some work
}
TimeSpan timeItTook = DateTime.Now - start;


但是这个精确度是多少?有更好的方法吗?

评论

您不会押注.NET类,因为您不知道它们是如何工作的?这是否意味着您还害怕使用String类?无论如何,Stopwatch类的文档明确表示它正在使用QueryPerformanceCounter()Win32 API函数。

String类与此无关。如果.NET中存在Stopwatch,我怎么知道它比QueryPerformanceCounter更好?这是可以存在的最佳选择!!

@ pixel3cs:否决正确答案,因为您在评论中受到批评不是很成熟

@ pixel3cs但是您有时间阅读Kernel32 api吗?

#1 楼

更好的方法是使用秒表类:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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


评论


如果您需要了解特定计算机上秒表计时的分辨率,则可以使用Stopwatch.Frequency属性。

–LukeH
09年9月9日在11:03

另外,Stopwatch.StartNew()静态方法是在一行上创建和启动Stopwatch的便捷方法。

–́JessieArr
16 Nov 4 '18:55



MS文档

–UpTheCreek
17年6月2日在9:20

#2 楼

正如其他人所说,Stopwatch是在此处使用的一个很好的类。您可以将其包装为一种有用的方法:

public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}


(请注意Stopwatch.StartNew()的使用。我更喜欢创建秒表,然后为了简单起见调用Start()。)显然,这招致了调用代表的麻烦,但是在大多数情况下,这是无关紧要的。然后,您可以编写:

TimeSpan time = StopwatchUtil.Time(() =>
{
    // Do some work
});


您甚至可以为此创建一个ITimer接口,并在可用的地方实现StopwatchTimer, CpuTimer等。

评论


@JonSkeet在循环中调用此实用程序时(即根据第二个代码示例),似乎对action()的调用在第一次迭代时会增加成本。如果可以在注释中解释您的情况,请问您能解释一下吗?非常感谢..

–Pero P.
2011年10月7日在20:15

@ppejovic:这可能是JIT编译的开销,也可能是初始化lambda表达式使用的类的开销。 “增加的成本”到底有多大?

–乔恩·斯基特(Jon Skeet)
2011年10月7日在20:16

@ppejovic:如果要调试,则应完全忽略所有性能结果。但是,无论是否在调试,都将使用不同的优化进行JIT编译。

–乔恩·斯基特(Jon Skeet)
2011年10月7日在20:29

@NAKRO:那是因为您所说的“工作”仅仅是“开始一项新任务”。确实并不需要很长时间。是的,它的确给出了正确的结果,但是您并未测量您真正想要测量的东西。如果要测量任务完成需要多长时间,则需要等待任务完成。

–乔恩·斯基特(Jon Skeet)
2014年2月12日14:06

@NAKRO:可以,但是您需要确保所涉及的“动作”既启动了所有任务,又等待它们完成。

–乔恩·斯基特(Jon Skeet)
2014年2月12日14:11

#3 楼

正如其他人所说,Stopwatch应该是正确的工具。不过,几乎没有任何改进,请参见此线程:在C#中对小型代码示例进行基准测试,是否可以改进此实现?。

我在这里看到了Thomas Maierhofer的一些有用的提示

他的代码基本上像这样:

//prevent the JIT Compiler from optimizing Fkt calls away
long seed = Environment.TickCount;

//use the second Core/Processor for the test
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

//prevent "Normal" Processes from interrupting Threads
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

//prevent "Normal" Threads from interrupting this thread
Thread.CurrentThread.Priority = ThreadPriority.Highest;

//warm up
method();

var stopwatch = new Stopwatch()
for (int i = 0; i < repetitions; i++)
{
    stopwatch.Reset();
    stopwatch.Start();
    for (int j = 0; j < iterations; j++)
        method();
    stopwatch.Stop();
    print stopwatch.Elapsed.TotalMilliseconds;
}

另一种方法是依靠Process.TotalProcessTime来测量CPU一直忙于运行该代码的时间/如下所示,这可以反映出更真实的情况,因为没有其他过程会影响测量。它的功能类似于:

 var start = Process.GetCurrentProcess().TotalProcessorTime;
 method();
 var stop = Process.GetCurrentProcess().TotalProcessorTime;
 print (end - begin).TotalMilliseconds;


可以在此处找到相同内容的裸露详细实现。

我编写了一个帮助程序类,以一种易于使用的方式执行这两种操作:

public class Clock
{
    interface IStopwatch
    {
        bool IsRunning { get; }
        TimeSpan Elapsed { get; }

        void Start();
        void Stop();
        void Reset();
    }



    class TimeWatch : IStopwatch
    {
        Stopwatch stopwatch = new Stopwatch();

        public TimeSpan Elapsed
        {
            get { return stopwatch.Elapsed; }
        }

        public bool IsRunning
        {
            get { return stopwatch.IsRunning; }
        }



        public TimeWatch()
        {
            if (!Stopwatch.IsHighResolution)
                throw new NotSupportedException("Your hardware doesn't support high resolution counter");

            //prevent the JIT Compiler from optimizing Fkt calls away
            long seed = Environment.TickCount;

            //use the second Core/Processor for the test
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

            //prevent "Normal" Processes from interrupting Threads
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

            //prevent "Normal" Threads from interrupting this thread
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
        }



        public void Start()
        {
            stopwatch.Start();
        }

        public void Stop()
        {
            stopwatch.Stop();
        }

        public void Reset()
        {
            stopwatch.Reset();
        }
    }



    class CpuWatch : IStopwatch
    {
        TimeSpan startTime;
        TimeSpan endTime;
        bool isRunning;



        public TimeSpan Elapsed
        {
            get
            {
                if (IsRunning)
                    throw new NotImplementedException("Getting elapsed span while watch is running is not implemented");

                return endTime - startTime;
            }
        }

        public bool IsRunning
        {
            get { return isRunning; }
        }



        public void Start()
        {
            startTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = true;
        }

        public void Stop()
        {
            endTime = Process.GetCurrentProcess().TotalProcessorTime;
            isRunning = false;
        }

        public void Reset()
        {
            startTime = TimeSpan.Zero;
            endTime = TimeSpan.Zero;
        }
    }



    public static void BenchmarkTime(Action action, int iterations = 10000)
    {
        Benchmark<TimeWatch>(action, iterations);
    }

    static void Benchmark<T>(Action action, int iterations) where T : IStopwatch, new()
    {
        //clean Garbage
        GC.Collect();

        //wait for the finalizer queue to empty
        GC.WaitForPendingFinalizers();

        //clean Garbage
        GC.Collect();

        //warm up
        action();

        var stopwatch = new T();
        var timings = new double[5];
        for (int i = 0; i < timings.Length; i++)
        {
            stopwatch.Reset();
            stopwatch.Start();
            for (int j = 0; j < iterations; j++)
                action();
            stopwatch.Stop();
            timings[i] = stopwatch.Elapsed.TotalMilliseconds;
            print timings[i];
        }
        print "normalized mean: " + timings.NormalizedMean().ToString();
    }

    public static void BenchmarkCpu(Action action, int iterations = 10000)
    {
        Benchmark<CpuWatch>(action, iterations);
    }
}


只需调用

Clock.BenchmarkTime(() =>
{
    //code

}, 10000000);




Clock.BenchmarkCpu(() =>
{
    //code

}, 10000000);


Clock的最后一部分是棘手的部分。如果要显示最终时间,则由您自己选择所需的时间。我写了一个扩展方法NormalizedMean,它为您提供了读取时序的平均值,从而消除了噪声。我的意思是我计算出每个时序与实际平均值的偏差,然后丢弃与偏差均值(称为绝对偏差)相距较远(仅较慢的值)的值(请注意,它不是经常听到的标准偏差) ,最后返回剩余值的平均值。这意味着,例如,如果定时值为{ 1, 2, 3, 2, 100 }(以ms或其他形式),它将丢弃100,并返回{ 1, 2, 3, 2 }的均值2。或者,如果计时为{ 240, 220, 200, 220, 220, 270 },则丢弃270,并返回{ 240, 220, 200, 220, 220 }的均值220

public static double NormalizedMean(this ICollection<double> values)
{
    if (values.Count == 0)
        return double.NaN;

    var deviations = values.Deviations().ToArray();
    var meanDeviation = deviations.Sum(t => Math.Abs(t.Item2)) / values.Count;
    return deviations.Where(t => t.Item2 > 0 || Math.Abs(t.Item2) <= meanDeviation).Average(t => t.Item1);
}

public static IEnumerable<Tuple<double, double>> Deviations(this ICollection<double> values)
{
    if (values.Count == 0)
        yield break;

    var avg = values.Average();
    foreach (var d in values)
        yield return Tuple.Create(d, avg - d);
}


评论


有关控制环境和忽略峰值的详细信息!谢谢。

– asgerhallas
2013年6月7日18:45

在原始示例中,长种子= Environment.TickCount;用作测试中算法的输入,以使其不确定,并防止在编译时对其进行评估。该种子不在此处使用。

– Piedar
19年11月7日在16:55

那values.Deviations()方法呢? (也许我可以自己做,但专家的意见会很好)

–埃里克·塞塞尔(Erik Thysell)
20年5月6日在7:08

#4 楼

使用秒表类

评论


Downvoters:我们很高兴知道出了什么问题!

–mmx
2009年6月9日在11:32

猜猜是因为您几乎在同一时间以较少的描述回答了相同的问题。 (我没有赞成或反对你)

–stephanmg
20-2-14在3:25



#5 楼

System.Diagnostics.Stopwatch是为此任务而设计的。

评论


查看上面问题的评论,您会明白为什么。

– Philippe Leybaert
09年9月9日12:35

#6 楼

秒表很好,但是将工作循环10 ^ 6次,然后除以10 ^ 6。
您将获得更高的精度。

评论


好点,但仍然需要一些时间来花费那10 ^ 6次:)

–Svish
09年10月10日在6:53

将秒表放在整个事情上。我以为很清楚。

–迈克·邓拉维(Mike Dunlavey)
09年6月10日在11:19

#7 楼

我正在使用:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(myUrl);
System.Diagnostics.Stopwatch timer = new Stopwatch();

timer.Start();

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

statusCode = response.StatusCode.ToString();

response.Close();

timer.Stop();


评论


介意共享可变计时器的类型,以及如何读出经过的时间?

–温纳·亨泽(Werner Henze)
16-2-3在12:38