在C#中对小代码样本进行基准测试,这个实现能否改进?

115

在 SO 上,我经常会对小代码块进行基准测试,以确定哪个实现方式最快。

经常会看到评论说基准测试代码没有考虑到即时编译器和垃圾回收器。

我有以下简单的基准测试函数,并且已经逐步完善:

  static void Profile(string description, int iterations, Action func) {
        // warm up 
        func();
        // clean up
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }

用法:

Profile("a descriptions", how_many_iterations_to_run, () =>
{
   // ... code being profiled
});

这种实现方式有什么缺陷吗?它足够好以显示实现X在Z次迭代中比实现Y更快吗?您能想到任何改进的方法吗?

编辑 很明显,基于时间的方法(而不是迭代次数)更受欢迎,有人有任何实现,其中时间检查不会影响性能吗?


请参见BenchmarkDotNet - Ben Hutchison
11个回答

0
你的问题基本上存在一个假设,即单次测量可以回答你所有的问题。你需要多次测量才能有效地了解情况,特别是在像C#这样的垃圾收集语言中。
另一个答案提供了一种衡量基本性能的可行方法。
static void Profile(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

然而,这个单一的度量并没有考虑到垃圾回收。一个合适的性能分析还要考虑到垃圾回收在多次调用中的最坏情况性能(这个数字有点无用,因为虚拟机可以在不收集剩余垃圾的情况下终止,但对于比较两个不同的func实现仍然很有用)。
static void ProfileGarbageMany(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

有时候,人们也想要测量仅被调用一次的方法的垃圾回收的最坏情况性能。

static void ProfileGarbage(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

但比推荐任何特定的可能的额外测量更重要的是,应该测量多种不同的统计数据,而不仅仅是一种统计数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接