关于 dotnet-inspect-performance
这项技能指导使用 dotnet-inspect 为 .NET 程序集筛选优化候选,并检查可处理的代码模式。它结合调用图影响力、性能与资源分析,并将静态发现视为需运行时验证的线索。
主要功能
- 01按调用图影响力排列成员
- 02按类别筛查性能模式
- 03按优先级和置信度过滤候选
- 04追踪分配与调用点证据
- 05发现异常路径中的 ArrayPool 周转
适用场景
- 01选择待优化审查的方法
- 02查找循环中的重复分配
- 03调查字符串物化候选
- 04筛选需性能分析的池化周转案例
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ richlander/ dotnet-inspect --skill dotnet-inspect-performance安装后在 Claude Code 中输入/dotnet-inspect-performance确认已加载。
详细介绍
dotnet-inspect:性能分析与分类
使用此技能,在 .NET 程序集中找出最值得优先优化或加固的成员,并根据可执行的改写模式对其进行分类。这项分析仍处于实验阶段;各节名称和信号集可能会随版本变化。
dnx dotnet-inspect -y -- <command>
优先按杠杆作用排序
Top Leverage 根据调用图杠杆作用对成员排序:直接调用方、Root Reach(可通过传递调用到达某成员的不同入口点)、扇出、深度和循环调用。分析整个库时从这里开始,然后再缩小到某个类型。
dnx dotnet-inspect -y -- library MyLib.dll -S "Top Leverage"
dnx dotnet-inspect -y -- type MyType --library MyLib.dll --all -S "Top Leverage"
排序结果包含可复制的 Stable 选择器、Visibility 和 Selector。
添加 --all 以包含非公共成员。
如需查看整个库的结构报告,而非排序后的候选项,请选择显式的
Library Metrics 部分:
dnx dotnet-inspect -y -- library MyLib.dll -S "Library Metrics"
该部分会报告由 Research 维护的已编译 IL 总体指标,不会给出总体评分或源代码层面的含义。
根据改写模式进行分类
库级分类按种类拆分为 @Performance 下的各个部分
(Performance: Boxing、Performance: Arrays、Performance: Strings、
Performance: Closures and Delegates 等)。结构发现会列出编写好的种类,但不会运行分析;添加 --effective 仅保留在此库中有发现结果的种类。计数会执行所选分组,并包含结果数为零的种类。类型/成员范围会保留聚焦的 Performance Triage 视角。
dnx dotnet-inspect -y -- library MyLib.dll -D @Performance
dnx dotnet-inspect -y -- library MyLib.dll -D @Performance --effective
dnx dotnet-inspect -y -- library MyLib.dll -S @Performance --count
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance: Boxing" --jsonl
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance: Strings" --jsonl
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
--where "Priority>=high" --top 20 --tsv
dnx dotnet-inspect -y -- library MyLib.dll \
--triage-shape scan-method-in-loop-call,scan-method-in-recursive-traversal,linq-scan-in-loop,string-build-in-loop \
--top 20 --tsv
dnx dotnet-inspect -y -- library MyLib.dll --triage-shape capturing-delegate --top 10 --jsonl
针对 IL 可见的开销(分配:box、newarr、delegate newobj、 ToArray/ToList/Concat),而非由 JIT 处理的问题(isinst/castclass 折叠、 去虚拟化、边界检查消除、空值检查折叠)。
使用 --where "Priority>=high" 进行信号密集的首轮筛查,使用 --loop 查找重复开销,使用 --min-confidence high|medium|low 设置证据/改写置信度下限,使用 --triage-shape 指定一个或多个模式,使用 --top N 获取精选排序结果的前缀。提供其中任意标志都会自动选择适用的性能视角。在库级行格式中,Performance:* 会将两个或更多包含结果的种类部分展平为一个表格,并在首列添加 Kind。如果筛选后仅剩一个包含结果的种类,行格式会采用该种类的具体架构,不包含 Kind;如果必须保留种类标识符,请使用结构化的 --json。@Performance 还包含异构部分,因此应将其用于发现、计数、Markdown 或 JSON 文档。
--top 会在渲染前缩小排序数据的范围;--rows N 会在渲染后限制行数。常见模式
包括 capturing-delegate、box-value-type、
generic-parameter-object-box(为 object.Equals 装箱的无约束泛型值;仅在实例化为值类型时分配,除非循环证据证明存在重复,否则优先级从中等开始)、small-array、
cache-lookup-factory-delegate(传递给 ConcurrentDictionary.GetOrAdd 的逐次调用实例工厂)、
linq-scan-in-loop、
scan-method-in-loop-call(从调用方循环中调用的线性扫描辅助方法)、
scan-method-in-recursive-traversal(每个递归遍历节点都会重复执行一次扫描)、
materialize-in-loop(可移至循环外的循环不变量 ToArray/ToList)、
string-build-in-loop、string-materialization、
enumerator-allocation、
async-state-machine、sync-call-in-async(调用签名兼容的 Async 同级方法的异步方法)以及
allocation-hotspot。请显式查询算法模式:
由于静态分析无法证明被扫描序列会随循环或遍历增长,扫描辅助方法的置信度会保持较低,因此 --min-confidence high 筛查会有意将它们排除在外。
string-materialization 行标识能够生成字符串的精确框架操作:拼接、连接、格式化、String.Create、字符串构造函数、内插字符串处理程序终结、StringBuilder.ToString 和 Encoding.GetString。Operation 指明策略,例如 string.concat 或 string.builder-finalization。这些只是可能发生的物化,并不保证会分配对象:它们不能证明有新对象分配、分配字节数、发生频率或重写安全性。修改代码前,请通过有代表性的分配跟踪或基准测试确认候选项。第一个版本仅限过程内分析;如果调用方将字符串创建委托给辅助方法,就不会继承该辅助方法的精确 Finding。
选择 sync-call-in-async 候选项后,在成员的 Call Graph 上投影 --fields AsyncAlternatives,将其机会数量带入杠杆视图:
dnx dotnet-inspect -y -- member MyType ReadAsync:1 --library MyLib.dll \
-S "Call Graph" --fields "Fanin,Depth,Loop,AsyncAlternatives"
图表提示提供的是源成员级上下文,不能替代分诊。使用 Performance Triage 查看精确 Finding、物理 EvidenceMethod/IL 凭据,以及建议的异步同级方法。在传统异步方法中,物理调用位于生成的 MoveNext 中,因此图表不会伪造一条从源方法直接指向该同步 API 的边。
默认的 Triage 排序将 Priority 与 Confidence 分开。Priority 是对静态可操作性的判断:有直接证据的算法放大效应、可避免的缓存查找工厂分配,以及可采取行动的高分配权重,优先级较高;缺少共享源标识的递归扫描辅助方法及其他通用重复开销,优先级中等;普通的单次候选项,优先级较低。即使权重较高,转义情况未知的 small-array 行仍为中等优先级,因为尚未证明可以安全地改用栈分配。Confidence 描述证据和建议重写的确定性,因此,高优先级、低置信度的行有意作为优先调查目标,而非宣称能带来运行时收益。扁平化的 Performance:* 行输出会在不同类型间保留此全局顺序。
对于不符合局部重写模式的注册表、管道或对象图构造,可选择启用聚合分配扇出:
dnx dotnet-inspect -y -- library MyLib.dll \
--triage-shape allocation-fanout \
--order-by "OncePaths desc" --top 20 --tsv
Direct Sites 是方法内的数量。Once Paths 会组合精确的程序集内调用点,并将重复调用点分别计数;条件、重复、未知、缓存和不透明路径分别列在不同的列中。请将其视为 IL 可见的正常返回路径数量,而非运行时字节数或观测到的频率。Opaque Paths 数量较高意味着虚拟调用、外部调用、委托、递归或运行时库工作仍需进一步钻取或使用分析器检查。
精确行会在结构化 JSON 中保留原生 Analysis 生成器提供的机器可读溯源信息:
Candidate、Finding(analysis.allocation、analysis.call-site 或 analysis.string-materialization)、
Provenance=exact、
Assembly、ModuleVersionId、MethodToken、Operation、Token、
EvidenceMethod 和 IL。
MethodToken 用于标识面向源代码的成员;当指令映射到单独的 MethodDef 时会提供 EvidenceMethod;对于异步源成员,它可以是生成的 MoveNext 方法体的名称,其偏移量会显示在 IL 中。精确的方法体坐标为 Assembly +(若存在则为 EvidenceMethod,否则为 MethodToken)+ IL;如果静态输入包含 ModuleVersionId,它可区分物理模块构建版本;Token 是 Operation 的操作数。使用这些字段进行运行时/静态数据关联,或将一条分诊行带入匹配的成对 diff 或 diff --history 确认工作流,无需解析 Evidence 文本:
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
--where "Finding=analysis.allocation" --where "Operation=box" --json
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
--where "Finding=analysis.call-site" --json
若要查询哪些面向源代码的方法既有匹配的性能证据,又包含一种已呈现的 C# 语法类型,请添加 Kind 谓词并省略 -S:
dnx dotnet-inspect -y -- library MyLib.dll \
--where "Kind=InvocationExpression" \
--where "Finding=analysis.call-site" \
--where "Shape=sync-call-in-async" \
--where "Confidence>=medium" --jsonl
这会输出 Body Shapes,而不是 Performance 行。类型化的性能机会会在反编译前缩小源 MethodDef 方法体的范围;需要候选项、证据和 IL 凭据时,请单独运行 Performance 查询。--top 和 --order-by 不能与 Body Shapes 组合使用;请使用 --rows 限制呈现的语法匹配项。
allocation-hotspot 等聚合行使用 Provenance=aggregate,并具有 pt~ 候选 ID,但没有精确的源 Finding、操作或令牌。复合的重复扫描判断可以在 SupportingFinding、SupportingOperation、SupportingToken、SupportingEvidenceMethod 和 SupportingIL 中单独保留支持该判断的精确局部调用。这些字段是运行时对应坐标,不是聚合行的源 Finding 或候选身份。
Provenance=unmatched 表示某条指令级行未能与预期的生产者普查结果关联。
选择直接调用方循环重复
每次调用只发生一次的分配,仍可能被上游调用方的循环重复。选择具有精确直接调用凭据的行:
dnx dotnet-inspect -y -- library MyLib.dll -S "Performance:*" \
--where "CallerLoop=direct" --json
CallerLoopDepth 和 CallerLoopWitness 标识确定性的调用点。此证据不会改变行的局部 Loop、重数、置信度、权重、候选身份或默认排名。可用它来筛选待分析的候选项,但不能据此证明调用方处于热点状态或循环确实执行。
只有已解析的调用边才符合条件。函数加载和回调注册不能证明回调已执行,递归遍历也不能证明实际深度或频率。不要将任一情况推断为调用方循环证据;应要求运行时证据或更强的产品自有调用契约。
排查异常路径上的池资源周转
选择明确的 Resource Triage 库分区,查找其精确的 def-use 路径在经过建模的清理之前到达外部输入边界的 ArrayPool<T> 获取操作:
dnx dotnet-inspect -y -- library MyLib.dll -S "Resource Triage" --jsonl
将 pool-churn-on-exception 视为分析和加固候选项,而不是永久性内存泄漏或内存损坏的指控。静态分析能证明缺乏保护的边界形态和 API 证据,但无法证明运行时频率。深入检查方法时,请使用 Candidate、Finding=analysis.resource-lifecycle、Acquire IL、Boundary IL 和 Boundary 保留精确溯源信息。每个边界占一行;多边界候选项会重复其候选项和获取字段,以便每项操作都与各自的 IL 偏移保持对应。
并非所有形态都能纯粹获益于热路径优化。除非分配发生在循环中,否则 async-state-machine 会以摊销(低置信度)类别报告:异步降级会将工作转移到状态对象中,而不是消除工作,且通常每次调用、枚举或订阅只发生一次。请将摊销行视为上下文信息,并在优化前通过性能分析器确认确实存在逐项开销。
确认分配的出现时间
关联调用方所选程序包单元格中某个方法的原生分配普查结果:
dnx dotnet-inspect -y -- diff --history --package [email protected] \
-t MyType -m HotPath \
--finding analysis.allocation --at first --at last
对于稀疏探测,可重复使用 --at;对于明确限定范围的密集遍历,可使用 --at all。这些探测可定位候选的旧版/新版边界,但不能确定分配开始出现的时间。请使用 Analysis 的原生分配 Findings 确认某个方法相邻版本对:
dnx dotnet-inspect -y -- diff --package [email protected] \
-t MyType -m HotPath \
--finding analysis.allocation
方法目标必须能在一个或两个端点解析。PairFinding.Added 确认新增了一处分配;Present、Removed 和 Changed 则分别标识错误的边界、分配消失或分配特征发生变化。该命令不会遍历版本;搜索策略和范围由调用方负责。
将可能原因追溯到一次新调用
确认分配边界后,比较同一调用方方法的直接调用统计:
dnx dotnet-inspect -y -- diff --package [email protected] \
-t MyType -m HotPath \
--finding analysis.call-site
目标方法是调用方,每一行标识一个被调用方。
PairFinding.Added 可确认新增的调用出现,例如新引入的
Enumerable.ToArray。Changed 可以显示现有调用是否移入循环,
或其分派/操作码特征是否发生变化。探查版本时使用单版本的 Calls
部分;使用这项最终的相邻版本比较来证明问题何时开始。
深入分析候选项
Call Graph 是一个有界双向图:向内追溯至入口点的调用方,并展示向外的调用,以选定成员为中心。使用 --fields 投影每个节点的成本(alloc、copy、unsafe、reflection、throw/exception、
catch/finally)。默认的 Markdown 边表最适合比较关系和成本线索。当候选项的调用路径或被调用路径很重要时,使用
--tree;使用 --mermaid 生成独立图表,或使用
--markdown --mermaid 嵌入图表。脚本需要读取相同的边行时,使用
--tsv 或 --jsonl。请求的成本线索仍作为注释显示在节点标签中;它们不会成为单独的机器可读列。
Call Graph 尚未采用 --envelope;请使用 --jsonl 或 --tsv 获取边
行。文档中的 --json 目前输出的是外围的 Type 结果,而非图边。针对精确成员的
--share url 是单独的公开 API Overview 投影,不会保留性能或 Call Graph 视图。
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph,Facts"
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --fields "Throw,Catch,Finally"
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --fields "Alloc,Loop" --tree
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll -S "Call Graph" --jsonl