简述:BenchmarkDotNet是一个用于进行性能基准++测试++的开源库,可以帮助开发者在.NET 应用程序中测试代码性能。
一、常用参数(特性)详解:
1. 基准方法标记
-
[Benchmark]:-
作用:最重要的特性,标记一个方法为基准测试方法。只有被此特性标记的方法才会被测量。
-
常用参数:
-
Description: 为方法提供自定义描述,会显示在结果表中。 -
Baseline: 设置为true将该方法设为基线。其他方法的Ratio列将会与这个基线方法进行比较。
-
-
cs
[Benchmark(Description = "My Awesome Sort", Baseline = true)]
public void MyAlgorithm() { ... }
2. 参数化基准测试
-
[Params]:-
作用 :用于参数化测试。你可以定义一个属性,并用
[Params]指定一组值。BenchmarkDotNet 会为每个参数值组合生成一个单独的测试用例。这是最强大的功能之一,用于回答"我的算法在不同数据量下的表现如何?"这类问题。 -
示例:
-
cs
public class MyBenchmarks
{
[Params(100, 1000, 10000)] // 测试3种不同规模的数据
public int DataSize { get; set; }
private int[] _data;
[GlobalSetup]
public void Setup()
{
// 根据 DataSize 生成测试数据
_data = new int[DataSize];
// ... 初始化数据
}
[Benchmark]
public void TestMethod()
{
// 使用 _data 进行测试
}
}
3.设置与清理方法
-
[GlobalSetup]:- 作用 :标记一个方法,该方法会在整个测试类中所有基准方法运行前 ,仅执行一次。常用于初始化昂贵的资源(如创建大型数组、建立数据库连接)。
-
[GlobalCleanup]:- 作用 :与
GlobalSetup对应,在所有测试完成后执行一次,用于清理资源。
- 作用 :与
-
[IterationSetup]:- 作用 :标记一个方法,该方法会在每一次基准迭代(Iteration)前执行。常用于重置状态,例如将测试数组恢复到未排序的状态。
-
[IterationCleanup]:- 作用:在每一次基准迭代后执行。
最佳实践 :在排序测试中,我们在
[GlobalSetup]中生成原始数据,在[IterationSetup]中拷贝原始数据到工作数组,以确保每次迭代的初始条件完全一致。
cs
private int[] _originalData;
private int[] _workData;
[GlobalSetup]
public void GlobalSetup()
{
// 生成一次原始数据
_originalData = GenerateTestData(1000);
}
[IterationSetup]
public void IterationSetup()
{
// 每次迭代前都拷贝一份原始数据,保证起点相同
_workData = (int[])_originalData.Clone();
}
[Benchmark]
public void Sort() => Array.Sort(_workData);
4. 诊断器 (Diagnosers)
-
作用:用于收集基准测试之外的额外诊断信息。
-
[MemoryDiagnoser]:-
最重要的诊断器之一 。为结果表添加两列:
Gen 0和Allocated。 -
Allocated: 该方法一次执行所分配的内存总量(字节/B, 千字节/KB)。 -
Gen 0: 执行该方法导致的 Generation 0 垃圾回收次数。 -
示例 :
Allocated: 1.02 KB和Gen 0: 0.4565表明该方法每次调用分配了约 1KB 内存,平均每执行两次多会触发一次 Gen 0 GC。
-
-
其他诊断器 :还可以通过 NuGet 包添加
DisassemblyDiagnoser(查看生成的汇编代码)、EventPipeProfiler(生成 CPU 火焰图)等高级诊断工具。
5. 任务配置 (Jobs)
-
作用:控制基准测试的运行环境,例如 .NET 运行时版本、JIT 编译器模式、平台等。
-
常用配置:
-
[SimpleJob]/[ShortRunJob]:-
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]: 指定在 .NET 6 环境下运行。 -
[ShortRunJob]: 非常常用。它配置了一个预设的"快速运行"任务,迭代次数较少,适用于快速验证和开发阶段的调试。正式测量性能时应移除它以获得更准确的结果。
-
-
cs
// 同时测试 .NET 6 和 .NET 8 下的性能差异
[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.Net80)]
[MemoryDiagnoser]
public class MyBenchmarks { ... }
二、输出结果:
运行基准测试后,你会得到一个详细的表格,包含以下关键列:

我们可以用该工具测试下冒泡排序和快速排序的算法性能比较,Console窗口结果输出如下:

,我们也可以使用**[HtmlExporter]**等特性设置结果输出格式,目前支持的Html,csv,markdown,json,xml等。
测试样例代码:
cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using System;
using System.Linq;
namespace SortingBenchmarks
{
[ShortRunJob]
//[HtmlExporter]
[MemoryDiagnoser] // 内存诊断器,分析内存分配
public class SortingBenchmark
{
// 使用 Params 特性来测试不同规模的数据
[Params(100, 1000, 5000)]
public int ArraySize { get; set; }
private int[] _originalArray;
private int[] _testArray;
private Random _random;
// 全局初始化,每个参数组合执行一次
[GlobalSetup]
public void Setup()
{
_random = new Random(42); // 固定种子确保每次生成的数据一致
_originalArray = new int[ArraySize];
for (int i = 0; i < ArraySize; i++)
{
_originalArray[i] = _random.Next();
}
}
// 每次迭代前拷贝数据,确保每次排序的初始数据相同
[IterationSetup]
public void IterationSetup()
{
_testArray = (int[])_originalArray.Clone();
}
[Benchmark(Baseline = true, Description = "QuickSort")]
public void QuickSort()
{
void Sort(int left, int right)
{
if (left < right)
{
int pivot = _testArray[right];
int i = left - 1;
for (int j = left; j < right; j++)
{
if (_testArray[j] <= pivot)
{
i++;
int temp1 = _testArray[i];
_testArray[i]= _testArray[j];
_testArray[j]= temp1;
}
}
int temp2 = _testArray[i+1];
_testArray[i + 1] = _testArray[right];
_testArray[right]=temp2;
int pivotIndex = i + 1;
Sort(left, pivotIndex - 1);
Sort(pivotIndex + 1, right);
}
}
Sort(0, _testArray.Length - 1);
}
[Benchmark(Description = "BubbleSort")]
public void BubbleSort()
{
int n = _testArray.Length;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (_testArray[j] > _testArray[j + 1])
{
// 交换元素
int temp = _testArray[j];
_testArray[j] = _testArray[j + 1];
_testArray[j + 1] = temp;
}
}
}
}
}
class Program
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<SortingBenchmark>();
}
}
}