乐于分享
好东西不私藏

[工具推荐].Net测试神器BenchmarkDotNet提升.NET应用性能

[工具推荐].Net测试神器BenchmarkDotNet提升.NET应用性能

在.NET开发中,性能优化是至关重要的环节。BenchmarkDotNet作为一款强大的开源基准测试库,能够帮助开发者准确测量代码的性能表现,从而有针对性地进行优化。本文将详细介绍BenchmarkDotNet的使用方法,助力.Net开发者提升应用性能。

BenchmarkDotNet简介

BenchmarkDotNet是专为.Net应用程序设计的开源基准测试库。它通过多次运行被测试的方法,收集统计数据,生成详细的报告,涵盖执行时间、内存使用等多方面信息。该库简单易用,可轻松集成到现有项目中,为开发者提供了全面的性能评估工具。

开始写基准测试很容易,看看下面的示例 

[SimpleJob(RuntimeMoniker.Net472, baseline: true)][SimpleJob(RuntimeMoniker.NetCoreApp30)][SimpleJob(RuntimeMoniker.NativeAot70)][SimpleJob(RuntimeMoniker.Mono)][RPlotExporter]publicclassMd5VsSha256{private SHA256 sha256 = SHA256.Create();private MD5 md5 = MD5.Create();privatebyte[] data;    [Params(1000, 10000)]publicint N;    [GlobalSetup]publicvoidSetup()    {        data = newbyte[N];new Random(42).NextBytes(data);    }    [Benchmark]publicbyte[] Sha256() => sha256.ComputeHash(data);    [Benchmark]publicbyte[] Md5() => md5.ComputeHash(data);}

BenchmarkDotNet 自动运行 运行所有运行时的基准测试, 汇总测量数据, 并打印一份包含最重要信息的汇总表:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.17763.805 (1809/October2018Update/Redstone5)Intel Core i7-7700K CPU 4.20GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores  [Host]       : .NET Framework 4.7.2 (4.7.3468.0), X64 RyuJIT  Net472       : .NET Framework 4.7.2 (4.7.3468.0), X64 RyuJIT  NetCoreApp30 : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT  NativeAot70  : .NET 7.0.0-preview.4.22172.7, X64 NativeAOT  Mono         : Mono 6.4.0 (Visual Studio), X64| Method |       Runtime |     N |       Mean |     Error |    StdDev | Ratio ||------- |-------------- |------ |-----------:|----------:|----------:|------:|| Sha256 |    .NET 4.7.2 |  1000 |   7.735 us | 0.1913 us | 0.4034 us |  1.00 || Sha256 | .NET Core 3.0 |  1000 |   3.989 us | 0.0796 us | 0.0745 us |  0.50 || Sha256 | NativeAOT 7.0 |  1000 |   4.091 us | 0.0811 us | 0.1562 us |  0.53 || Sha256 |          Mono |  1000 |  13.117 us | 0.2485 us | 0.5019 us |  1.70 ||        |               |       |            |           |           |       ||    Md5 |    .NET 4.7.2 |  1000 |   2.872 us | 0.0552 us | 0.0737 us |  1.00 ||    Md5 | .NET Core 3.0 |  1000 |   1.848 us | 0.0348 us | 0.0326 us |  0.64 ||    Md5 | NativeAOT 7.0 |  1000 |   1.817 us | 0.0359 us | 0.0427 us |  0.63 ||    Md5 |          Mono |  1000 |   3.574 us | 0.0678 us | 0.0753 us |  1.24 ||        |               |       |            |           |           |       || Sha256 |    .NET 4.7.2 | 10000 |  74.509 us | 1.5787 us | 4.6052 us |  1.00 || Sha256 | .NET Core 3.0 | 10000 |  36.049 us | 0.7151 us | 1.0025 us |  0.49 || Sha256 | NativeAOT 7.0 | 10000 |  36.253 us | 0.7076 us | 0.7571 us |  0.49 || Sha256 |          Mono | 10000 | 116.350 us | 2.2555 us | 3.0110 us |  1.58 ||        |               |       |            |           |           |       ||    Md5 |    .NET 4.7.2 | 10000 |  17.308 us | 0.3361 us | 0.4250 us |  1.00 ||    Md5 | .NET Core 3.0 | 10000 |  15.726 us | 0.2064 us | 0.1930 us |  0.90 ||    Md5 | NativeAOT 7.0 | 10000 |  15.627 us | 0.2631 us | 0.2461 us |  0.89 ||    Md5 |          Mono | 10000 |  30.205 us | 0.5868 us | 0.6522 us |  1.74 |

测量数据可以导出为多种格式(md、html、csv、xml、json 等),包括图表:

支持运行环境:.NET 5+、.NET Framework 4.6.1+、.NET Core 2.0+、Mono、NativeAOT支持语言:C#、F#、Visual Basic支持操作系统:Windows、Linux、macOS支持的架构:x86、x64、ARM、ARM64、Wasm 和 LoongArch64

特色

BenchmarkDotNet 拥有大量对全面性能调查至关重要的功能。 这些特征的设计由四个方面体现:简洁自动化可靠性友好性。

简洁性

如果你想写基准测试,不应该非得是有经验的性能工程师。 你可以用声明式风格设计非常复杂的性能实验,使用简单的 API。

例如,如果你想参数化你的基准测试, 用 标记字段或属性 :BenchmarkDotNet 将枚举所有指定值 并为每个案例运行基准测试。 如果你想比较基准测试, 通过以下方式标记其中一个基准测试,BenchmarkDotNet将将其与其他所有基准进行比较。 如果你想比较不同环境的性能,可以用作业。 例如,你可以通过和 在 .NET 8.0 和 Mono 上运行所有基准测试。[Params(1, 2, 3)][Benchmark(Baseline = true)][SimpleJob(RuntimeMoniker.Net80)][SimpleJob(RuntimeMoniker.Mono)]

如果你不喜欢属性,可以通过流流式调用大多数API,写出这样的代码:

ManualConfig.CreateEmpty() // A configuration for our benchmarks    .AddJob(Job.Default // Adding first job        .WithRuntime(ClrRuntime.Net472) // .NET Framework 4.7.2        .WithPlatform(Platform.X64) // Run as x64 application        .WithJit(Jit.LegacyJit) // Use LegacyJIT instead of the default RyuJIT        .WithGcServer(true// Use Server GC    ).AddJob(Job.Default // Adding second job        .AsBaseline() // It will be marked as baseline        .WithEnvironmentVariable("Key""Value"// Setting an environment variable        .WithWarmupCount(0// Disable warm-up stage    );

如果你更喜欢命令行体验,可以通过以下方式配置基准测试 任何控制台应用程序中的控制台参数(其他类型的应用程序不支持)。

自动化

可靠的基准测试总是包含大量模板代码。

让我们来想想在典型情况下你应该怎么做。 首先,你应该做一个试点实验,确定方法调用次数的最佳。 接下来,你应进行多次热身迭代,确保基准达到稳态。 之后,你应该执行主要迭代并计算一些基本统计数据。 如果你在基准测试中计算了一些数值,应该用它来防止死代码消除。 如果你用循环,你应该关注循环展开对结果的影响 (这可能取决于处理器架构)。 一旦你得到结果,你应该检查获得的性能分布是否有一些特殊属性 比如多模态或极高的离群值。 你还应评估基础设施的开销,并将其从结果中扣除。 如果你想测试多个环境,应该在每个环境中进行测量并手动汇总结果。

如果你是从零开始写代码,很容易出错,弄坏你的测量结果。 请注意,这是你在基准测试时应遵循的完整清单的简化版: 还有许多隐藏的陷阱需要妥善处理。 幸运的是,你不必担心,因为 BenchmarkDotNet 会帮你完成这些枯燥且耗时的工作。

此外,图书馆还能帮助你完成一些调查过程中可能需要完成的高级任务。 例如, BenchmarkDotNet 可以测量管理和原生内存流量 并打印拆解列表以供基准测试。

可靠性

许多手写基准结果往往会给出错误的数据,导致错误的商业决策。 BenchmarkDotNet 保护您免受大多数基准测试陷阱,并实现高精度测量。

你不必担心方法调用次数、热身次数和实际迭代次数: BenchmarkDotNet 尝试选择最佳的基准参数, 在测量预估与所有基准测试总时长之间取得良好权衡。 所以,你不应该使用任何神奇的数字(比如“我们这里应该做100次迭代”), 图书馆会根据统计指标的数值帮你做决定。

BenchmarkDotNet 还阻止了使用 DEBUG 模式构建的未优化装配的基准测试,因为 相应的结果将不可靠。 如果你已经连接了调试器,库会打印警告, 如果你使用虚拟机监控程序(HyperV、VMware、VirtualBox), 或者你对当前环境有其他问题。

在6+年的开发过程中,我们遇到了数十个不同的问题,可能会影响你的测量结果。 在BenchmarkDotNet内部,有许多启发式方法、检查、技巧和技巧可以帮助你 提高结果的可靠性。

友好

绩效数据分析是一项耗时的活动,需要专注、知识和经验。 BenchmarkDotNet 为你完成了主要的分析,并以用户友好的形式呈现结果。

实验结束后,你会得到一个汇总表,里面包含了大量关于已执行基准测试的有用数据。 默认情况下,它只包含最重要的列, 但它们可以很容易定制。 列集是自适应的,依赖于基准定义和测量值。 例如,如果你将其中一个基准标记为基线, 你还会获得额外的列,帮助你将所有基准与基线进行比较。 默认情况下,它总是显示平均值列, 但如果我们检测到平均值和中位数值之间存在巨大差异, 两栏都会被介绍。

BenchmarkDotNet 会尝试找出你性能分布中一些异常属性,并发布一些关于它的美好信息。 例如,它会在多模态分布或高异常值时发出警告。 在这种情况下,你可以向上滚动结果,查看每个分布的ASCII风格直方图 或者用 生成漂亮的 PNG 图。[RPlotExporter]

BenchmarkDotNet不会让你数据过载;它只显示根据你的结果所必需的关键信息: 它允许你在原始案件中保持摘要简短,仅在复杂案件时延长摘要。 当然,你可以手动请求额外的统计数据和可视化。 如果你不自定义摘要视图, 默认的展示方式会尽可能用户友好。:)