.NET 性能提示 – 基准测试





5.00/5 (7投票s)
微基准测试你的 C# - 陷阱以及一个帮助你规避它们的工具
微基准测试
微优化一直名声不佳,尽管我认为了解这些知识可以帮助你编写更好的代码。我们还应该明确区分它与微基准测试的区别,后者有点冒险,但如果你知道如何正确操作,则要安全得多。
现在,如果你正将它与唐纳德·克努斯 44 年前(错误的)引言联系起来,那么现在已经有很多更好、更平衡的论点来讨论这个问题;并且有很多工具可以帮助你更好地进行优化。我认为一些混淆源于对它在更广泛的性能知识和工具中的位置的误解。记住,他说过“我们不应该放弃那关键的 3% 的机会”,根据我的经验,这听起来很合理。
常见陷阱
使用老方法 System.Diagnostics.Stopwatch
会遇到很多陷阱,例如
- 操作的迭代次数不足,导致耗时可以忽略不计
- 忘记禁用编译器优化
- 无意中测量了与你测试内容无关的代码
- 未能“预热”以考虑 JIT 成本和处理器缓存
- 忘记将设置代码与被测代码分离
- 等等...
这时,亚当·西特尼克的 BenchmarkDotNet 就派上用场了。它解决了上述所有问题,甚至更多。
BenchmarkDotNet
它作为 NuGet 包提供
并且具有出色的文档
你可以通过对象、属性或流畅接口选择配置方法。你可以配置的内容包括
- 比较 RyuJIT(自 .NET46 x64 以来,以及自 Core 2.0 x86 以来)和 Legacy JIT
- 比较 x86 与 x64
- 比较 Core 与完整框架(又名 Clr)
- JIT 内联(以及尾调用,在我的经验中,在 64 位应用程序中,这可能会与内联令人困惑地相似)
- 你甚至可以测试来自我上次提示的 Server GC 和 Workstation GC 之间的差异
一个非常简单的例子
对于许多场景,仅使用标准设置启动即可。这是一个我用来比较 DateTime
、DateTimeOffset
和 NodaTime 的例子。
[ClrJob, CoreJob]
- 我使用了属性方法进行配置,装饰类以使BenchmarkDotNet
在 .NET 完整框架和 Core 上运行测试。[Benchmark]
- 用于装饰我想基准测试的每个方法
[ClrJob, CoreJob]
public class DateTimeBenchmark
{
private DateTime dateNow;
private DateTimeOffset dateOffset;
private ZonedDateTime nowInIsoUtc;
[Benchmark]
public void DateTime_Now()
{
dateNow = DateTime.Now;
}
[Benchmark]
public void DateTime_Utc()
{
dateNow = DateTime.UtcNow;
}
[Benchmark]
public void DateTimeOffset_Now()
{
dateOffset = DateTimeOffset.Now;
}
[Benchmark]
public void DateTimeOffset_Utc()
{
dateOffset = DateTimeOffset.UtcNow;
}
[Benchmark]
public void NodaTime_ZonedDateTime()
{
nowInIsoUtc = SystemClock.Instance.GetCurrentInstant().InUtc();
}
}
一个启动的调用
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<DateTimeBenchmark>();
}
请注意,如果你想尝试这段代码,你需要安装 BenchmarkDotNet
和 NodaTime
的 NuGet 包。
输出
显然,这不能替代理解基类库 (BCL) 中 DateTime
类背后的实现细节;但这是一个快速简便的方法,可以初步识别问题区域。事实上,这只是我向同事解释 ISO 8601、时区、夏令时以及 DateTime.Now
的陷阱时的一个小部分。
一个让我困惑的地方
一个陷阱是,如果你正在测试 Core 和完整框架,请确保创建一个新的 Core 控制台应用程序并编辑你的 csproj 文件,将 <TargetFramework> 替换为例如:
<TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>