开源测试利器BenchmarkDotNet
  楠木大叔   1/30/21 10:27:01 PM

导航

  • 项目背景
  • 概述
  • 指南
    • 入门
    • 如何运行
    • 最佳实践
    • 安装Nuget包
    • 选择运行策略

BenchmarkDotNet 帮助您把程序方法的执行过程转换为基准测试数据,并跟踪其性能,使得测数量数据可以共享和重复。 这并不比写单元测试复杂!基于这个引擎,它执行了一系列精妙的算法,保证了测试数据的可靠性和准确性。BenchmarkDotNet让你远离常见的基准测试错误,并在基准测试设计或获得的度量出现问题时发出警告。基准测试结果会以用户友好的形式呈现出来,实验的重要数据会高亮展示。 该库被3500多个项目(包括.NET Core)采用,并由.NET基金会提供支持。

很容易开始编写基准测试,请查看一个示例(此处是可复制的可粘贴版本):

[SimpleJob(RuntimeMoniker.Net472, baseline: true)]
[SimpleJob(RuntimeMoniker.NetCoreApp30)]
[SimpleJob(RuntimeMoniker.CoreRt30)]
[SimpleJob(RuntimeMoniker.Mono)]
[RPlotExporter]
public class Md5VsSha256
{
    private SHA256 sha256 = SHA256.Create();
    private MD5 md5 = MD5.Create();
    private byte[] data;

    [Params(1000, 10000)]
    public int N;

    [GlobalSetup]
    public void Setup()
    {
        data = new byte[N];
        new Random(42).NextBytes(data);
    }

    [Benchmark]
    public byte[] Sha256() => sha256.ComputeHash(data);

    [Benchmark]
    public byte[] 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
  CoreRt30     : .NET CoreRT 1.0.28231.02 @Commit: 741d61493c560ba96e8151f9e56876d4d3828489, X64 AOT
  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 |    CoreRt 3.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 |    CoreRt 3.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 |    CoreRt 3.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 |    CoreRt 3.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 Framework 4.6.1+, .NET Core 2.0+, Mono, CoreRT
支持 语言: C#, F#, Visual Basic
支持 操作系统: Windows, Linux, macOS

特性

BenchmarkDotNet有许多在全面的性能调查中是必不可少的特性。四个方面定义了这些特性的设计:简单, 自动化, 可靠性, 和 友好性.

简单

即便你不是一个经验丰富的性能测试工程师,你依然可以使用简单的api以声明式风格设计非常复杂的性能实验。

例如,如果要参数化基准,请使用[Params(1,2,3)]标记字段或属性:BenchmarkDotNet将枚举所有指定的值,并为每种情况运行基准。

如果你想对比不同的基准,可以通过标记[benchmark(baseline:true)]将其中一个基准标记为基准:BenchmarkDotNet将其与所有其他基准进行比较。

[SimpleJob(RuntimeMoniker.NetCoreApp30)] and [SimpleJob(RuntimeMoniker.Mono)]. 例如,你可以通过标记[SimpleJob(RuntimeMoniker.NetCoreApp30)][SimpleJob(RuntimeMoniker.Mono)]来让程序运行在.NET Core 3.0 and Mono 环境.

如果你不喜欢用属性标注的方式,你可以通过调用Fluent API,比如这种写法:

ManualConfig.CreateEmpty() // A configuration for our benchmarks
    .With(Job.Default // Adding first job
            .With(ClrRuntime.Net472) // .NET Framework 4.7.2
            .With(Platform.X64) // Run as x64 application
            .With(Jit.LegacyJit) // Use LegacyJIT instead of the default RyuJIT
            .WithGcServer(true) // Use Server GC
    ).With(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
    );

如果你喜欢命令行,你可以在控制台程序或者.NET Core命令行工具中使用控制台参数来执行dll的基准测试:

dotnet benchmark MyAssembly.dll --runtimes net472 netcoreapp2.1 Mono

自动化

可靠的基准测试总是包含许多样例代码。

让我们想想在一个典型的案例中你应该做什么。首先,您应该做一个试验性实验,并确定方法调用的最佳数量。接下来,您应该执行几个预热迭代,并确保您的基准达到稳定状态。 之后,您应该执行主迭代并计算一些基本统计信息。如果在基准测试中计算一些值,应该以某种方式使用它来防止“死代码消除”——永远不可能被执行到的代码。如果使用循环,则应注意循环展开对结果的影响(这可能取决于处理器体系结构)。 一旦得到结果,就应该检查所获得的性能分布的一些特殊属性像多模态或极高的异常值。 你也要评估基础设施的损耗,并将它们从你结果中剔除。如果要测试多个环境,则应在每个环境中执行测量并手动聚合结果。

如果你完全从头自己写基准测试代码,非常容易犯错并影响您的测试结果。值得注意的是,这是完整检查表的简短版本,在基准测试期间应遵循:还有很多隐藏的陷阱需要妥善处理。幸运的是,你不必担心因为BenchmarkDotNet已经帮你把无聊而耗时的事情给做了。[how-it-works](https://BenchmarkDotNet.org/articles/guides/how-it-works.html).

此外,该库还可以帮助您完成一些高级任务,您可能希望在调查期间执行这些任务。 例如,BenchmarkDotNet可以测量托管https://benchmarkdotnet.org/articles/configs/diagnosers.html#usage和[本机](https://benchmarkdotnet.org/articles/samples/IntroNativeMemory.html内存流量,并打印基准的反汇编列表。

可靠性

许多手工编写的基准产生错误的数字,导致错误的业务决策。BenchmarkDotNet让您远离大多数基准测试陷阱的影响,并带给您实现高精度的测量结果。您不必担心程序方法调用的最佳、预热和实际迭代的次数:BenchmarkDotNet会选择最佳的基准参数并在度量预测和所有基准运行的总持续时间之间实现良好的权衡。所以,你不需要使用任何魔法数字(比如“我们应该在这里执行100次迭代”),类库将根据统计度量值为您执行此操作。

BenchmarkDotNet还防止对使用调试模式生成的未优化程序集进行基准测试,因为相应的结果是不可靠的。如果使用hypervisor(HyperV、VMware、VirtualBox),附加的调试器,或者如果你对当前的环境有任何其他问题,它会打印一个警告。

在6年多的发展过程中,我们遇到过很多对测量结造成干扰的问题。在BenchmarkDotNet中,有大量的推断,检测,黑科技和技巧来帮助您提高测试的稳定性。

友好

性能数据分析是一项耗时的工作,需要专注、知识和经验。BenchmarkDotNet为您完成数据分析的主要部分,并以用户友好的形式显示结果。

实验结束后,您将得到一个概览表,其中包含有关已执行基准的许多有用数据。 默认地,概览表只包含了最重要的字段, 但是他们可以容易地定制. 列头字段的设置,取决于基准定义和测量值。 比如, 如果你标记了某个基准测试为 基线,那么,你将得到更多的列,它们会帮助你和其它基准测试做比较。默认情况下,它总是显示Mean列,但是如果我们发现平均值和中值之间存在巨大的差异,两列都将展示。

BenchmarkDotNet试图找到性能分布的一些不寻常的属性,并把与之相关的信息打印出来。例如,当出现多模式分布或高异常值时,它会发出警告。在这种情况下,您可以向上滚动结果并检查每个分布的ASCII样式直方图,或者使用[RPlotExporter]生成漂亮的图片。

BenchmarkDotNet不会让数据超载;它只显示根据结果而定的基本信息:它允许您保持原始案例的摘要较小,并且只扩展复杂案例的摘要。当然,您可以手动地请求任何其他统计和可视化。如果你不想自定义结果概览视图,默认的展示方式已经足够友好。:)

谁会使用 BenchmarkDotNet?

每个人!BenchmarkDotNet已经被超过3500+ 个项目采用,包括 dotnet/performance (reference benchmarks for all .NET Runtimes), dotnet/runtime (.NET Core runtime and libraries), Roslyn (C# and Visual Basic compiler), Mono, ASP.NET Core, ML.NET, Entity Framework Core, SignalR, F#, Orleans, Newtonsoft.Json, Elasticsearch.Net, Dapper, Expecto, Accord.NET, ImageSharp, RavenDB, NodaTime, Jint, NServiceBus, Serilog, Autofac, Npgsql, Avalonia, ReactiveUI, SharpZipLib, LiteDB, GraphQL for .NET, MediatR, TensorFlow.NET.
在 GitHub上,你可以搜索到与BenchmarkDotNet相关的2500+ issues, 1500+ commits, and 450,000+ files

了解有关基准测试的更多信息

BenchmarkDotNet不是一个银弹,它神奇地使您的所有基准都正确,并为您分析度量。

即使你使用这个库,你仍然应该知道如何设计基准实验,以及如何根据原始数据得出正确的结论。 If you want to know more about benchmarking methodology and good practices, it's recommended to read a book by 如果您想了解更多关于基准测试方法和良好实践的信息,建议你读一本Andrey Akinshin (BenchmarkDotNet项目负责人)的书: "Pro .NET Benchmarking"

使用此深入的指南可以正确地设计基准,测量.NET应用程序的关键性能指标,并分析结果。 这本书提供了几十个案例研究,帮助您理解复杂的基准测试主题。 您将避免常见的陷阱,控制测量的准确性,并提高软件的性能。

概述

安装

创建一个控制台应用程序,安装 BenchmarkDotNet NuGet 包.支持:

  • 项目: 具有PackageReferences的经典和现代的项目
  • 运行时: .NET Framework (4.6+), .NET Core (2.0+), Mono, CoreRT
  • 操作系统: Windows, Linux, MacOS
  • 开发语言: C#, F#, VB

设计一个基准测试

创建一个控制台应用程序,编写一个用于测试类和方法,并给方法打上Benchmark特性。在以下示例中,我们比较了MD5和SHA256加密哈希函数:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    public class Md5VsSha256
    {
        private const int N = 10000;
        private readonly byte[] data;

        private readonly SHA256 sha256 = SHA256.Create();
        private readonly MD5 md5 = MD5.Create();

        public Md5VsSha256()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
        }
    }
}

BenchmarkRunner.Run<Md5VsSha256>(),这个调用运行你的基准测试并将结果打印到控制台。

值得注意是,在做基准测试前,请讲程序配置成Release模式。否则,结果将不符合实际。如果忘记更改配置,BenchmarkDotNet将打印警告。

基准测试结果

BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17134.472 (1803/April2018Update/Redstone4)
Intel Core i7-2630QM CPU 2.00GHz (Sandy Bridge), 1 CPU, 8 logical and 4 physical cores
Frequency=1948699 Hz, Resolution=513.1629 ns, Timer=TSC
.NET Core SDK=2.1.502
  [Host]     : .NET Core 2.1.6 (CoreCLR 4.6.27019.06, CoreFX 4.6.27019.05), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.6 (CoreCLR 4.6.27019.06, CoreFX 4.6.27019.05), 64bit RyuJIT


| Method |      Mean |     Error |    StdDev |
|------- |----------:|----------:|----------:|
| Sha256 | 100.90 us | 0.5070 us | 0.4494 us |
|    Md5 |  37.66 us | 0.1290 us | 0.1207 us |

Jobs

你可以一次性测试多个环境。例如,你可以对比.NET Framework, .NET Core, Mono and CoreRT性能测试。只需在类声明之前添加ClrJobMonoJobCoreJobCoreRtJob属性(它需要.NETCore项目,已安装corecrl和Mono):

[ClrJob, MonoJob, CoreJob, CoreRtJob]
public class Md5VsSha256

结果示例:

BenchmarkDotNet=v0.11.0, OS=Windows 10.0.16299.309 (1709/FallCreatorsUpdate/Redstone3)
Intel Xeon CPU E5-1650 v4 3.60GHz, 1 CPU, 12 logical and 6 physical cores
Frequency=3507504 Hz, Resolution=285.1030 ns, Timer=TSC
.NET Core SDK=2.1.300-preview1-008174
  [Host]     : .NET Core 2.1.0-preview1-26216-03 (CoreCLR 4.6.26216.04, CoreFX 4.6.26216.02), 64bit RyuJIT
  Job-YRHGTP : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2633.0
  Core       : .NET Core 2.1.0-preview1-26216-03 (CoreCLR 4.6.26216.04, CoreFX 4.6.26216.02), 64bit RyuJIT
  CoreRT     : .NET CoreRT 1.0.26414.01, 64bit AOT
  Mono       : Mono 5.10.0 (Visual Studio), 64bit 

| Method | Runtime |       Mean |     Error |    StdDev |
|------- |-------- |-----------:|----------:|----------:|
| Sha256 |     Clr |  75.780 us | 1.0445 us | 0.9771 us |
| Sha256 |    Core |  41.134 us | 0.2185 us | 0.1937 us |
| Sha256 |  CoreRT |  40.895 us | 0.0804 us | 0.0628 us |
| Sha256 |    Mono | 141.377 us | 0.5598 us | 0.5236 us |
|        |         |            |           |           |
|    Md5 |     Clr |  18.575 us | 0.0727 us | 0.0644 us |
|    Md5 |    Core |  17.562 us | 0.0436 us | 0.0408 us |
|    Md5 |  CoreRT |  17.447 us | 0.0293 us | 0.0244 us |
|    Md5 |    Mono |  34.500 us | 0.1553 us | 0.1452 us |

这里有很多内置的jobs可供使用。例如,你可以对比加LegacyJitX86vsLegacyJitX64vsRyuJitX64

[LegacyJitX86Job, LegacyJitX64Job, RyuJitX64Job]

或者你可以定义你自己的jobs:

[Config(typeof(Config))]
public class Md5VsSha256
{
    private class Config : ManualConfig
    {
        public Config()
        {
            Add(new Job(EnvMode.LegacyJitX86, EnvMode.Clr, RunMode.Dry)
                {
                    Env = { Runtime = Runtime.Clr },
                    Run = { LaunchCount = 3, WarmupCount = 5, TargetCount = 10 },
                    Accuracy = { MaxStdErrRelative = 0.01 }
                }));
        }
    }

更多阅读Jobs, Configs

列字段

你可以给概览表增加自定义字段:

[MinColumn, MaxColumn]
public class Md5VsSha256
MethodMedianStdDevMinMax
Sha256131.3200 us4.6744 us129.8216 us147.7630 us
Md526.2847 us0.4424 us25.8442 us27.4258 us

当然,您可以根据完整的基准摘要定义自己的列。

更多阅读Columns

报表

你可以将基准测试结果导出成多种形式:

[MarkdownExporter, AsciiDocExporter, HtmlExporter, CsvExporter, RPlotExporter]
public class Md5VsSha256

如果你安装了R, RPlotExporter 可以生成很多漂亮的图:

更多阅读Exporters

基线

为了放大研究,你需要标记基准测试方法为基线

public class Sleeps
{
    [Benchmark]
    public void Time50() => Thread.Sleep(50);

    [Benchmark(Baseline = true)]
    public void Time100() => Thread.Sleep(100);

    [Benchmark]
    public void Time150() => Thread.Sleep(150);
}

因此,您将在摘要表中有其他列:

MethodMedianStdDevRatio
Time100100.2640 ms0.1238 ms1.00
Time150150.2093 ms0.1034 ms1.50
Time5050.2509 ms0.1153 ms0.50

更多阅读Baselines

参数

可以通过Params属性标记类中的一个或多个字段或属性。在该属性中,可以指定一组值。然后,您将得到每个params值组合的结果。

public class IntroParams
{
    [Params(100, 200)]
    public int A { get; set; }

    [Params(10, 20)]
    public int B { get; set; }

    [Benchmark]
    public void Benchmark()
    {
        Thread.Sleep(A + B + 5);
    }
}

MethodMedianStdDevAB
Benchmark115.3325 ms0.0242 ms10010
Benchmark125.3282 ms0.0245 ms10020
Benchmark215.3024 ms0.0375 ms20010
Benchmark225.2710 ms0.0434 ms20020

更多阅读Parameterization

开发语言

你也可以基于F#或者VB来编写基准测试. 例如:

type StringKeyComparison () =
    let mutable arr : string [] = [||]
    let dict1 = ConcurrentDictionary<_,_>()
    let dict2 = ConcurrentDictionary<_,_>(StringComparer.Ordinal)

    [<Params (100, 500, 1000, 2000)>] 
    member val public DictSize = 0 with get, set

    [<GlobalSetup>]
    member self.GlobalSetupData() =
        dict1.Clear(); dict2.Clear()
        arr <- getStrings self.DictSize
        arr |> Array.iter (fun x -> dict1.[x] <- true ; dict2.[x] <- true)

    [<Benchmark>]
    member self.StandardLookup () = lookup arr dict1

    [<Benchmark>]
    member self.OrdinalLookup () = lookup arr dict2
Public Class Sample
    <Params(1, 2)>
    Public Property A As Integer
    <Params(3, 4)>
    Public Property B As Integer

    <Benchmark>
    Public Function Benchmark() As Integer
            return A + B
    End Function
End Class

诊断

可以把这诊断器附加到您的基准测试,并获得一些有用的信息。

当前正诊断器:

  • GC和内存分配(MemoryDiagnoser),它是跨平台、内置的,默认情况下不再启用。
  • JIT内联事件(InliningDiagnoser)。您可以在一个单独的软件包中找到这个诊断程序,其中包含用于Windows的诊断程序(BenchmarkDotNet.Diagnostics.Windows):image

下面是MemoryDiagnoser的输出示例,请注意右侧的额外列(Gen 0Allocated):

MethodMeanStdDevGen 0Allocated
Iterative31.0739 ns0.1091 ns_0 B
LINQ83.0435 ns1.0103 ns0.006932 B

更多阅读Diagnosers

BenchmarkRunner

运行基准测试有几种方法:可以使用现有类、基于internet上的代码或基于源代码运行基准测试:

var summary = BenchmarkRunner.Run<MyBenchmarkClass>();
var summary = BenchmarkRunner.Run(typeof(MyBenchmarkClass));

string url = "<E.g. direct link to a gist>";
var summary = BenchmarkRunner.RunUrl(url);

string benchmarkSource = "public class MyBenchmarkClass { ...";
var summary = BenchmarkRunner.RunSource(benchmarkSource);

更多阅读如何运行

入门

使用BenchmarkDotNet,请遵循以下步骤。

步骤1 安装

通过NuGet 包: BenchmarkDotNet安装 BenchmarkDotNet

PM> Install-Package BenchmarkDotNet

更多阅读BenchmarkDotNet Nuget

步骤2 设计一个基准测试

创建一个控制台应用程序,编写一个用于测试类和方法,并给方法打上Benchmark特性。在以下示例中,我们比较了MD5SHA256加密哈希函数:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    public class Md5VsSha256
    {
        private const int N = 10000;
        private readonly byte[] data;

        private readonly SHA256 sha256 = SHA256.Create();
        private readonly MD5 md5 = MD5.Create();

        public Md5VsSha256()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Md5VsSha256>();
        }
    }
}

BenchmarkRunner.Run<Md5VsSha256>(),这个调用运行你的基准测试并将结果打印到控制台。

步骤3 查看结果

查看结果,这是上面基准测试的输出结果:

BenchmarkDotNet=v0.10.1, OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4702MQ CPU 2.20GHz, ProcessorCount=8
Frequency=2143476 Hz, Resolution=466.5319 ns, Timer=TSC
  [Host]     : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
  DefaultJob : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1586.0
MethodMeanStdDevAllocated
Sha256130.5169 us1.8489 us188 B
Md525.8010 us0.1757 us113 B

步骤4 结果分析

在bin目录中,可以找到许多有用的文件,其中包含详细信息。例如:

更多

BenchmarkDotNet提供了许多有助于高质量性能研究的特性。如果你想了解关于BenchmarkDotNet更多的特性,可以查阅概述。如果你有问题,请查阅FAQ 。如果你没有找到你问题的答案,可以在gitter上提问 or在github上创建issuce.

如何运行

有几种方式可以让你的基准测试运行起来。

类型

var summary = BenchmarkRunner.Run<MyBenchmarkClass>();
var summary = BenchmarkRunner.Run(typeof(MyBenchmarkClass));

全局dotcore 工具

你可以使用BenchmarkDotNet.Tool来执行你的基准测试。

dotnet tool install -g BenchmarkDotNet.Tool
dotnet benchmark MyAssemblyWithBenchmarks.dll --filter *

Url

也可以直接运行来自internet的基准测试代码。

string url = "<E.g. direct link to raw content of a gist>";
var summary = BenchmarkRunner.RunUrl(url);

Source

string benchmarkSource = "public class MyBenchmarkClass { ...";
var summary = BenchmarkRunner.RunSource(benchmarkSource);

基准开关

或者可以创建一组基准并从命令行中选择一个基准

static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

还可以使用config命令样式从命令行指定一些配置(更多@docs.console参数)

dotnet run -c Release -- --job short --runtimes clr core --filter *BenchmarkClass1*

最佳实践

使用编译的Release版本

不要使用visual studio debug模式编译的dll来做基准测试。因为调试版本的基准测试可能比10-100慢倍。Release版本,意味着您应该在csproj文件中有<Optimize>true</Optimize>,或者使用/Optimize进行csc编译。另外,在基准测试期间,您永远不应该使用附加的调试器(例如Visual Studio或WinDbg)。最好的方法是在发布模式中构建我们的基准,并从命令行运行它。

尝试不同的环境

请不要推断你的结果。或者做得非常小心。我再次提醒:不同环境下的结果可能会有很大的不同。如果Foo1方法比CLR4、.NET Framework 4.5、x64、RyuJIT、Windows的Foo2方法快,则表示Foo1方法比CLR4、.netframework4.5、x64、RyuJIT、Windows的Foo2方法快,仅此而已。但是在尝试之前,您不能对该方法在CLR 2或.NET Framework4.6、LegacyJIT-x64、x86或Linux+Mono的性能做任何说明。

避免死代码消除—————不被使用的代码

你也应该使用计算结果。例如,如果运行以下代码:

void Foo()
{
    Math.Exp(1);
}

然后JIT可以消除这段代码,因为没有使用Math.Exp计算的结果。更好的方法是这样使用:

double Foo()
{
    return Math.Exp(1);
}

电源设置和其他应用

  • 关闭除基准进程和标准操作系统进程之外的所有应用程序。如果运行基准测试的同时,还在使用visual studio,可能会对基准测试结果造成负面影响。
  • 如果您使用笔记本电脑进行基准测试,请保持电源接入并使用最高性能模式。

安装Nuget包

Packages包

我们有以下一组NuGet软件包(您可以直接从NuGet.org安装):

  • BenchmarkDotNet:基本的BenchmarkDotNet基础设施和逻辑。这就是运行基准测试所需的一切
  • BenchmarkDotNet.Diagnostics.Windows:提供一组Windows诊断程序的附加可选包。
  • BenchmarkDotNet.Tool: 一个dotnet工具,用于执行用BenchmarkDotNet构建基准测试。
  • BenchmarkDotNet.Templates:BenchmarkDotNet的模板。

版本控制和说明

我们有三种版本:稳定版夜间版开发版。你可以通过BenchmarkDotNetInfo.版本BenchmarkDotNetInfo.标题从代码中获取最新版本。

稳定版

这些版本已经可以在Nuget官方获取。

<packageSources>
  <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
  • 主NuGet包的示例:BenchmarkDotNet.0.10.3.nupkg
  • BenchmarkDotNetInfo.FullTitle示例:BenchmarkDotNet v0.10.3

夜间版本

如果你想使用BenchmarkDotNet的夜间版,请在<packageSources>节点添加NuGet.config配置https://ci.appveyor.com/nuget/benchmarkdotnet

<packageSources>
  <add key="bdn-nightly" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
</packageSources>

现在你可以从bdn-nightly获取安装包。

  • 主NuGet包的示例: BenchmarkDotNet.0.10.3.13.nupkg.
  • BenchmarkDotNetInfo.FullTitle示例: BenchmarkDotNet v0.10.3.13-nightly.

开发版本

您还可以从源代码构建BenchmarkDotNet包。.nupkg文件可以在.\build\build-and-pack.cmd命令行帮助下生成。

  • 主NuGet包的示例: BenchmarkDotNet.0.10.3-develop.nupkg.
  • BenchmarkDotNetInfo.FullTitle示例: BenchmarkDotNet v0.10.3.20170304-develop.

选择运行策略

如果运行基准测试,则始终(显式或隐式)使用Job。每个Job都有RunStrategy参数,允许在不同的基准模式之间切换。默认的运行策略是吞吐量,这对于大多情况都是运行良好的。然而,在某些特定情况下,其他策略也很有用。

吞吐量

吞吐量是默认的运行策略,非常适合微标准测试。它基于一组测试迭代自动选择主迭代中的操作量。迭代次数也会根据Job精准设置自动选择。基准方法应具有稳定状态。当然,你也可以手动设置所以特征参数,比如:

[SimpleJob(launchCount: 3, warmupCount: 10, targetCount: 30)]
public class MyBenchmarkClass

样例:冷启动

如果你想跳过试点和热身步骤,那么你可以选择冷启动策略。

用法

[SimpleJob(RunStrategy.ColdStart, launchCount:50)]
public class MyBenchmarkClass

源代码

using System;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;

namespace BenchmarkDotNet.Samples
{
    [SimpleJob(RunStrategy.ColdStart, targetCount: 5)]
    [MinColumn, MaxColumn, MeanColumn, MedianColumn]
    public class IntroColdStart
    {
        private bool firstCall;

        [Benchmark]
        public void Foo()
        {
            if (firstCall == false)
            {
                firstCall = true;
                Console.WriteLine("// First call");
                Thread.Sleep(1000);
            }
            else
                Thread.Sleep(10);
        }
    }
}

输出

Result       1: 1 op, 1002034900.00 ns, 1.0020 s/op
Result       2: 1 op, 10219700.00 ns, 10.2197 ms/op
Result       3: 1 op, 10406200.00 ns, 10.4062 ms/op
Result       4: 1 op, 10473900.00 ns, 10.4739 ms/op
Result       5: 1 op, 10449400.00 ns, 10.4494 ms/op

MethodMeanErrorStdDevMinMaxMedian
Foo208.7 ms1,707.4 ms443.5 ms10.22 ms1,002.0 ms10.45 ms

链接

样例: 监控简介

如果一个基准测试方法至少需要100毫秒,您还可以使用监视策略。

用法

[SimpleJob(RunStrategy.Monitoring, launchCount: 10, warmupCount: 0, targetCount: 100)]
public class MyBenchmarkClass

源代码

using System;
using System.Threading;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;

namespace BenchmarkDotNet.Samples
{
    [SimpleJob(RunStrategy.Monitoring, targetCount: 10, id: "MonitoringJob")]
    [MinColumn, Q1Column, Q3Column, MaxColumn]
    public class IntroMonitoring
    {
        private Random random = new Random(42);

        [Benchmark]
        public void Foo()
        {
            Thread.Sleep(random.Next(10) * 10);
        }
    }
}

输出

Result       1: 1 op, 61552600.00 ns, 61.5526 ms/op
Result       2: 1 op, 10141700.00 ns, 10.1417 ms/op
Result       3: 1 op, 10482900.00 ns, 10.4829 ms/op
Result       4: 1 op, 50410900.00 ns, 50.4109 ms/op
Result       5: 1 op, 10421400.00 ns, 10.4214 ms/op
Result       6: 1 op, 20556100.00 ns, 20.5561 ms/op
Result       7: 1 op, 70473200.00 ns, 70.4732 ms/op
Result       8: 1 op, 50581700.00 ns, 50.5817 ms/op
Result       9: 1 op, 10559000.00 ns, 10.5590 ms/op
Result      10: 1 op, 70496300.00 ns, 70.4963 ms/op

MethodMeanErrorStdDevMinQ1Q3Max
Foo36.57 ms40.03 ms26.47 ms10.14 ms10.48 ms61.55 ms70.50 ms

链接

版权声明: 本文为智客工坊「楠木大叔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。