35

我创建了一个简单的类来对我的一些方法进行基准测试。但它准确吗?我对基准测试、时间安排等有点陌生,所以我想我可以在这里寻求一些反馈。另外,如果它很好,也许其他人也可以使用它:)

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

4 回答 4

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字段。如果它关闭,则只有非常粗略的(20 毫秒)分辨率。

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

于 2009-10-02T09:35:37.570 回答
10

这里有几个问题。

首先,请记住,第一次运行代码时,其方法调用的传递闭包会被 jitted。这意味着第一次运行的成本可能比随后的每次运行都高。根据您是对“冷”时间还是“热”时间进行基准测试,这可能会有所不同。我见过一些方法,其中 jitting 方法的成本比其他所有调用的成本加起来都要高!

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

这两者都表明了所有基准测试的弱点:基准测试本质上是不切实际的,因此价值有限。在实际代码中,GC 将运行,抖动将运行,等等。通常情况下,基准性能与实际性能完全不同,因为基准没有考虑到大型系统中固有的实际成本的可变性。与其孤立地分析性能特征,我更喜欢看真实客户实际面临的现实场景的性能特征。

于 2009-10-02T17:07:24.683 回答
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 秒(即seconds,而不是毫秒),而在其他机器上,这种漂移可能更大。所以说它是高度准确的有点误导,而实际上它只是高度精细的。对于时序短时方法,这显然不是一个重大问题。

最后一点,当然相关的:我经常在基准测试时注意到,我会得到一堆运行时间,它们都聚集在一个狭窄的值范围内(例如 80、80、79、82 等)。 ,但偶尔会在 Windows 中发生其他事情(例如打开另一个程序或我的防病毒程序启动或其他事情),我会得到一个与其他人完全不同的值(例如 80、80、79、271、80 等.)。我认为这个异常值问题的一个简单解决方案是使用您的测量值的中位数而不是平均值。我不知道 Linq 是否自动支持此功能。

于 2009-10-02T02:21:50.283 回答
2

由于我不是 C# 程序员,因此我无法准确地说该类是否适合计算函数执行所需的时间。但是,为了可重复性和准确性,需要注意一些事项。

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

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

于 2009-10-02T02:20:35.600 回答