C#:这个基准测试类准确吗?

时间:2009-10-02 01:47:39

标签: c# benchmarking feedback

我创建了一个简单的类来对我的一些方法进行基准测试。但它准确吗?我对基准测试,计时等等都不熟悉,所以我想在这里可以请一些反馈。此外,如果它是好的,也许其他人也可以使用它:)

public static class Benchmark
{
    public static IEnumerable<long> This(Action subject)
    {
        var watch = new Stopwatch();
        while (true)
        {
            watch.Reset();
            watch.Start();
            subject();
            watch.Stop();
            yield return watch.ElapsedTicks;
        }
    }
}

你可以像这样使用它:

var avg = Benchmark.This(() => SomeMethod()).Take(500).Average();

有任何反馈意见吗?它看起来是非常稳定和准确的,还是我错过了什么?

4 个答案:

答案 0 :(得分:21)

它与您可以获得的简单基准测试一样准确。但是有一些因素不在你的控制之下:

  • 从其他进程加载系统
  • 基准测试之前/期间的堆状态

你可以对最后一点做点什么,基准是可以防御调用GC.Collect的罕见情况之一。您可以事先调用subject一次以消除任何JIT问题。但这要求subject的呼叫是独立的。

public static IEnumerable<TimeSpan> This(Action subject)
{
    subject();     // warm up
    GC.Collect();  // compact Heap
    GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty

    var watch = new Stopwatch();
    while (true)
    {
        watch.Reset();
        watch.Start();
        subject();
        watch.Stop();
        yield return watch.Elapsed;  // TimeSpan
    }
}

对于奖金,您的班级应该检查System.Diagnostics.Stopwatch.IsHighResolution field。如果它关闭,则只有非常粗糙(20 ms)的分辨率。

但是在普通的PC上,许多服务在后台运行,它永远不会非常准确。

答案 1 :(得分:10)

在这里遇到问题。

首先,请记住,第一次运行代码时,其方法调用的传递闭包将被jitted。这意味着第一次运行的成本可能高于每次后续运行。根据您是否对“冷”时间或“热”时间进行基准测试,这可能会有所不同。我已经看到了一些方法,其中jitting方法的成本高于其他每次调用它的成本!

其次,请记住垃圾收集器在另一个线程上运行。如果你在一次运行中制造垃圾,那么直到后续运行才能实现清理垃圾的成本。因此,您无法将一次运行的总成本计算在后期运行中。

这两个都表明了所有基准测试的弱点:基准测试本质上是不现实的,因此价值有限。在实际代码中,GC将运行,抖动将继续运行,依此类推。通常情况下,基准性能与现实世界的性能完全不同,因为基准测试没有考虑大型系统固有的实际成本的可变性。我不是单独分析性能特征,而是更倾向于研究真实客户实际面临的现实场景的性能特征。

答案 2 :(得分:7)

你绝对应该返回ElapsedMilliseconds而不是ElapsedTicks。 ElapsedTicks返回的值取决于秒表频率,在不同系统上可能会有所不同。它不一定对应于Timespan或DateTime对象的Ticks属性。

请参阅http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx

如果你想要额外的Ticks分辨率,你应该返回watch.Elapsed.Ticks(即Timestamp.Ticks)而不是watch.ElapsedTicks(这可能是最细微的< .Net中的潜在错误)。来自MSDN:

  

秒表刻度不同于   DateTime.Ticks。每个蜱都在   DateTime.Ticks值表示一个   100纳秒间隔。每个勾选   ElapsedTicks值表示   时间间隔等于1秒   除以频率。

除此之外,我猜你的代码很好,虽然我认为你会在测量中包含一些方法调用开销,如果这些方法本身只需要很少的时间来执行,这可能很重要。此外,您可能希望从计算出的平均值中排除对该方法的第一次调用,但我不确定您是如何在班级中这样做的。

最后一点,这可能与此类的大多数用途无关:与系统时间相比,秒表运行速度有点快。在我的计算机上,它在24小时后提前约5秒(即,而不是毫秒),而在其他机器上,这种漂移可能更大。因此,当它实际上只是高度粒度时,说它高度准确会有点误导。对于定时短期方法,这显然不是一个重大问题。

还有一点,当然 相关:我经常注意到,在基准测试时,我会得到一堆运行时间,这些运行时间都聚集在一个狭窄的值范围内(例如80,80,79,82等),但偶尔会在Windows中发生其他事情(比如打开另一个程序或我的反病毒或其他东西)并且我会从其他人那里得到一个非常重要的价值(例如80,80,79,271,80等)。我认为这个异常问题的简单解决方案是使用测量的中值而不是 mean 。我不知道Linq是否自动支持。

答案 3 :(得分:2)

由于我不是C#程序员,我不能准确地说该类是否是计算函数执行所需时间的适当实现。但是,重复性和准确性需要牢记。

我不了解.NET Framework的各种细节,但取决于它如何编译为本机代码,任何编译都可能会影响基准测试结果。此外,功能是否在缓存中也会产生影响。因此,您需要遍历您的函数以确保编译没有命中,并且所有内容都已加载并准备就绪。一旦完成,您就可以开始了。

其他人可能拥有比我更好的.NET信息和知识。