BenchmarkDotNet测C#性能核心是“测得准”:测试类必须public、方法须无参且返回值以防死码消除、必加[MemoryDiagnoser]、仅在Release模式下运行,否则结果不可信。

用 BenchmarkDotNet 做 C# 性能测试,测不准比不测更危险——它默认屏蔽 JIT 预热、死码消除和时钟抖动,但一旦类结构、方法签名或运行环境出错,结果就全不可信。
测试类必须是 public,且不能嵌套
这是最常被忽略的硬性门槛。BenchmarkDotNet 在扫描时只识别顶层 public 类,internal、private 或嵌套类(如 Program.Benchmarks)会被完全跳过,不报错、不提示,只输出 “0 benchmarks found”。
- 错误写法:
internal class StringBench、class Program { public class Bench { } } - 正确写法:
public class StringBench,单独文件,无继承、无泛型约束 - 若用 IDE 自动生成类,注意检查生成代码顶部是否带
public修饰符
[Benchmark] 方法必须无参、返回 void 或具体类型
带参数的方法直接被忽略;返回 void 虽合法,但存在死码消除风险——JIT 可能因返回值未被消费而整段优化掉;推荐返回计算结果(如 string、int),让 BenchmarkDotNet 内部强制保留逻辑。
- 禁止:
public string Test(int n)→ 报错 “Method has parameters” - 不推荐:
public void ConcatWithPlus()→ 若没做任何副作用,可能被优化为空操作 - 推荐:
public string ConcatWithPlus() => _a + _b;→ 返回值被框架捕获,逻辑保真 - 异步方法需显式声明
Task并加[Benchmark],否则不识别
[MemoryDiagnoser] 不是可选,而是必开
只看 Mean 会漏掉关键瓶颈。两个算法耗时接近,但一个单次分配 2MB,另一个只分配 128B——后者在高频服务中稳得多。不加这个特性,输出里压根没有 Allocated 列,也看不到 Gen0 次数。
- 必须加:
[MemoryDiagnoser]是属性,不是命名空间引用 - Windows 下想看 GC 代际细节,得额外安装
BenchmarkDotNet.Diagnostics.Windows包 - .NET 6+ 能识别
Span<char>.IndexOf等零分配模式,但前提是开了[MemoryDiagnoser] - 没开它,等于只看了半张体检报告
Release 模式 + 当前 SDK 运行环境决定结果可信度
同一段代码,在 .NET 6、.NET 8、x64、ARM64、Debug 下跑,结果可能差 2–5 倍。这不是误差,是真实差异。Debug 模式下 JIT 优化被禁用,BenchmarkDotNet 会直接拒绝运行并报错 “Assembly is non-optimized”。
- 必须用
dotnet run -c Release启动,不能靠 IDE 的“启动”按钮(它默认 Debug) - 项目文件必须是 SDK 风格:
<Project Sdk="Microsoft.NET.Sdk">,且明确指定<TargetFramework>net8.0</TargetFramework> - 跨版本对比?不能只改 SDK,得手动配置:
Job.ShortRun.WithRuntime(CoreRuntime.Core80) - M1/M2 Mac(ARM64)上,某些 SIMD 路径未启用,
Span<char>.IndexOf可能比 x64 慢 15%
真正难的不是写几个 [Benchmark],而是确保整个测量链路没被 JIT、GC、OS 干扰器悄悄绕过——从类可见性、方法签名、内存诊断到运行时环境,每个环节都卡着“准不准”的命门。



















