![BenchmarkDotNet 数组参数基准测试实战:用 [ArgumentsSource] 将数组作为基准参数(IntroArrayParam 深度解析)](http://pic.xiahunao.cn/yaotu/BenchmarkDotNet 数组参数基准测试实战:用 [ArgumentsSource] 将数组作为基准参数(IntroArrayParam 深度解析))
性能测试开发工具【免费下载链接】BenchmarkDotNetPowerful .NET library for benchmarking项目地址https://gitcode.com/gh_mirrors/be/BenchmarkDotNet点击查看免费下载BenchmarkDotNet 支持把数组、集合等复杂类型直接作为基准方法的参数本文以官方示例 IntroArrayParam 为骨架系统讲解[ArgumentsSource]的用法、数组参数的生命周期以及参数创建成本不计入基准这一关键语义。读完本文你将掌握如何为基准方法注入数组类型参数、如何设计多组参数化用例并从源码层面理解 BenchmarkDotNet 内部是如何解析与校验这些参数的。示例背景为什么需要数组参数常规的[Arguments]特性适合传少量标量值如int、string。但当你需要把一段真实数据——例如一个整数数组——作为基准输入时直接在特性里写数组字面量既不灵活也不可读。官方示例 IntroArrayParam 演示的正是这种场景两个基准方法都接收int[]数组和int值用[ArgumentsSource]统一提供多组数组 查找目标值的组合。该示例的核心价值在于对比两种数组查找实现的性能Array.IndexOf(array, value).NET 运行时内置的数组线性查找 API手写的for循环线性查找。通过把数组长度3与100和目标值命中第 4 个元素以及不存在的101作为参数展开可以一次跑出多种组合下的性能差异这正是 BenchmarkDotNet 参数化的典型用法。完整示例源码以下即官方示例的完整实现仓库路径samples/BenchmarkDotNet.Samples/IntroArrayParam.csusing BenchmarkDotNet.Attributes; namespace BenchmarkDotNet.Samples { public class IntroArrayParam { [Benchmark] [ArgumentsSource(nameof(Data))] public int ArrayIndexOf(int[] array, int value) Array.IndexOf(array, value); [Benchmark] [ArgumentsSource(nameof(Data))] public int ManualIndexOf(int[] array, int value) { for (int i 0; i array.Length; i) if (array[i] value) return i; return -1; } public IEnumerableobject[] Data() { yield return new object[] { new int[] { 1, 2, 3 }, 4 }; yield return new object[] { Enumerable.Range(0, 100).ToArray(), 4 }; yield return new object[] { Enumerable.Range(0, 100).ToArray(), 101 }; } } }要点拆解两个基准方法均带有[ArgumentsSource(nameof(Data))]参数提供者为同类的公共方法Data()Data()返回IEnumerableobject[]每个object[]元素对应一组参数按声明顺序映射到基准方法的形参(int[] array, int value)三组用例分别是长度 3 的数组查找存在的值4、长度 100 的数组查找存在的值4、长度 100 的数组查找不存在的值101。关于返回类型IEnumerableobject[]是历史兼容写法目前仍受支持但官方文档见 IntroArgumentsSource 说明已不再推荐改用带类型的元素形式如IEnumerable(int[] array, int value)或IEnumerableobject[]之外的强类型集合更佳配套的 Code Fix 也提供了自动转换支持。若数据源仅声明为非泛型IEnumerableBenchmarkDotNet 会直接拒绝因为生成代码无法从中推断参数类型。关键语义参数创建成本不计入基准原文档在示例开头特别用[!WARNING]强调了一个容易踩坑的事实创建参数arguments的开销不会计入基准结果。也就是说当你传入数组参数时BenchmarkDotNet 会在运行基准之前分配好这些数组基准循环中只测量方法体本身的执行时间数组的分配、填充等准备工作被完全排除在计时之外。这一语义从源码结构上可以得到印证在 BenchmarkConverter.cs 的GetParameterInstances流程中参数实例ParameterInstances是在生成基准用例阶段一次性构建的——[ArgumentsSource]指向的源方法通过GetValidValuesForParamsSourceAsync被读取并物化为值列表随后逐组转换为ParameterInstances挂到每个BenchmarkCase上。也就是说数据源只在用例发现阶段被枚举一次之后每个工作负载迭代直接复用已固化的参数值因此分配数组的耗时不会进入每次迭代的测量窗口。对应的设计含义是基准测试的是对给定数据的处理逻辑而非数据本身的构造。这在对比不同算法在相同输入下的表现时非常合理但如果你要测的恰恰是创建数据结构 处理的完整链路那就需要把构造逻辑写进基准方法体内而不是放进ArgumentsSource。与 [Arguments] / [Params] 的分工数组参数属于 BenchmarkDotNet 参数化体系的一部分官方文档 Parameterization 专题 将其与其他参数化手段并列。从 BenchmarkConverter.cs 的处理逻辑可以清晰看到它们的分工[Arguments]直接在特性中给出若干组值GetParameterInstances会校验值的个数与基准方法形参个数严格一致否则抛出InvalidOperationException[ArgumentsSource]从方法/属性读取多组参数适合值多、值复杂如数组、对象的场景[Params]/[ParamsSource]作用于字段或属性对同一基准展开多组配置。若基准方法带形参但既没有[Arguments]也没有[ArgumentsSource]AssertMethodHasCorrectSignatureBenchmarkConverter.cs会直接判定为非法声明并抛出InvalidBenchmarkDeclarationException。数组参数在结果表中的呈现运行该示例dotnet run -c Release后结果表如下引自 IntroArrayParam.md 的 Output 部分| Method | array | value | Mean | Error | StdDev | Allocated | |-------------- |----------- |------ |----------:|----------:|----------:|----------:| | ArrayIndexOf | Array[100] | 4 | 15.558 ns | 0.0638 ns | 0.0597 ns | 0 B | | ManualIndexOf | Array[100] | 4 | 5.345 ns | 0.0668 ns | 0.0625 ns | 0 B | | ArrayIndexOf | Array[3] | 4 | 14.334 ns | 0.1758 ns | 0.1558 ns | 0 B | | ManualIndexOf | Array[3] | 4 | 2.758 ns | 0.0905 ns | 0.1208 ns | 0 B | | ArrayIndexOf | Array[100] | 101 | 78.359 ns | 1.8853 ns | 2.0955 ns | 0 B | | ManualIndexOf | Array[100] | 101 | 80.421 ns | 0.6391 ns | 0.5978 ns | 0 B |观察要点array列数组参数在结果表中以Array[N]的形式呈现其长度Array[3]、Array[100]便于快速识别不同规模的输入Allocated列全为0 B这正是数组在基准前已分配好的直接证据——测量区间内两个方法都没有额外分配内存数值差异ManualIndexOf在小数组、命中靠前元素时明显更快2.758 nsvs14.334 ns而在 100 元素数组上查找不存在的101时两者耗时接近78.359 nsvs80.421 ns因为此时两者都必须遍历完整数组。注意这些数字是特定硬件上的示例输出在不同机器上会有差异但相对趋势具有参考意义。数据源机制的源码级剖析特性定义[ArgumentsSource]定义在 src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs继承自PriorityAttribute用于控制参数展开顺序仅可作用于方法AttributeTargets.Method不可重复使用[AttributeUsage(AttributeTargets.Method, AllowMultiple false)] public class ArgumentsSourceAttribute : PriorityAttribute { public string Name { get; } public Type? Type { get; } public ArgumentsSourceAttribute(string name) { ... } public ArgumentsSourceAttribute(Type type, string name) { ... } }两个构造函数对应两种引用方式[ArgumentsSource(nameof(Data))]引用同类成员[ArgumentsSource(typeof(OtherClass), nameof(OtherClass.Data))]引用外部类型中的成员此时Type属性非空。参数解析与校验解析入口BenchmarkConverter.GetParameterInstancesBenchmarkConverter.cs在检测到[ArgumentsSource]后通过GetValidValuesForParamsSourceAsync读取源数据再用SmartParamBuilder.CreateForArguments逐组构建ParameterInstances。数据源只会被枚举一次枚举结果被固化到用例中。行内取值SmartParamBuilder.CreateForArgumentsSmartParamBuilder.cs中整组参数一行只做一次SourceRead即每个用例只读取一次源而不是为每个形参分别读取随后按声明类型把一行拆给各个参数。数量校验如果源方法产出的参数个数与基准方法形参个数不一致会抛出InvalidBenchmarkDeclarationException提示invalid number of arguments provided by [ArgumentsSource(...)]。返回类型校验SourceReturnTypeValidatorSourceReturnTypeValidator.cs在运行期对每个[ArgumentsSource]成员做可达性与可读性校验并注明它是BDN1306、BDN1308、BDN1311、BDN1504这些分析器规则的运行时对等物对应实现见 ArgumentsAttributeAnalyzer.cs用于约束源必须返回可枚举类型产出的元素必须与形参匹配等约束。数据源的形式要求结合 IntroArgumentsSource 文档 与 参数化专题[ArgumentsSource]指向的源需满足是公共方法或属性返回IEnumerableT或IAsyncEnumerableT元素类型必须是具名类型——仅返回非泛型IEnumerable会被拒绝元素到形参的映射由元素类型决定单参数基准元素即参数值本身如IEnumerableTimeSpan对应TimeSpan参数多参数基准每个元素是一组完整参数如IEnumerable(int[] array, int value)或本文示例的IEnumerableobject[]by-ref-like 参数如ReadOnlySpanbyte源产出的是其转换源类型如IEnumerablebyte[]源可以是实例方法也可以是静态方法若源不在基准类中需在特性构造器中用typeof显式指定返回IAsyncEnumerableT的源会被异步等待读取且源方法可带[EnumeratorCancellation] CancellationToken可选参数。扩展实践复杂类型与数组组合如果数组元素本身是自定义类型官方文档提供了更完整的实践模式见 IntroArgumentsSource.md 的 Another example对非基础类型参数重写ToString()即可定制结果表中该参数的显示名。示例中SomeClass重写ToString()返回${Values.Length} items最终结果表里该列显示为10 items、15 items而非默认的类型全名。同理当你以数组为参数时若数组元素是自定义对象也可通过自定义参数包装类型并重写ToString()来控制array列的显示文本让结果表更易读。小结通过[ArgumentsSource]向基准方法传入数组参数是把真实数据规模纳入基准设计的标准做法数据源在用例发现阶段一次性构建数组分配成本不计入测量结果表中以Array[N]展示输入规模。配合源码层的解析BenchmarkConverter、行内取值与数量校验SmartParamBuilder以及运行期校验SourceReturnTypeValidator你可以放心地把数组、集合乃至异步数据源投入参数化基准。更完整的参数化手段对照可继续阅读 Parameterization 专题 与同系列示例 IntroArguments、IntroArgumentsSource、IntroParamsSource。赞分享性能测试开发工具【免费下载链接】BenchmarkDotNetPowerful .NET library for benchmarking项目地址https://gitcode.com/gh_mirrors/be/BenchmarkDotNet点击查看免费下载相关推荐BenchmarkDotNet 参数化基准四用 [ArgumentsSource] 为基准方法批量提供参数BenchmarkDotNet 参数化基准四用 ArgumentsSource 为基准方法批量提供参数 导读 当基准方法的参数数量较多、无法或不宜通过性能测试开发工具BenchmarkDotNet 参数化基准[Params] 特性实现多参数组合测试的完整指南BenchmarkDotNet 参数化基准 Params 特性实现多参数组合测试的完整指南 导读 本指南以 BenchmarkDotNet 官方示例 Intr性能测试开发工具用 [ParamsAllValues] 自动穷举枚举与布尔参数的 BenchmarkDotNet 参数化基准测试用 ParamsAllValues 自动穷举枚举与布尔参数的 BenchmarkDotNet 参数化基准测试 导读 ParamsAllValues 是 Benc性能测试开发工具上一篇awesome-low-level-design微内核架构构建灵活可扩展的底层系统下一篇Waterius 水表数据永不丢失的秘密EEPROM 环形缓冲 CRC8 校验存储原理全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考