BenchmarkDotNet

最近看到一个讨论,是关于Linq和Foreach具体哪个更快的话题?本文倒不是讨论linq查询和foreach遍历孰快孰慢的问题,而是要谈一谈其性能测试用到的工具Benchmark。Benchmark简称基准测试,可以帮助我们测试计算机程序的性能,打个比方,你决定优化你的代码,但是你又不确定是不是做了反向优化,这时你就可以使用基准测试帮助你决策。

.Net core的基准测试库:https://benchmarkdotnet.org/

快速上手

我们就以List的两种查询方式为例,来看看linq和foreach遍历查询那个速度更快。

Step-1

从Nuget中引入BenchmarkDotNet

Step-2

编写测试类BenchmarkDemo,向List中插入1W条数据,分别向要测试的方法添加Benchmark特性,注意基准测试类必须是Public的。

using BenchmarkDotNet.Attributes;
using System.Collections.Generic;
using System.Linq;

namespace SnailCode.CloudPlat.Extension.AppRun
{
    /// <summary>
    /// 性能基准测试。
    /// </summary>
    public class BenchmarkDemo
    {
        public List<string> _listArr = new List<string>();

        /// <summary>
        /// 初始化插入10000条数据
        /// </summary>
        public BenchmarkDemo()
        {
            for (int i = 0; i < 10000; i++)
            {
                _listArr.Add("hello");
            }
            _listArr.Add("word");
        }


        /// <summary>
        /// Linq查询。
        /// </summary>
        /// <returns></returns>
        [Benchmark]
        public string LinqLoop()
        {
            return _listArr.First(p => string.Equals("word", p));
        }

        /// <summary>
        /// Foreach查询。
        /// </summary>
        /// <returns></returns>
        [Benchmark]
        public string ForeachLoop()
        {
            foreach (var l in _listArr)
            {
                if (string.Equals("word", l))
                    return l;
            }
            return string.Empty;
        }
    }
}

添加性能测试逻辑并运行:

using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using System;
using System.Text;

namespace SnailCode.CloudPlat.Extension.AppRun
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("开始执行");
            var summary = BenchmarkRunner.Run<BenchmarkDemo>(new DebugInProcessConfig());
            Console.WriteLine("执行完毕");
            Console.ReadLine();
        }
    }
}

基准运行结果如下:

从结果上我们可以看到,上诉的两个方法中ForeachLoop确实比LinqLoop更快一些,其测试结果存储在.\BenchmarkDotNet.Artifacts\results目录下。

常见用例

测试代码在.NET6和.NET7下的性能差异,只需要添加如下标注:

    [SimpleJob(RuntimeMoniker.Net70)]
    [SimpleJob(RuntimeMoniker.Net60)]
    public class BenchmarkDemo

通过运行结果我们可以看到foreach循环在.NET7.0里似乎做了很大优化,性能提升了近一倍。

如果我们需要测试内存,则可以通过如下方式:

    [MemoryDiagnoser]
    public class BenchmarkDemo

则输出结果中就包含了内存信息:

注意事项

  1. Benchmark推荐使用Release模式来进行基准测试,而非本人示例中的Debug模式,因为调试版本的运行速度可能会慢10~100倍。
  2. 不同环境运行的结果有很大差异,比如示例中的foreach循环在.NET7和.NET6下的运行速度有天壤之别。
  3. 避免代码消除,比如你定义了对象却从不使用,那么编译器就可能消除此代码
    void Foo()
    {
        Math.Exp(1);
    }

最好的方式是这样:

double Foo()
{
    return Math.Exp(1);
}
posted @ 2024-06-28 13:33  猫探长  阅读(9)  评论(0编辑  收藏  举报