性能测试的准确时间测量

Svi*_*ish 307 .net c# testing performance

查看某些内容(例如方法调用)接受代码的最精确方式是什么?

我猜的最容易和最快的是:

DateTime start = DateTime.Now;
{
    // Do some work
}
TimeSpan timeItTook = DateTime.Now - start;
Run Code Online (Sandbox Code Playgroud)

但这有多精确?还有更好的方法吗?

Phi*_*ert 568

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

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

Console.WriteLine("Elapsed={0}",sw.Elapsed);
Run Code Online (Sandbox Code Playgroud)

  • 此外,Stopwatch.StartNew()静态方法是在一行上创建和启动秒表的便捷方式. (26认同)
  • 如果您需要知道特定机器上秒表计时的分辨率,那么您可以使用Stopwatch.Frequency属性. (5认同)
  • [MS文档](https://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch(v = vs.110).aspx) (5认同)
  • 很酷,不知道这个班级是否存在. (2认同)

Jon*_*eet 166

正如其他人所说,Stopwatch在这里使用是一个很好的课程.你可以用一个有用的方法包装它:

public static TimeSpan Time(Action action)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    action();
    stopwatch.Stop();
    return stopwatch.Elapsed;
}
Run Code Online (Sandbox Code Playgroud)

(注意使用Stopwatch.StartNew().我更喜欢这个来创建一个秒表然后调用Start()简单.)显然这会引起调用委托,但在绝大多数情况下都不相关.然后你写:

TimeSpan time = StopwatchUtil.Time(() =>
{
    // Do some work
});
Run Code Online (Sandbox Code Playgroud)

您甚至可以ITimer为此创建一个接口,StopwatchTimer, CpuTimer其中包含可用的实现.

  • pixel3cs:听起来就像酸葡萄一样明显.要明确:你是否指责我投票欺诈?你是否指责我不是"真正的Jon Skeet"?至于为什么秒表比使用P/Invoke更好:它使用起来更简单,但得到相同的结果. (24认同)
  • @NAKRO:那是因为你所说的"工作"只是"开始一项新任务".这真的*不需要很长时间.所以是的,它确实*给出了正确的结果,但你没有衡量你真正想要衡量的东西.如果要测量任务完成所需的时间,则需要等待它完成. (8认同)
  • 指责像Jon Skeet这样的人投票欺诈是一种耻辱!像他这样的人必须因为他们为进步社区所做的事而受到尊重.不关心这些乔恩,人们爱你并信任你. (5认同)
  • @NAKRO:嗯,你可以,但是你需要确保所涉及的"动作"都启动所有任务*并等待它们完成*. (5认同)
  • 我认为真正的Jon Skeet没有时间在SO上发布答案。你们都在乎声誉,而不是最佳答案。一些SO用户可能有2个帐户,但他们从另一个帐户中为自己投票。同样,一些用户阅读最佳答案然后再使用相同的解决方案进行回答,范围是从其他人那里获得投票。有什么比QueryPerformanceFrequency更好的??? (2认同)
  • @ppejovic:如果你正在调试*那么你应该完全忽略所有的性能结果.但无论您是否正在调试JIT编译,只需进行不同的优化. (2认同)

naw*_*fal 76

正如其他人所说,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;
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是依靠Process.TotalProcessTime测量CPU保持忙于运行代码/进程的时间,如此处所示.这可以反映更真实的情况,因为没有其他进程影响测量.它做的事情如下:

 var start = Process.GetCurrentProcess().TotalProcessorTime;
 method();
 var stop = Process.GetCurrentProcess().TotalProcessorTime;
 print (end - begin).TotalMilliseconds;
Run Code Online (Sandbox Code Playgroud)

在这里可以找到相同的裸体,详细的实现.

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

打电话吧

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

}, 10000000);
Run Code Online (Sandbox Code Playgroud)

要么

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

}, 10000000);
Run Code Online (Sandbox Code Playgroud)

最后一部分Clock是棘手的部分.如果您想显示最终时间,可由您自行选择所需的时间.我写了一个扩展方法NormalizedMean,它给出了丢弃噪声的读取时间的平均值.我的意思是我计算每个时间与实际平均值的偏差,然后我从偏差的平均值(称为绝对偏差;请注意它不是经常听到的标准偏差)中丢弃更快的值(只有较慢的值) ,最后返回剩余值的平均值.这意味着,例如,如果定时值{ 1, 2, 3, 2, 100 }(以毫秒为单位或其他),它丢弃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);
}
Run Code Online (Sandbox Code Playgroud)

  • 在最初的示例中,“长种子=Environment.TickCount;”被用作被测试算法的输入,[为了使其具有不确定性](https://www.codeproject.com/Articles/61964/ Performance-Tests-Precise-Run-Time-Measurements-wi?msg=4548708#xx4548708xx) 并防止在编译时对其进行评估。此处未使用该种子。 (2认同)

Meh*_*ari 13

使用秒表课程

  • Downvoters:我们很高兴知道出了什么问题! (2认同)
  • 猜测是因为您几乎在同一时间回答了相同的问题,但描述较少。(我没有给你投赞成票或反对票) (2认同)

Dim*_*kis 13

System.Diagnostics.Stopwatch专为此任务而设计.


Mik*_*vey 5

秒表很好,但循环工作10 ^ 6次,然后除以10 ^ 6.你会得到更多的精确度.

  • 好点,但仍然需要花时间在那些10 ^ 6次:) (2认同)
  • 将秒表放在整个事物周围.我觉得很清楚. (2认同)