BenchmarkDotNet 泛型基准测试实战使用 [GenericTypeArguments] 为开放泛型 Benchmark 类注入类型参数【免费下载链接】BenchmarkDotNetPowerful .NET library for benchmarking项目地址: https://gitcode.com/gh_mirrors/be/BenchmarkDotNet泛型 Benchmark 类是 C# 基准测试中常见的进阶场景但 BenchmarkDotNet 默认无法直接运行未闭合的泛型类型open generic type。本文以官方示例IntroGenericTypeArguments为主线讲解如何通过[GenericTypeArguments]特性为泛型 Benchmark 类指定一组或多组类型实参使同一套基准逻辑可以针对不同类型分别测量并深入仓库源码剖析其底层闭合机制、可重复特性用法以及内置 Roslyn 分析器的约束校验帮助读者写出可复制的多类型泛型基准测试。示例概述IntroGenericTypeArguments官方示例位于 samples/BenchmarkDotNet.Samples/IntroGenericTypeArguments.cs完整源码如下using BenchmarkDotNet.Attributes; namespace BenchmarkDotNet.Samples { [GenericTypeArguments(typeof(int))] [GenericTypeArguments(typeof(char))] public class IntroGenericTypeArgumentsT { [Benchmark] public T Create() Activator.CreateInstanceT(); } }该示例的核心思想非常简洁定义了一个开放泛型基准类IntroGenericTypeArgumentsT其中含有一个[Benchmark]标记的方法Create()Create()通过Activator.CreateInstanceT()创建类型T的实例并返回用于测量通过反射创建不同类型实例的开销类上通过两个[GenericTypeArguments]特性分别指定typeof(int)与typeof(char)表示将T分别闭合为int和char各跑一轮基准。运行后BenchmarkDotNet 会为该类生成两个独立的基准用例Benchmark CaseIntroGenericTypeArgumentsint.Create()与IntroGenericTypeArgumentschar.Create()分别输出各自的时间/内存统计结果。该示例对应的官方文档为 docs/articles/samples/IntroGenericTypeArguments.md其中以!code-csharp指令直接内嵌了上述源码并给出了指向该示例的永久链接BenchmarkDotNet.Samples.IntroGenericTypeArguments。特性定义GenericTypeArgumentsAttribute 的三种构造形式[GenericTypeArguments]特性的定义位于 src/BenchmarkDotNet.Annotations/Attributes/GenericTypeArgumentsAttribute.cs其关键声明如下[AttributeUsage(AttributeTargets.Class, AllowMultiple true)] public class GenericTypeArgumentsAttribute : Attribute { public Type[] GenericTypeArguments { get; } public GenericTypeArgumentsAttribute(Type type) { ... } // 单类型实参 public GenericTypeArgumentsAttribute(Type type1, Type type2) { ... } // 双类型实参 public GenericTypeArgumentsAttribute(Type type1, Type type2, Type type3) { ... } // 三类型实参 }从定义可以看出三个要点目标对象AttributeTargets.Class只能标注在类上Benchmark 类本身可重复AllowMultiple true允许在同一类上叠加多个[GenericTypeArguments]特性每个特性对应一组类型实参从而生成多组闭合后的基准参数个数构造函数提供 1 个、2 个、3 个Type三种重载分别对应泛型类型定义中T、T1, T2、T1, T2, T3三种常见情况。类型实参数必须与类的泛型参数个数一致否则无法成功闭合详见下文分析器校验部分。此外特性构造函数的Type参数均标注了[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]这是为 Native AOT / 裁剪场景保留类型元数据确保泛型实参类型在发布裁剪后仍能被反射完整访问。底层原理GenericBenchmarksBuilder 如何闭合泛型类型BenchmarkDotNet 会在发现基准类阶段将带[GenericTypeArguments]的开放泛型类型转换为可运行的具体闭合类型。核心实现位于 src/BenchmarkDotNet/Helpers/GenericBenchmarksBuilder.csinternal static Type[] GetRunnableBenchmarks(IEnumerableType types) types.Where(type type.ContainsRunnableBenchmarks()) .SelectMany(BuildGenericsIfNeeded) .Where(x x.isSuccess) .Select(x x.result) .ToArray(); internal static IEnumerable(bool isSuccess, Type result) BuildGenericsIfNeeded(Type type) { var typeArguments type.GetCustomAttributes(true).OfTypeGenericTypeArgumentsAttribute() .Select(x x.GenericTypeArguments) .ToArray(); if (typeArguments.Any()) return BuildGenericTypes(type, typeArguments); return [(true, type)]; } private static IEnumerable(bool isSuccess, Type result) BuildGenericTypes(Type type, IEnumerableType[] typeArguments) typeArguments.Select(genericArg (type.TryMakeGenericType(genericArg, out var builtType), builtType)); private static bool TryMakeGenericType(this Type type, Type[] typeArguments, out Type result) { try { result type.MakeGenericType(typeArguments); return true; } catch (ArgumentException) { result type; return false; } }整个流程可以拆解为四步过滤可运行类型ContainsRunnableBenchmarks()先排除抽象类再结合是否是开放泛型进行判断详见下文读取特性通过反射读取类上的所有GenericTypeArgumentsAttribute得到一组组类型实参数组逐一闭合对每组类型实参调用type.MakeGenericType(genericArg)把开放泛型类型定义如IntroGenericTypeArguments实例化为闭合类型如IntroGenericTypeArgumentsint失败兜底MakeGenericType在实参个数不匹配或实参约束不满足时会抛出ArgumentExceptionTryMakeGenericType将其捕获并标记isSuccess false后续Where(x x.isSuccess)会直接丢弃该组失败类型不会让整个运行崩溃。同时src/BenchmarkDotNet/Extensions/ReflectionExtensions.cs 中的ContainsRunnableBenchmarks与IsRunnableGenericType定义了什么样的泛型类是可达的internal static bool ContainsRunnableBenchmarks(this Type type) { var typeInfo type.GetTypeInfo(); if (typeInfo.IsAbstract || typeInfo.IsGenericType !IsRunnableGenericType(typeInfo)) return false; return typeInfo.GetBenchmarks().Any(); } private static bool IsRunnableGenericType(TypeInfo typeInfo) (!typeInfo.IsGenericTypeDefinition || typeInfo.GenericTypeArguments.Any() || typeInfo.GetCustomAttributes(true).OfTypeGenericTypeArgumentsAttribute().Any()) typeInfo.DeclaredConstructors.Any(ctor ctor.IsPublic ctor.GetParameters().Length 0);关键结论有两点一个未闭合的泛型类型定义IsGenericTypeDefinition为 true 且无已闭合实参只有在携带[GenericTypeArguments]特性时才会被视为可运行否则会被直接过滤掉——这解释了为什么示例类必须标注该特性无论是否泛型类都必须具备公共无参构造函数public且参数个数为 0因为 BenchmarkDotNet 需要以无参方式实例化基准类来执行每个[Benchmark]方法。多类型实参覆盖一参、两参、三参泛型GenericTypeArgumentsAttribute的三种构造函数覆盖了最常见的泛型元数arity。官方测试 tests/BenchmarkDotNet.Tests/GenericBuilderTests.cs 对这三种情况均有覆盖单类型实参与 IntroGenericTypeArguments 示例完全一致[GenericTypeArguments(typeof(int))] [GenericTypeArguments(typeof(char))] public class OneArgGenericBenchmarkT { [Benchmark] public T CreateT() Activator.CreateInstanceT(); }对应测试断言GetRunnableBenchmarks返回恰好 2 个类型且分别为OneArgGenericBenchmarkint与OneArgGenericBenchmarkchar。双类型实参[GenericTypeArguments(typeof(int), typeof(char))] [GenericTypeArguments(typeof(char), typeof(string))] public class TwoArgGenericBenchmarkT1, T2 { [Benchmark] public T1 CreateT1() Activator.CreateInstanceT1(); [Benchmark] public T2 CreateT2() Activator.CreateInstanceT2(); }三类型实参[GenericTypeArguments(typeof(int), typeof(char), typeof(string))] [GenericTypeArguments(typeof(char), typeof(string), typeof(byte))] public class ThreeArgGenericBenchmarkT1, T2, T3 { [Benchmark] public T1 CreateT1() Activator.CreateInstanceT1(); [Benchmark] public T2 CreateT2() Activator.CreateInstanceT2(); [Benchmark] public T3 CreateT3() Activator.CreateInstanceT3(); }由测试可以确认每组[GenericTypeArguments]是一组独立的类型实参组合N 个特性就会生成 N 个闭合类型每个闭合类型各自作为独立基准参与测量互不干扰。分析器约束编写时的编译期保护BenchmarkDotNet 提供了配套的 Roslyn 分析器src/BenchmarkDotNet.Analyzers其中的 BenchmarkClassAnalyzer.cs 定义了与泛型类型实参相关的两条诊断规则在编译期即可拦截错误用法ClassWithGenericTypeArgumentsAttributeMustBeGenericRule[GenericTypeArguments]只能标注在泛型类上。若把该特性放在非泛型类上分析器会直接报错因为类型实参无处可闭GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule特性提供的类型实参数必须与类声明的泛型参数个数一致。例如类声明为T1, T2却只传了一个typeof(int)编译期即被标记为错误若在无分析器的环境下运行这一错误会落入GenericBenchmarksBuilder.TryMakeGenericType的ArgumentException捕获分支对应的闭合类型会被静默丢弃。这两条规则进一步印证了特性的使用契约泛型类 匹配数量的类型实参 公共无参构造函数三者缺一不可。运行与验证在仓库根目录下可通过以下方式运行该示例dotnet run -c Release --project samples/BenchmarkDotNet.Samples --filter *IntroGenericTypeArguments*运行结束后输出结果中会看到两组独立的结果行分别对应IntroGenericTypeArgumentsint与IntroGenericTypeArgumentschar两个闭合类型的Create基准各自包含 Mean、Error、StdDev 等统计列可用于比较同一段泛型代码在不同类型实参下的性能差异。若想在不启动完整基准运行的情况下验证泛型闭合逻辑本身可直接运行官方单元测试dotnet test tests/BenchmarkDotNet.Tests --filter FullyQualifiedName~GenericBuilderTestsGenericBuilderTests中的TestBuildGenericWithOneArgument、TestBuildGenericWithTwoArguments、TestBuildGenericWithThreeArguments三个测试用例分别验证了 1 参、2 参、3 参泛型在[GenericTypeArguments]驱动下的闭合结果数量与具体类型是与示例源码相互印证的底层行为测试。小结IntroGenericTypeArguments展示了 BenchmarkDotNet 中一套泛型基准逻辑、多组类型实参的标准做法用[GenericTypeArguments]可叠加为泛型 Benchmark 类声明一组或多组类型实参底层由 GenericBenchmarksBuilder 通过反射读取特性并调用MakeGenericType逐一闭合失败组合被安全跳过闭合后的每个类型成为一个独立基准用例与普通 Benchmark 类同等参与预热、迭代与统计编译期由 BenchmarkClassAnalyzer 的规则约束非泛型类不可用与实参个数必须匹配两类错误用法。对于需要在不同泛型实参如int/long/string/自定义结构体下重复测量同一算法实现的场景该特性是保持代码 DRY 且保证测量独立性的推荐方案。【免费下载链接】BenchmarkDotNetPowerful .NET library for benchmarking项目地址: https://gitcode.com/gh_mirrors/be/BenchmarkDotNet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
3个坑解决JS编码慢问题一文搞懂性能优化实战 3个坑解决JS编码慢问题一文搞懂性能优化实战 控制台满屏红字,StackTrace 长得像天书,点进去全是 native code 或者混淆后的 eval… · 2026/9/23 10:17:49
瑞利数入门到精通:3个代码细节让仿真速度翻倍 瑞利数入门到精通:3个代码细节让仿真速度翻倍 看了一堆流体力学教程,代码能跑通,但一到实际工程场景就卡壳?别急,这就是典型的“懂原理不懂落地”。很多工程师在计算自然对流时,盯着瑞利数(Rayleigh… · 2026/9/23 10:17:36
搞定编制军衔源码:3个完整示例彻底解决Stacktrace报错 搞定编制军衔源码:3个完整示例彻底解决Stacktrace报错 报错堆栈一屏红,StackTrace 看得人头皮发麻?别慌,这不是你代码写得烂,是“编制军衔”这块硬骨头没啃透。很多转岗做后端或系统架构的同事,一碰到这种涉及状态机、权限校验和… · 2026/9/23 10:17:17
3个真实项目教你用鼓励英文搞定面试避坑指南 3个真实项目教你用鼓励英文搞定面试避坑指南 面试官问起“为什么用Python写后端”,你支支吾吾答不上来?这种尴尬,比代码报错更让人窒息。别再背八股文了,真正让你过关的,是能讲清楚一个完整项目怎么跑起来的。… · 2026/9/23 11:08:31
NetSuite总账模型全解析:从科目表到多账簿的财务实战指南 1. 从一张科目表说起:NetSuite 总账模型的底层逻辑NetSuite 的总账模型,简单说就是把“企业发生了一笔经济业务”这件事,翻译成“会计语言”,再落到一张资产负债表和利润表上的完整链路。很多第一次启动 NetSuite 的财务负责人&am… · 2026/9/23 11:08:31
扩容u盘避坑指南 3天搞定U盘扩容避坑指南:保姆级教程让小白变专家 你是不是也遇到过这种崩溃瞬间:手里攥着 32GB 的 U 盘,看着里面仅剩 100MB 的可用空间,想扩容到 64GB… · 2026/9/23 11:08:30
3分钟搞定鼠标左右键事件,手写实现绕过框架坑 3分钟搞定鼠标左右键事件,手写实现绕过框架坑 学会语法却不知怎么搭项目?很多开发者卡在“事件监听”这个坎上。浏览器原生 API 很简单,但框架里总出错。今天不讲虚的,直接 手写实现 一个跨平台的鼠标左右键检测模块。 我们在 CSDN… · 2026/9/23 11:08:24
LanceDB Python 绑定开发指南:从环境搭建到提交高质量的 PR 向量数据库数据库人工智能后端 【免费下载链接】lancedb Developer-friendly OSS embedded retrieval library for multimodal AI. Search More; Manage Less. 项目地址: https://gitcode.com/gh_mirrors/la/lancedb 点击查看 免费下载 本篇指南以 python/CONTRIBU… · 2026/9/23 11:08:18
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29