目录
5:分析和解释来自BenchmarkDotNet的基准测试结果
为什么BenchmarkDotNet对于衡量C#代码中的性能很重要?
什么是基准测试方法,如何在BenchmarkDotNet中编写它们?
本文探讨了使用BenchmarkDotNet对C#代码进行高效基准测试的来龙去脉。它从设置开始,然后指导您完成如何编写和运行基准测试的示例。阅读本文后,您将能够有效地使用BenchmarkDotNet在C#代码上编写和运行基准测试。
作为软件工程师,我们一直在努力实现代码的高性能和高效率。无论是优化算法还是微调数据结构,我们做出的每一个决策都会对应用程序的整体性能产生重大影响。可以帮助我们准确衡量代码性能的一种强大方法是称为基准测试的过程,我们将了解如何将BenchmarkDotNet与C#代码一起使用。
在本文中,我们将探讨使用BenchmarkDotNet对C#代码进行有效基准测试的来龙去脉。我们将从设置开始,我将指导您完成如何编写和运行基准测试的示例。最后,您将能够有效地使用BenchmarkDotNet在C#代码上编写和运行基准测试。
让我们跳进去吧!
1:安装和设置BenchmarkDotNet
第一步是在项目中安装BenchmarkDotNet包。为此,可以在Visual Studio中打开NuGet包管理器并搜索“BenchmarkDotNet.”选择最新版本,然后单击“安装”按钮将其添加到您的项目中。没有其他外部依赖项或任何花哨的事情需要你去做。
正如你将在以下部分中看到的那样,它是安装NuGet包后的纯代码配置!值得一提的是,在对C#代码进行基准测试期间,BenchmarkDotNet会自动执行优化,例如JIT预热和以随机顺序运行基准测试,以提供准确且公正的结果。但所有这一切都发生在你不需要做任何特别的事情的情况下。
请记住,就像编写测试一样,您很可能希望将基准测试代码放在专用项目中。这将允许您将核心代码与基准测试分开发布。可能没有太多充分的理由将基准代码部署到您的服务中或将您的基准代码交付给您的客户。
2:BenchmarkDotNet中的基准测试方法入门
在使用BenchmarkDotNet框架编写基准测试方法时,我们需要正确配置一些事情。在本节中,我将指导你如何编写有效的基准测试方法,以准确衡量C#代码的性能。这个关于BenchmarkDotNet入门的视频也是一个有用的指南:
构建您的基准代码
正确构建基准测试代码以确保准确和高效的基准测试非常重要。请遵循以下准则:
- 为基准创建单独的类:首先为基准创建专用类。这样可以使基准测试代码井井有条,并与应用程序代码的其余部分分开。
- 应用正确的[XXXRunJob]属性:通过使用以下属性之一标记基准测试类来选择要运行的适当类型的基准测试作业[ShortRunJob]、[MediumRunJob]、[LongRunJob]或[VeryLongRunJob]。
- 应用[MemoryDiagnoser]属性:要在基准测试期间启用内存测量,请将该[MemoryDiagnoser]属性应用于基准测试类。此属性允许您收集与内存相关的信息以及执行时间。如果您严格关心的是运行时而不是内存,则可以省略此项。
请注意,您的类无法密封。我建议只坚持使用具有上述适当属性的标准public class定义。不过,你可以让你的基准类从其他东西继承,所以如果你发现在这里使用一些继承来实现可重用性有好处,这可能是一个可行的选择。
编写基准测试方法
基准测试方法是定义要测量的代码的地方。下面是一些使用BenchmarkDotNet编写基准测试方法的技巧:
- 应用[Benchmark]属性:每个基准测试方法都需要该[Benchmark]属性。该属性告诉BenchmarkDotNet,应该将此方法视为基准。
- 避免基准测试方法中的设置成本:基准测试方法应侧重于衡量代码本身的性能,而不是初始化基准测试方案的成本。
- 避免分配和过度使用内存:基准测试方法不应关注内存分配导致的开销。在基准测试方法中最大限度地减少分配并减少内存使用量,以获得准确的性能测量值。
请注意,BenchmarkDotNet会为您处理所有预热工作——在对C#代码进行基准测试之前,您无需特意手动编写代码,提前进行迭代以使事情达到稳定状态。
基准测试方法示例
下面是BenchmarkDotNet中一个基准测试方法的示例:
[Benchmark]
public void SimpleMethodBenchmark()
{
for (int i = 0; i < 1000; i++)
{
// Execute the code to be measured
MyClass.SimpleMethod();
}
}
在此示例中,该[Benchmark]属性应用于SimpleMethodBenchmark方法,指示应将其视为基准。假设我们使用的是实例方法,而不是如图所示的static方法。在这种情况下,我们希望实例化基准方法之外的类,尤其是在我们需要创建、配置和传入依赖项时。最小化(阅读:消除)在方法中完成的工作量,而不是您尝试基准测试的方法。
请记住,如果除了运行时特性之外,您还对内存感兴趣,请确保将该[MemoryDiagnoser]属性应用于基准测试类,而不是作为基准测试的方法。
3:使用BenchmarkDotNet运行基准测试
当谈到在c#中使用BenchmarkDotNet运行基准测试时,需要牢记一些重要的注意事项。在本节中,我将解释如何运行基准测试,讨论不同的方案和选项,并提供代码示例来帮助你入门。您也可以跟随此视频了解如何运行BenchmarkDotNet基准测试。
使用BenchmarkRunner运行基准测试
该BenchmarkRunner类提供了一种非常简单的机制,用于从类型、类型列表或程序集运行您提供的所有基准。这样做的好处是简单,因为您只需运行可执行文件,它就会立即运行您配置代码运行的所有基准测试。
您可以在 GitHub或以下查看一些示例代码:
using BenchmarkDotNet.Running;
var assembly = typeof(Benchmarking.BenchmarkDotNet.BenchmarkBaseClass.Benchmarks).Assembly;
BenchmarkRunner.Run(
assembly,
args: args);
在上面的代码中,我们只是为其中一个基准指定一个程序集。但是,您可以使用Assembly.GetExecutingAssembly或找到其他方法来列出您感兴趣的类型。
使用BenchmarkSwitcher运行基准测试
该BenchmarkSwitcher类非常相似,但行为不同,因为您可以过滤要运行的基准测试。有一些细微的API差异,因为BenchmarkRunner允许您指定程序集的集合,而BenchmarkRunner(在撰写本文时)没有。
下面是一个代码示例,或者您可以查看GitHub页面:
using BenchmarkDotNet.Running;
var assembly = typeof(Benchmarking.BenchmarkDotNet.BenchmarkBaseClass.Benchmarks).Assembly;
BenchmarkSwitcher
// used to load all benchmarks from an assembly
.FromAssembly(assembly)
// OR... if there are multiple assemblies,
// you can use this instead
//.FromAssemblies
// OR... if you'd rather specify the benchmark
// types directly, you can use this
///.FromTypes(new[]
///{
/// typeof(MyBenchmark1),
/// typeof(MyBenchmark2),
///})
.Run(args);
正如您将在上面的代码注释中注意到的那样,我们可以通过多种方式调用BenchmarkSwitcher。这两种方法之间的主要区别在于,切换器将允许您在命令行上提供用户输入或筛选器。
4:自定义基准测试执行
BenchmarkDotNet提供各种选项来自定义基准测试的执行,允许您根据需要微调基准测试过程。要考虑的两个重要选项是迭代计数和预热迭代。
配置基准测试参数
如果我们想在基准测试运行中具有多样性,我们可以在公共字段上使用该[Params]属性。这类似于使用xUnit理论进行参数化测试,如果您对此很熟悉的话。对于使用此属性标记的每个字段,您基本上将构建一个基准方案矩阵以运行。
让我们看一些示例代码:
[MemoryDiagnoser]
[ShortRunJob]
public class OurBenchmarks
{
List<int>? _list;
[Params(1_000, 10_000, 100_000, 1_000_000)]
public int ListSize;
[GlobalSetup]
public void Setup()
{
_list = new List<int>();
for (int i = 0; i < ListSize; i++)
{
_list.Add(i);
}
}
[Benchmark]
public void OurBenchmark()
{
_list!.Sort();
}
}
在上面的代码中,我们有一个标有[Params]的ListSize字段。这意味着在我们的[GlobalSetup]方法中,我们能够为要运行的基准矩阵的每个变体获取一个新值。在本例中,由于只有一个参数,对于ListSize的每个值,只会有一个基准测试——所以基于指定的四个不同值,会有四个不同的基准测试。
调整每个基准的迭代计数
BenchmarkDotNet允许您控制每个基准测试方法的迭代次数。默认情况下,每个基准测试都会执行合理的次数以获得可靠的测量结果。但是,您可以通过将[IterationCount]属性应用于单个基准测试方法来调整迭代计数,并指定所需的迭代次数。
[Benchmark]
[IterationCount(10)] // Custom iteration count
public void MyBenchmarkMethod()
{
// Benchmark code here
}
在实践中我不应该这样做,我个人不必手动配置迭代计数。
每个基准测试的自定义预热迭代
预热迭代在实际基准测试开始之前执行,允许JIT编译器和CPU缓存预热。这有助于消除由基准测试方法的初始编译引起的任何性能不一致。您可以使用该[WarmupCount]属性自定义预热迭代次数。
[Benchmark]
[WarmupCount(5)] // Custom warm-up count
public void MyBenchmarkMethod()
{
// Benchmark code here
}
与迭代计数一样,我也没有遇到默认预热的问题。我建议您将这些保留为默认值,除非您知道要做什么来相应地调整它们。
5:分析和解释来自BenchmarkDotNet的基准测试结果
在优化性能方面,基准测试是软件开发过程中的重要组成部分。使用BenchmarkDotNet运行基准测试后,能够准确地分析和解释结果非常重要。在本节中,我将指导您完成分析基准测试结果和确定潜在性能改进的过程。
在BenchmarkDotNet中建立基线基准
在分析基准测试结果时,几个关键指标可以提供有关代码性能的宝贵见解。但是,可能具有挑战性的是了解您要与之进行比较的内容。如果您只是尝试比较两个实现,那么它可能感觉不那么具有挑战性。但是,当您比较许多时,您可能希望建立一个基线来查看您的改进(或回归)。
以下是我们如何将基准测试方法作为“基线”:
[Benchmark(Baseline = true)]
public void Baseline()
{
// Code under benchmark
}
解释基准测试结果
获得基准测试结果并了解关键指标后,解释调查结果以确定潜在的性能改进非常重要。以下是如何解释基准测试结果的几个示例:
- 识别瓶颈:在某些基准测试中寻找异常值或明显更长的执行时间。这可能表明代码中存在需要优化的潜在瓶颈。
- 比较性能:比较不同基准测试方法或不同版本代码的平均执行时间或中位数执行时间。这可以帮助您确定哪种方法或版本更有效。
- 发现变异性:注意标准差和百分位数,以确定基准结果中的任何显着变异性。这可以帮助您确定性能优化可以发挥作用的领域。
我发现我大部分时间都在看中位数/平均值,看看什么比较快,什么比较慢。如果你需要更深入地研究统计分析,而你只需要一个入门,我发现即使是维基百科也提供了一个合理的起点。
6:使用BenchmarkDotNet优化C#代码
作为软件工程师,我们不断努力编写不仅功能强大而且执行高效的代码。BenchmarkDotNet是一个强大的工具,可以帮助我们衡量和比较代码的性能。通过分析BenchmarkDotNet提供的结果,我们可以确定我们的代码可能表现不佳的地方,并对其进行优化以获得更好的结果。在本节中,我将分享一些根据我们从运行基准测试中收集的结果来优化C#代码的技术。您可以观看此视频,了解如何测量和调整迭代器性能,了解实际示例:
识别性能瓶颈
优化C#代码的第一步是确定性能瓶颈。BenchmarkDotNet允许我们测量代码的执行时间并比较不同的实现。通过分析基准测试结果,我们可以确定占用时间最多的领域。
让我们考虑一个示例,其中我们有一个循环,用于对大型数组执行计算。我们可以用BenchmarkDotNet来测量这个循环的不同实现的执行时间,并识别任何潜在的瓶颈。
[ShortRunJob]
public class ArrayComputation
{
private readonly int[] array = new int[1000000];
[GlobalSetup]
public void Setup()
{
// TODO: decide how you want to fill the array :)
}
[Benchmark]
public void LoopWithMultipleOperations()
{
for (int i = 0; i < array.Length; i++)
{
array[i] += 1;
array[i] *= 2;
array[i] -= 1;
}
}
[Benchmark]
public void LoopWithSingleOperation()
{
for (int i = 0; i < array.Length; i++)
{
array[i] = (array[i] + 1) * 2 - 1;
}
}
}
在此示例中,我们有两个基准方法,LoopWithMultipleOperations和LoopWithSingleOperation。第一种方法对数组的每个元素执行多个操作,而第二种方法将这些操作合并到单个计算中。通过使用BenchmarkDotNet比较这两种方法的执行时间,我们可以确定哪种实现更有效。
回想一下前面的部分,我们可以针对不同的尺寸对其进行参数化!有时,这对于查看我们在不同场景下是否具有不同的行为是必要的,因此值得探索,而不仅仅是表面。
优化循环并减少内存分配
循环通常是我们可以优化代码以获得更好性能的领域。低效的循环可能导致不必要的内存分配或冗余计算。BenchmarkDotNet可以帮助我们识别此类问题并指导我们优化代码。
考虑以下示例,其中我们有一个连接string的循环,并且我们也确保使用[MemoryDiagnoser]:
[MemoryDiagnoser]
[ShortRunJob]
public class StringConcatenation
{
private readonly string[] strings = new string[1000];
[GlobalSetup]
public void Setup()
{
// TODO: decide how you want to fill the array :)
}
[Benchmark]
public string ConcatenateStrings()
{
string result = "";
for (int i = 0; i < strings.Length; i++)
{
result += strings[i];
}
return result;
}
[Benchmark]
public string StringBuilderConcatenation()
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < strings.Length; i++)
{
stringBuilder.Append(strings[i]);
}
return stringBuilder.ToString();
}
}
在此示例中,我们有两种基准测试方法:ConcatenateStrings和StringBuilderConcatenation。第一种方法在循环中使用string串联,这可能会导致频繁的内存分配和性能不佳。第二种方法使用StringBuilder有效地连接string。通过使用BenchmarkDotNet比较这两种方法的执行时间,我们可以观察性能差异,并验证使用StringBuilder进行string级连接的有效性。
现在您知道如何使用BenchmarkDotNet!
BenchmarkDotNet是准确高效地对C#代码进行基准测试的宝贵工具。在整篇文章中,我们探讨了有效使用BenchmarkDotNet的技巧。通过利用这些,您可以准确地衡量和优化C#代码的性能。性能的提高可以带来更高效的应用程序、更好的用户体验和整体增强的软件质量!
请记住,基准测试是一个迭代过程,还有其他可用的资源和工具可以进一步帮助您优化C#代码。请考虑探索分析工具、性能计数器和其他性能分析技术,以更深入地了解应用程序的性能。
常见问题解答:如何使用BenchmarkDotNet
为什么BenchmarkDotNet对于衡量C#代码中的性能很重要?
BenchmarkDotNet对于衡量C#代码中的性能非常重要,因为它提供了一种可靠且准确的方法来对代码库的不同部分进行基准测试,使开发人员能够识别性能瓶颈并做出明智的优化。
如何在C#项目中安装和设置BenchmarkDotNet?
若要在C#项目中安装和设置BenchmarkDotNet,可以使用NuGet将BenchmarkDotNet包添加到项目中。安装后,您可以开始使用该BenchmarkDotNet框架来编写和运行基准测试。
什么是基准测试方法,如何在BenchmarkDotNet中编写它们?
BenchmarkDotNet中的基准测试方法是您编写的用于测量特定代码段性能的方法。要编写基准测试方法,您需要使用BenchmarkDotNet提供的属性来修饰您的方法并配置基准测试执行。
如何使用BenchmarkDotNet运行基准测试?
您可以使用BenchmarkRunner或BenchmarkSwitcher类在BenchmarkDotNet中运行基准测试。你还需要确保在发布模式下运行,理想情况下,无需附加调试器以获得最准确的结果。
https://www.codeproject.com/Articles/5378617/How-to-Use-BenchmarkDotNet-6-Simple-Performance-Bo