关于 build-perf-diagnostics
这项技能通过二进制日志分析并改善 MSBuild 性能,面向处理构建缓慢问题的开发者。它结合 binlog MCP 查询与日志回放诊断,并提供常见瓶颈的分析指引。
主要功能
- 01生成并检查构建 binlog
- 02分析目标和任务耗时
- 03检查节点利用率与分析器
- 04识别常见构建瓶颈
- 05按影响程度排列发现
适用场景
- 01找出 .NET 构建中的慢速目标
- 02调查并行构建利用率偏低
- 03评估分析器或复制任务的开销
- 04安排构建性能改进的优先级
MCP 相关
使用 `binlog` 命名空间中的捆绑 `Microsoft.AITools.BinlogMcp` 服务进行性能分析。MCP 不可用时,文档提供将 binlog 回放为诊断文本日志的方法。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dotnet/ skills --skill build-perf-diagnostics安装后在 Claude Code 中输入/build-perf-diagnostics确认已加载。
详细介绍
性能分析方法
- 生成 binlog:
dotnet build /bl:{} -m - 使用此插件捆绑的 binlog MCP 服务(
Microsoft.AITools.BinlogMcp,通过binlogMCP 命名空间公开)
如果服务无法连接,并且需要修复启动问题,请使用 troubleshoot-binlog-mcp。如果修复受阻、被拒绝或没有必要,请使用下面的回放流程。
MCP 不可用时的替代流程:将 binlog 回放为文本日志
- 生成 binlog:
dotnet build /bl:{} -m - 回放为包含性能摘要的诊断日志:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary" - 查看性能摘要(位于
full.log末尾):grep "Target Performance Summary\|Task Performance Summary" -A 50 full.log - 找出耗时较长的目标和任务:PerformanceSummary 部分会按累计耗时排序,列出所有目标和任务
- 检查节点利用率:搜索调度和节点消息
grep -i "node.*assigned\|building with\|scheduler" full.log | head -30 - 检查分析器:搜索分析器计时信息
grep -i "analyzer.*elapsed\|Total analyzer execution time\|CompilerAnalyzerDriver" full.log
关键指标和阈值
- 构建时长:什么算“正常”——小型项目 <10s,中型项目 <60s,大型项目 <5min
- 节点利用率:理想情况下,各节点的活动时间占比应 >80%。利用率低 = 序列化瓶颈
- 单个目标占主导:如果某个目标占构建时间的 >50%,请进行调查
- 分析器耗时与编译耗时:分析器耗时应 <Csc 任务耗时的 30%。如果更高,请考虑移除耗时较长的分析器
- RAR 耗时:ResolveAssemblyReference 耗时 >5s 值得关注。>15s 则属于异常
常见瓶颈
1. ResolveAssemblyReference(RAR)缓慢
- 症状:每个项目的 RAR 耗时 >5s
- 根本原因:程序集引用过多、引用路径基于网络、程序集搜索路径过长
- 修复方法:减少引用数量;对 RAR 开销大的分析使用
<DesignTimeBuild>false</DesignTimeBuild>;对诊断设置<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent> - 进阶:
<DesignTimeBuild>和<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch> - 关键见解:即使在增量构建中,RAR 也会无条件运行,因为用户可能安装了目标框架包或将程序集添加到了 GAC(参见 dotnet/msbuild#2015)。使用 .NET Core 微型程序集时,引用数量通常非常多。
- 减少传递引用:设置
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>,避免引入完整的传递闭包(注意:项目可能需要为所用的类型添加直接引用)。对于仅在构建时需要(不属于 API 表面)的 ProjectReferences,使用ReferenceOutputAssembly="false"。移除未使用的 PackageReferences。
2. Roslyn 分析器和源生成器
- 症状:相对于文件数量,Csc 任务耗时远超预期(>干净编译时间的 2 倍)
- 诊断:查看回放日志中的 Task Performance Summary,了解 Csc 任务耗时;搜索分析器计时信息;比较启用和禁用分析器时的 Csc 耗时(
/p:RunAnalyzers=false) - 修复方法:
- 有条件地在开发过程中禁用:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers> - 按配置设置:
<RunAnalyzers Condition="'$(Configuration)' == 'Debug'">false</RunAnalyzers> - 仅启用代码风格分析:
<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild> - 从日常开发循环中移除确实多余的分析器
- 在 .editorconfig 中设置严重性,以降低不太重要规则的级别
- 有条件地在开发过程中禁用:
- 核心原则:在 CI 中保留分析器检查。切勿直接“移除”分析器,而应通过条件进行配置。
- GlobalPackageReference:通过
Directory.Packages.props中的GlobalPackageReference添加的分析器会应用于所有项目。请考虑测试项目是否需要与生产代码相同的分析器集。 - EnforceCodeStyleInBuild:在
Directory.Build.props中设为true时,会在每次构建时强制执行代码风格分析。应根据 CI 环境(ContinuousIntegrationBuild)进行条件设置,以避免拖慢开发迭代。
3. 串行化瓶颈(单线程目标)
- 症状:性能摘要显示,大部分构建时间集中在单个项目;诊断日志显示某个节点正在工作,而其他节点处于空闲状态
- 常见原因:目标未正确声明依赖项,单个项目位于关键路径上
- 解决方法:拆分大型项目,优化关键路径上的项目,确保正确设置
BuildInParallel
4. 过多文件 I/O(复制任务)
- 症状:复制任务的累计耗时较高
- 根本原因:复制数千个文件、跨网络驱动器复制、复制任务意外地按每个项目运行一次(逐文件),而非作为单个批次运行(见 dotnet/msbuild#12884)
- 解决方法:使用硬链接(
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>),减少 CopyToOutputDirectory 项,适当时使用<UseCommonOutputDirectory>true</UseCommonOutputDirectory>,设置<SkipCopyUnchangedFiles>true</SkipCopyUnchangedFiles>,考虑使用--artifacts-path(.NET 8+)集中管理输出 - Dev Drive:在 Windows 上,切换到 Dev Drive(使用写时复制、减少 Defender 扫描的 ReFS)可显著降低复制密集型构建的文件 I/O 开销。建议开发计算机和可自托管的 CI Agent 都使用 Dev Drive。
5. 求值开销
- 症状:在任何编译开始前,构建启动缓慢
- 根本原因:Directory.Build.props 过于复杂、通配符 glob 扫描大型目录、NuGetSdkResolver 开销(即使已还原,每次项目求值也会增加 180-400ms,见 dotnet/msbuild#4025)
- 解决方法:降低 Directory.Build.props 的复杂度;对于使用显式文件列表的旧项目,使用
<EnableDefaultItems>false</EnableDefaultItems>;尽可能避免使用基于 NuGet 的 SDK 解析器 - 另见:
eval-performance技能,了解详细指南
6. 构建期间还原 NuGet
- 症状:即使没有必要,每次构建都会运行还原
- 解决方法:
- 将还原与构建分开:先运行
dotnet restore,再运行dotnet build --no-restore - 启用静态图求值:在 Directory.Build.props 中设置
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>— 在大型构建中可显著节省时间(效果取决于工作负载)
- 将还原与构建分开:先运行
- 另见:当多个管道或脚本步骤各自执行还原时,或在添加
--no-restore之前,使用eliminate-redundant-restores技能(如果没有更早的还原步骤覆盖当前步骤,它会因 NETSDK1004/NETSDK1047 而失败)
7. 项目数量过多和依赖图形状
- 症状:有许多小型项目,每个项目耗时都很短,但开销累积起来;深层依赖链会导致构建串行化
- 可考虑:合并项目,或使用
/graph模式改进调度 - 依赖图形状很重要:宽依赖图(层级少、并行分支多)比深依赖图(层级多、串行化)构建更快。将深层结构重构为宽层结构,可显著缩短全新构建和增量构建时间。
- 操作建议:检查是否存在不必要的项目依赖,考虑将瓶颈项目拆分为两个项目,或合并小型叶子项目
使用 Binlog 重放进行性能分析
使用文本日志重放的分步工作流:
- 重放并生成性能摘要:
dotnet msbuild build.binlog -noconlog -fl "-flp:v=diag;logfile=full.log;performancesummary" - 查看目标/任务性能摘要(位于
full.log末尾):
这会按累计耗时排序,显示所有目标和任务 — 相当于找出耗时较长的目标/任务。grep "Target Performance Summary\|Task Performance Summary" -A 50 full.log - 查找各项目的构建耗时:
grep "done building project\|Project Performance Summary" full.log - 检查并行度(多节点调度):
grep -i "node.*assigned\|RequiresLeadingNewline\|Building with" full.log | head -30 - 检查分析器开销:
grep -i "Total analyzer execution time\|analyzer.*elapsed\|CompilerAnalyzerDriver" full.log - 深入分析特定的慢速目标:
grep 'Target "CoreCompile"\|Target "ResolveAssemblyReferences"' full.log
快速见效清单
- 使用
/maxcpucount(或-m)进行并行构建 - 将还原与构建分开(先运行
dotnet restore,再运行dotnet build --no-restore) - 启用静态图还原(
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>) - 为 Copy 启用硬链接(
<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>) - 在开发内循环中按条件禁用分析器:
<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers> - 启用引用程序集(
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>) - 检查增量构建是否存在问题(参见
incremental-build技能) - 检查 bin/obj 是否冲突(参见
check-bin-obj-clash技能) - 对多项目解决方案使用图构建(
/graph) - 使用
--artifacts-path(.NET 8+)集中管理输出目录结构 - 在 Windows 开发计算机和可自托管的 CI 环境中启用 Dev Drive(ReFS)
影响程度分类
报告发现的问题时,请按影响程度分类,以便确定修复优先级:
- 🔴 高影响(优先处理):耗时超过总构建时间 10% 的项目,或单个目标耗时超过构建时间 50%
- 🟡 中等影响:耗时占构建时间 2%~10% 的项目
- 🟢 快速见效:改动简单且影响适中(例如在 Directory.Build.props 中设置属性标志)
评论
支持 Markdown