【问题标题】:C#: Is this benchmarking class accurate?C#:这个基准测试类准确吗?
【发布时间】:2010-12-03 04:56:13
【问题描述】:

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

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

有什么反馈吗?它看起来是不是相当稳定和准确,还是我遗漏了什么?

【问题讨论】:

  • +1 非常原始的实现。我会尝试它来衡量它是否准确足够用于我所做的即插即用测试类型。谢谢!
  • 你能不能只使用var avg = Benchmark.This(SomeMethod).Take(500).Average(); 而不是使用lambda?
  • 在这个例子中我实际上可以,是的。只是这样写是为了更清楚地表明我正在发送一个可能是 lambda 或任何东西的 Action。
  • 如果您使用的是 VS 2010 beta2,并将 Svish 的代码复制到由 Project/Add Class 创建的新类中:请注意对使用 System.Collections 的引用;不会自动添加到课程中...您将需要它。
  • 您可以使用这个开源框架:BenchmarkDotNet。它包括秒表使用、GC 预调用、预热、设置进程优先级、thred、ProcessorAffinity-mask、用于基准竞赛的 api 以及带有结果的漂亮控制台输出。

标签: c# benchmarking feedback


【解决方案1】:

它与简单的基准测试一样准确。但有些因素不是你能控制的:

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

您可以对最后一点做点什么,基准测试是可以捍卫调用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 毫秒)的分辨率。

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

【讨论】:

  • 在收集之后等待待处理的终结器不是一个坏主意。请记住,终结器在不同的线程上运行;当前运行进行时,前一次运行的终结器可能正在另一个线程上运行。
  • 所有好主意。我已经在课堂上实现了它们。我唯一改变的是只返回 TimeSpan 而不是 ElapsedMillisecondsTicks :)
【解决方案2】:

您绝对应该返回 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 值代表一个 100 纳秒间隔。每个打勾 ElapsedTicks 值表示 时间间隔等于 1 秒 除以频率。

除此之外,我想您的代码很好,尽管我认为您会在测量中包含一些方法调用开销,如果方法本身执行时间很短,这可能会很重要。此外,您可能希望从计算的平均值中排除对该方法的第一次调用,但我不确定您在课堂上如何做到这一点。

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

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

【讨论】:

  • 您对方法调用开销有所了解。不过,我真的无法摆脱它……我认为……无论如何,它不应该太多,而且我现在使用它的方法要么太快,以至于我不在乎它是否不准确, 或者这么长以至于方法调用开销相比之下很小:p
  • @Svish:方法调用的开销大得惊人。要查看差异,请尝试计时循环,将两个数字内联与将两个数字作为参数传递给方法并在该方法内相乘。
  • +1 表示 ElapsedTicks 诉 Elapsed.Ticks。另外,您是否尝试过使用百分位数来清除“边缘”案例?例如90% 的迭代达到或低于阈值。请参阅techbookreport.com/tutorials/quantiles.html,尽管我作弊并且只使用 Excel。 :)
  • @Zach:我以通常的方式了解了 ElapsedTicks 与 Elapsed.Ticks - 通过在 StackOverflow 上被拍打。就个人而言,我通过使用“眼间冲击测试”(又名“它正好击中你的眼睛”)来消除异常值。 :)
  • @Joren:使用 ElapsedTicks 不一定是错误。但是,这是一个用于基准测试的类,因此如果该类的用户将 ElapsedTicks 值转储到 TimeSpan 以计算经过的持续时间,几乎可以肯定最终会出错。我应该说“这可能是 .Net 中最微妙的潜在错误之一”。事实上,我这么说。
【解决方案3】:

这里有几个问题。

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

其次,请记住垃圾收集器在另一个线程上运行。如果您在一次运行中制造垃圾,那么清理垃圾的成本可能要到后续运行才能实现。因此,您没有考虑一次运行的总成本,而是将其强加到以后的运行中。

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

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

    • 通常编译的 .Net 应用程序不是本机 EXE,但无论如何您的观点都非常有效。在 Visual Studio 中以调试模式运行的应用程序通常会比正常启动的 .Net EXE 运行得慢,而且函数在第一次被调用时通常会运行得更慢(尤其是当它们调用必须加载的其他程序集时) ,因此第一次调用方法而不将其运行时间包括在平均测量中是有意义的。
    • 好主意,应该对其进行编辑,以便在开始产生结果之前运行该方法一次:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-02
    • 2017-11-07
    • 2011-07-18
    • 1970-01-01
    • 2014-09-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多