【问题标题】:Exact time measurement for performance testing [duplicate]性能测试的精确时间测量[重复]
【发布时间】:2010-11-01 10:36:27
【问题描述】:

查看某事物(例如方法调用)在代码中占用多长时间的最准确方法是什么?

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

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

但这到底有多准确?有更好的方法吗?

【问题讨论】:

  • 您不会押注 .NET 类,因为您不知道它们是如何工作的?这是否意味着您也害怕使用 String 类?无论如何,Stopwatch 类的文档明确表示它正在使用 QueryPerformanceCounter() Win32 API 函数。
  • String 类与此无关。如果 Stopwatch 存在于 .NET 中,我怎么知道它比 QueryPerformanceCounter 更好?这是可以存在的最佳选择!
  • @pixel3cs:因为您在评论中受到批评而对正确答案投反对票不是很成熟
  • @pixel3cs 但是你有时间阅读 Kernel32 api 吗?

标签: c# .net performance testing


【解决方案1】:

我正在使用这个:

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();

【讨论】:

  • 介意分享变量定时器的类型以及如何读出经过的时间?
【解决方案2】:

正如其他人所说,Stopwatch 应该是解决此问题的正确工具。不过可以对其进行一些改进,具体请参见此线程:Benchmarking small code samples in C#, can this implementation be improved?

我看到了Thomas Maierhofer here的一些有用提示

他的代码基本上是这样的:

//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 保持忙碌的时间运行非常代码/进程as shown here 这可以反映更真实的情况,因为没有其他进程影响测量。它做了类似的事情:

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

samething can be found here. 的一个赤裸裸的详细实现

我编写了一个辅助类来以易于使用的方式执行这两个操作:

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 }(以毫秒或其他单位为单位),它会丢弃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);
}

【讨论】:

  • 关于控制环境和忽略尖峰的好细节!谢谢。
  • 在原始示例中,long seed = Environment.TickCount; 用作被测算法in order to make it non-deterministic 的输入,并防止在编译时对其进行评估。此处未使用该种子。
  • values.Deviations()method.. 怎么样? (也许我可以自己做,但专家的意见会很好)
【解决方案3】:

更好的方法是使用 Stopwatch 类:

using System.Diagnostics;
// ...

Stopwatch sw = new Stopwatch();

sw.Start();

// ...

sw.Stop();

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

【讨论】:

  • 如果您需要知道特定机器上秒表计时的分辨率,那么您可以使用 Stopwatch.Frequency 属性。
  • 另外,Stopwatch.StartNew() 静态方法是在单行上创建和启动秒表的便捷方式。
【解决方案4】:

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

【讨论】:

  • 好点,但仍然需要一些东西来花时间在这 10^6 次 :)
  • 把秒表放在整个事情上。我认为这很清楚。
【解决方案5】:

正如其他人所说,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() 的调用似乎会在第一次迭代中增加成本。如果可以在评论中解释,您能否解释一下这里发生了什么?!非常感谢..
  • @ppejovic:这可能是 JIT 编译的成本,或者可能是初始化 lambda 表达式使用的类的成本。这种“增加的成本”到底有多大?
  • @ppejovic:如果您正在调试,那么您应该完全忽略所有性能结果。但无论您是否在调试,都会发生 JIT 编译,只是使用不同的优化。
  • @NAKRO:那是因为你所说的“工作”只是“开始一项新任务”。这真的不会需要很长时间。所以是的,它确实给出了正确的结果,但是你没有测量你真正想要测量的东西。如果你想衡量任务完成需要多长时间,那么你需要等待它完成。
  • @NAKRO:你可以,但你需要确保所涉及的“动作”同时启动所有任务并等待它们完成
【解决方案6】:

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

【讨论】:

  • 查看上面问题的cmets,你就会明白为什么了。
【解决方案7】:

使用Stopwatch

【讨论】:

  • 反对者:我们很高兴知道出了什么问题!
  • 猜一猜,因为您几乎在同一时间回答了相同的问题,但描述较少。 (我没有投票赞成或反对你)
猜你喜欢
  • 2012-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 2018-03-17
  • 1970-01-01
相关资源
最近更新 更多