ZetCode

C# 基准测试

最后修改于 2023 年 7 月 5 日

在本文中,我们使用 BenchmarkDotNet 库对 C# 代码进行基准测试。

基准测试是衡量代码性能的过程。它使我们能够确定程序中的性能瓶颈。

BenchmarkDotNet 是一个强大的 .NET 库,用于执行基准测试。我们可以测量 C#、F# 和 VB 代码。

$ dotnet add package BenchmarkDotNet

我们安装 BenchmarkDotNet 包。

$ dotnet run --project SimpleEx.csproj -c Release

这是我们运行基准测试的方法。

C# 基准测试简单示例

在以下示例中,我们测量字符串连接的各种方法的性能。

Program.cs
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class Program
{
    int n = 10_000;

    [Benchmark]
    public string Builder()
    {
        StringBuilder output = new StringBuilder();

        for (int i = 0; i < n; i++)
        {
            output.Append("falcon").Append(i);
        }

        return output.ToString();
    }

    [Benchmark]
    public string Interpolation()
    {
        string output = string.Empty;

        for (int i = 0; i < n; i++)
        {
            output = $"{output}falcon{i}";
        }

        return output;
    }

    [Benchmark]
    public string Addition()
    {
        string output = string.Empty;

        for (int i = 0; i < n; i++)
        {
            output += "falcon" + i;
        }

        return output.ToString();
    }

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

在该程序中,我们比较了三种字符串连接方法:使用 StringBuilder、字符串插值和加法运算。

[MemoryDiagnoser]
public class Program
{
    ...
}

使用 [MemoryDiagnoser],我们还可以测量内存使用情况。

[Benchmark]
public string Builder()
{
    StringBuilder output = new StringBuilder();

    for (int i = 0; i < n; i++)
    {
        output.Append("falcon").Append(i);
    }

    return output.ToString();
}

此方法使用 StringBuilder 添加字符串。该方法使用 [Benchmark] 进行装饰。

var summary = BenchmarkRunner.Run<Program>();

我们运行基准测试。

// * Summary *

BenchmarkDotNet=v0.13.2, OS=ubuntu 22.04
11th Gen Intel Core i5-1135G7 2.40GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.104
  [Host]     : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.4 (6.0.422.16404), X64 RyuJIT AVX2


|        Method |         Mean |     Error |    StdDev |        Gen0 |        Gen1 |        Gen2 |    Allocated |
|-------------- |-------------:|----------:|----------:|------------:|------------:|------------:|-------------:|
|       Builder |     120.6 us |   0.30 us |   0.27 us |     62.3779 |     62.3779 |     62.3779 |    398.29 KB |
| Interpolation | 120,826.4 us | 354.51 us | 331.61 us | 290600.0000 | 247600.0000 | 247600.0000 | 956382.51 KB |
|      Addition |  73,354.2 us | 448.96 us | 419.96 us | 290714.2857 | 249000.0000 | 247714.2857 |  956694.4 KB |

输出包括操作系统和硬件摘要,以及显示基准测试统计信息的表格。从输出中我们可以看到,加法运算速度最快,而 builder 的内存效率最高。

C# 基准测试排序算法

在下一个示例中,我们对排序算法进行基准测试。

Program.cs
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
public class Program
{
    const int n = 100_000;
    int[] vals = new int[n];

    [GlobalSetup]
    public void GlobalSetup()
    {
        var rnd = new Random();

        for (int i = 0; i < n; i++)
        {
            vals[i] = rnd.Next(1, 100);
        }
    }

    [Benchmark]
    public void SelectionSort()
    {
        int len = vals.Length;

        for (int i = 0; i < len - 1; i++)
        {
            int min_idx = i;

            for (int j = i + 1; j < len; j++)
            {
                if (vals[j] < vals[min_idx])
                {
                    min_idx = j;
                }
            }

            int temp = vals[min_idx];
            vals[min_idx] = vals[i];
            vals[i] = temp;
        }
    }

    [Benchmark]
    public void BubbleSort()
    {
        int len = vals.Length;

        for (int i = 0; i < len - 1; i++)
        {
            for (int j = 0; j < len - i - 1; j++)
            {
                if (vals[j] > vals[j + 1])
                {
                    int temp = vals[j];
                    vals[j] = vals[j + 1];
                    vals[j + 1] = temp;
                }
            }
        }
    }

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

我们将选择排序与冒泡排序算法进行比较。

const int n = 100_000;
int[] vals = new int[n];

[GlobalSetup]
public void GlobalSetup()
{
    var rnd = new Random();

    for (int i = 0; i < n; i++)
    {
        vals[i] = rnd.Next(1, n);
    }
}

使用 [GlobalSetup] 属性,我们准备一个包含 100000 个随机选择的介于 1 和 100000 之间的整数值的数组。此代码仅执行一次。

[Benchmark]
public void SelectionSort()
{
    int len = vals.Length;

    for (int i = 0; i < len - 1; i++)
    {
        int min_idx = i;

        for (int j = i + 1; j < len; j++)
        {
            if (vals[j] < vals[min_idx])
            {
                min_idx = j;
            }
        }

        int temp = vals[min_idx];
        vals[min_idx] = vals[i];
        vals[i] = temp;
    }
}

我们有选择排序算法;它对准备好的整数数组进行排序。

|        Method |    Mean |    Error |   StdDev | Allocated |
|-------------- |--------:|---------:|---------:|----------:|
| SelectionSort | 3.646 s | 0.0569 s | 0.0532 s |   1.38 KB |
|    BubbleSort | 4.124 s | 0.0136 s | 0.0127 s |   3.28 KB |

选择排序在内存和速度方面都略胜一筹。

来源

BenchmarkDotNet Github 页面

在本文中,我们使用 BenchmarkDotNet 库测量了 C# 代码的性能。

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程