研发协作
MSBuild 构建慢时用 binlog 性能分析定位瓶颈
当 MSBuild/dotnet 构建变慢、需要找出具体瓶颈(RAR、分析器、Copy、还原、串行化等)时,通过 binlog 与性能摘要定位并优化。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
MSBuild 构建慢时用 binlog通用
解决思路
- 生成 binlog:运行 `dotnet build /bl:{} -m`。
- 优先使用随插件提供的 binlog MCP 服务器(`Microsoft.AITools.BinlogMcp`,暴露在 `binlog` MCP 命名空间下)进行分析。
- MCP 不可用时的替代流程:先 `dotnet build /bl:{} -m` 生成 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`。
- 对照关键指标阈值:构建时长(小项目 <10s、中型 <60s、大型 <5min);节点利用率理想 >80%;单个目标 >50% 构建时间需调查;分析器应 <30% 的 Csc 任务时间;RAR >5s 需关注、>15s 属病态。
- 瓶颈 1(RAR 慢):减少引用数量、避免网络引用路径与过大的程序集搜索路径;对 RAR 密集分析使用 `<DesignTimeBuild>false</DesignTimeBuild>`;诊断时设 `<ResolveAssemblyReferencesSilent>true</ResolveAssemblyReferencesSilent>`;使用 `<DesignTimeBuild>` 与 `<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>`;设 `<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>` 避免拉入完整传递闭包(项目需为所用类型补直接引用);对仅构建期需要的 ProjectReference 加 `ReferenceOutputAssembly="false"`;裁剪未使用的 PackageReference。注意 RAR 即使增量构建也会无条件运行(用户可能装了 targeting pack 或 GAC 程序集),.NET Core 微程序集下引用数往往很高。
- 瓶颈 2(Roslyn 分析器与源生成器):对比有无分析器的 Csc 时长(`/p:RunAnalyzers=false`);开发环境条件禁用 `<RunAnalyzers Condition="'$(ContinuousIntegrationBuild)' != 'true'">false</RunAnalyzers>`;按配置禁用 `<RunAnalyzers Condition="'$(Configuration)' == 'Debug'">false</RunAnalyzers>`;仅代码风格 `<EnforceCodeStyleInBuild Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</EnforceCodeStyleInBuild>`;移除内循环中真正冗余的分析器;在 .editorconfig 中降低次要规则严重级别。原则:CI 中必须保留分析器强制,绝不直接删除,而是条件化配置。注意 `Directory.Packages.props` 中 `GlobalPackageReference` 添加的分析器会作用于所有项目(测试项目是否也需要同样集合);`Directory.Build.props` 中 `EnforceCodeStyleInBuild=true` 会强制每次构建做代码风格分析,应按 CI 环境条件化。
- 瓶颈 3(串行化瓶颈):性能摘要显示时间集中在单个项目、诊断日志显示其他节点空闲时,拆分大项目、优化关键路径项目、确保正确的 `BuildInParallel`。
- 瓶颈 4(Copy 任务文件 I/O 过多):使用硬链接 `<CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>`;减少 CopyToOutputDirectory 项;适当使用 `<UseCommonOutputDirectory>true</UseCommonOutputDirectory>`;设 `<SkipCopyUnchangedFiles>true</SkipCopyUnchangedFiles>`;考虑 `--artifacts-path`(.NET 8+)集中输出布局;Windows 上改用 Dev Drive(ReFS,写时复制并减少 Defender 扫描),推荐用于开发机与自托管 CI。注意 Copy 任务可能按项逐文件运行而非单批执行。
- 瓶颈 5(求值开销):降低 Directory.Build.props 复杂度;对使用显式文件列表的遗留项目设 `<EnableDefaultItems>false</EnableDefaultItems>`;尽量避免基于 NuGet 的 SDK 解析器(NuGetSdkResolver 每次项目求值增加 180-400ms,即使已还原)。
- 瓶颈 6(构建中执行 NuGet 还原):分离还原与构建,先 `dotnet restore` 再 `dotnet build --no-restore`;在 Directory.Build.props 中启用静态图求值 `<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>`(大型构建可显著省时,效果依工作负载而定)。
- 瓶颈 7(项目数量多与图形状):考虑项目合并,或使用 `/graph` 模式改善调度;宽依赖图(层少、并行分支多)比深依赖图(层多、串行)构建更快,从深改宽可显著改善清理与增量构建;查找不必要的项目依赖,拆分瓶颈项目或合并小型叶子项目。
- 快速收益清单:使用 `/maxcpucount`(或 `-m`)并行构建;分离还原与构建;启用静态图还原;为 Copy 启用硬链接;开发内循环条件禁用分析器;启用引用程序集 `<ProduceReferenceAssembly>true</ProduceReferenceAssembly>`;检查增量构建是否失效;检查 bin/obj 冲突;多项目解决方案使用 `/graph` 构建;使用 `--artifacts-path`(.NET 8+);Windows 开发机与自托管 CI 启用 Dev Drive(ReFS)。
- 报告发现时按影响分类以排优先级:🔴 高影响(优先做)——占总构建时间 >10% 的项,或单个目标 >50% 构建时间;🟡 中影响——占 2-10% 构建时间;🟢 快速收益——容易改且影响适中的项(如 Directory.Build.props 中的属性开关)。
适用边界 · 注意事项
- 尚未建立初始性能基线时(应先用 build-perf-baseline)
- 修复增量构建问题(应使用 incremental-build)或调优并行度(应使用 build-parallelism)
- 非 MSBuild 构建系统