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
则输出结果中就包含了内存信息:
注意事项
- Benchmark推荐使用Release模式来进行基准测试,而非本人示例中的Debug模式,因为调试版本的运行速度可能会慢10~100倍。
- 不同环境运行的结果有很大差异,比如示例中的foreach循环在.NET7和.NET6下的运行速度有天壤之别。
- 避免代码消除,比如你定义了对象却从不使用,那么编译器就可能消除此代码
void Foo()
{
Math.Exp(1);
}
最好的方式是这样:
double Foo()
{
return Math.Exp(1);
}