Nx CLI 基准测试实战:用 1110 项目合成工作区度量任务流水线性能
2026/9/10 22:01:47 网站建设 项目流程

Nx CLI 基准测试实战:用 1110 项目合成工作区度量任务流水线性能

【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx

Nx 官方仓库在benchmarks/目录下内置了一套自研基准测试(Benchmark)工作区,它用 1110 个轻量项目模拟超大规模 Monorepo,配合 hyperfine 度量 Nx CLI 的启动、项目图构建、任务调度与缓存命中等多个维度的耗时,并通过goals.json目标值与本地baseline.json基线形成可量化的性能回归防线。读完本文,你将掌握这套基准测试的设计思路、五个bench:*目标的含义、目标/基线对比机制的实现细节,以及如何在 CI 中用它守护 Nx 的性能底线。

一、基准测试工作区设计:三层扇出结构

整套基准测试以一个**合成工作区(Synthetic Workspace)**为载体,其结构与真实大型 Monorepo 对齐:

  • 1110 个项目,以三层扇出(3-level fan-out)排布:10 groups × 10 subs × 10 leaves
  • 每个项目都定义buildcopycat三个 target;
  • 所有 target 都操作同一个共享文件 lorem.md(标准 Lorem Ipsum 文本),不做真实编译,只保留足够的文件 I/O 来考验 Nx 的任务流水线(task pipeline)。

从仓库中的实际项目文件可以清晰地看到这棵依赖树(见 benchmarks/packages/group-01、benchmarks/packages/group-01/sub-01):

  • 组节点 group-01/project.json 只声明name与三个空 target;
  • 子节点 sub-01/project.json 通过implicitDependencies: ["group-01"]隐含依赖组节点;
  • 叶子节点 leaf-01/project.json 同样通过implicitDependencies: ["group-01-sub-01"]形成三层依赖链。

叶子节点的真实名称如group-01-sub-01-leaf-01,1110 个项目由此生成,为run-many -t ...提供了足够的并发规模压力。

二、Workspace Targets:三个精心设计的测量探针

README 用一张表概括了三个 target 的行为(详见 benchmarks/README.md):

Target命令缓存输出依赖
buildcp lorem.md → dist/output.md^build
copycp lorem.md → copy-out/output.md
catcat lorem.md

它们的底层实现位于 benchmarks/nx.json 的targetDefaults中,关键配置如下:

{ "targetDefaults": { "build": { "command": "mkdir -p {projectRoot}/dist && cp lorem.md {projectRoot}/dist/output.md", "dependsOn": ["^build"], "cache": true, "inputs": [ "{projectRoot}/**/*", "{workspaceRoot}/lorem.md", { "dependentTasksOutputFiles": "**/*" } ], "outputs": ["{projectRoot}/dist"] }, "cat": { "command": "cat lorem.md", "cache": true, "inputs": ["{projectRoot}/**/*", "{workspaceRoot}/lorem.md"] }, "copy": { "command": "mkdir -p {projectRoot}/copy-out && cp lorem.md {projectRoot}/copy-out/output.md", "cache": true, "inputs": ["{projectRoot}/**/*", "{workspaceRoot}/lorem.md"], "outputs": ["{projectRoot}/copy-out"] } }, "analytics": false, "parallel": 5 }

从中可以读出三层测量意图:

  • build 带拓扑依赖dependsOn: ["^build"]要求子项目先于父项目构建,且dependentTasksOutputFiles: "**/*"把依赖任务的产物纳入输入哈希,专门用来考验 Nx 的拓扑排序 + 跨任务缓存联动能力(README 注明该场景当前在基准列表中处于 disabled 状态,即build-warm);
  • copy 带输出追踪:有outputs声明,可完整走一遍"缓存命中 → 恢复输出产物"的路径;
  • cat 无输出产物cat只有 stdout,无输出目录,用来测量纯调度 + 哈希计算的最小开销。

此外parallel: 5控制了run-many的并行度,整个工作区关闭了 analytics,避免遥测干扰测量数据。

三、环境准备与快速开始

前置依赖

基准测试依赖 hyperfine 作为计时工具,安装方式:

cargo install hyperfine

同时需要 pnpm(仓库根目录使用 pnpm-workspace.yaml 与 pnpm-lock.yaml 管理依赖),因为所有命令都通过 pnpm 触发。

一键运行全部基准

# 运行所有基准,并与 goals + baseline 对比 pnpm nx run benchmarks

单独运行某个基准

pnpm nx bench:version benchmarks pnpm nx bench:show-projects benchmarks pnpm nx bench:cat-warm benchmarks pnpm nx bench:copy-warm benchmarks

值得注意:每个bench:*target 都dependsOn: ^build(见 benchmarks/package.json 的nx.targets配置),这意味着执行基准前会先编译 Nx 各包——因为基准命令实际调用的是node ../packages/nx/dist/bin/nx.js,必须保证产物存在。所有bench:*还都设置了"parallelism": false,确保基准按序串行执行,互不抢占资源。

四、五个基准测试详解

基准实际执行的命令度量维度
versionnx --versionCLI 启动与模块加载耗时
show-projectsnx show projects通过 daemon 构建项目图
cat-warmrun-many -t cat×1110无输出产物场景下的任务调度 + 哈希
copy-warmrun-many -t copy×1110带输出追踪的缓存任务执行
build-warmrun-many -t build×1110带拓扑依赖的缓存任务(当前禁用)

它们在 benchmarks/package.json 的scripts中都有完整定义,例如:

"bench:version": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 10 --export-json results-version.json 'node ../packages/nx/dist/bin/nx.js --version'", "bench:show-projects": "NX_NO_CLOUD=true hyperfine --show-output --setup 'node ../packages/nx/dist/bin/nx.js reset' --warmup 1 --min-runs 5 --export-json results-show-projects.json 'node ../packages/nx/dist/bin/nx.js show projects --tui=false'"

所有基准遵循相同的测量纪律,README 中明确说明:

  • 统一设置NX_NO_CLOUD=true彻底隔离 Nx Cloud 网络层,只测本地性能;
  • 每次迭代前通过--setup执行nx reset清空 daemon 与缓存状态,保证每次测量都从冷启动开始;
  • 每个基准至少采集5 次version10 次)取统计值;
  • version之外都带 1 次--warmup预热;
  • 结果以 hyperfine 的--export-json形式写入results-<name>.json文件。

从命令细节还能看出不同基准的差异:show-projects使用--tui=false关闭交互界面,而build-warm使用--tui=true --tui-auto-exit主动开启 TUI 并自动退出,用以覆盖 TUI 渲染路径下的真实耗时。

五、目标与基线:性能回归的双保险

性能数据由两个文件共同承载:

文件是否提交作用
goals.json提交(committed)团队约定的目标耗时,CI 中基准超时即失败
baseline.jsongitignore(本地生成)本地机器的个人对比基线

goals.json的当前目标值(单位为秒,max字段):

{ "version": { "max": 0.05 }, "show-projects": { "max": 0.1 }, "cat-warm": { "max": 0.3 }, "copy-warm": { "max": 0.5 }, "build-warm": { "max": 0.75 } }

即:CLI 启动 50ms 内、项目图构建 100ms 内、1110 个无输出任务 300ms 内、带输出追踪的缓存任务 500ms 内、带拓扑依赖的缓存任务 750ms 内。这些数字直接构成 CI 的硬性回归门槛。

设置本地基线

# 首次运行会自动创建 baseline.json pnpm nx run benchmarks # 显式用本次结果覆盖基线 pnpm nx run benchmarks -- --set-baseline

--set-baseline参数由 run-benchmarks.ts 解析(process.argv.includes('--set-baseline')),它会将本次五个基准的mean均值写入baseline.json。基线文件是本地专属、不入库的,因此换机器后首次运行会自动重新生成,这保证了团队内每台开发机都有与自己硬件匹配的参照系。

六、执行流程与对比脚本原理

README 把整个流程归纳为三步:

  1. 每个bench:*脚本调用 hyperfine,生成对应的results-<name>.json
  2. runtarget 通过dependsOn依赖全部五个bench:*(且它们都parallelism: false),按序执行;
  3. 全部完成后,run-benchmarks.ts读取结果文件并打印对比表格。

runtarget 的依赖顺序在 benchmarks/package.json 中为:

"run": { "dependsOn": [ "bench:version", "bench:show-projects", "bench:cat-warm", "bench:copy-warm", "bench:build-warm" ] }

对比脚本实现细节

run-benchmarks.ts 是整套机制的"大脑",值得关注的技术点:

  • 结果读取loadResult()读取results-<name>.json,直接取 hyperfine JSON 中的raw.results[0](包含meanstddevminmax);
  • 时间格式化formatMs()把秒转毫秒,≥1s 显示为x.xxs,否则显示为xxxms
  • 增量计算formatDelta()输出相对百分比(如+12%);
  • 彩色对比表:表头为Benchmark | Goal | Baseline | Current,每个基准行以(goalDelta | baselineDelta)形式展示双重增量——
    • 相对目标:mean <= goal.max显示绿色,超标显示红色;
    • 相对基线:比基线快 10% 以上(ratio < -0.1)显示绿色,慢 10% 以上(ratio > 0.1)显示红色,其余为灰色,避免微小波动引起噪音;
  • 健壮性:某个results-<name>.json缺失或损坏时,loadResult返回null并跳过该行;若一个结果都没有,脚本以错误码退出并提示先运行单个基准。

对比表同时回答两个问题:"本次是否突破团队目标?"(对照 goals)和"相比我本机上次表现如何?"(对照 baseline)。

七、CI 集成:目标即门槛

README 明确指出基准测试运行在 CI 的affected target 流水线中:

nx affected --targets=...bench

goals.json中的目标值充当回归闸门(regression gate):任何一次改动导致对应基准的平均耗时超过goals.json中的max值时,CI 即失败。这也是为什么goals.json必须提交入库——它是团队集体认可的性能契约。

另外,run-benchmarks.ts 对 CI 环境做了专门适配:当process.env.CI存在时,不会打印"To update baseline"之类的交互提示,保证 CI 输出干净、可解析;而在本地运行时,脚本会在表格末尾温和提示pnpm bench -- --set-baseline命令(注意该提示是脚本内置输出,实际更新基线仍应使用 README 中的pnpm nx run benchmarks -- --set-baseline)。

八、扩展阅读

如果想进一步深入这套基准设施,建议按以下顺序阅读仓库中的关键文件:

  • benchmarks/README.md — 基准测试的总纲与使用入口;
  • benchmarks/package.json — 五个基准的 hyperfine 命令与 Nx target 编排;
  • benchmarks/nx.json —build/copy/cat三个 target 的targetDefaults与输入/输出/缓存声明;
  • benchmarks/run-benchmarks.ts — goals/baseline 对比与彩色表格的完整实现;
  • benchmarks/goals.json — 当前性能目标值;
  • benchmarks/lorem.md — 所有任务共享的操作文件;
  • benchmarks/packages/group-01 与 benchmarks/packages/group-01/sub-01/leaf-01/project.json — 三层扇出项目结构及隐含依赖的样板;
  • packages/nx — 被基准的 Nx 核心包源码(基准命令直接调用其dist产物)。

这套工作区把"可复现的性能实验"和"可持续的回归守护"结合到了一起:hyperfine保证统计严谨,nx reset+NX_NO_CLOUD=true保证环境纯净,goals.json+baseline.json双轨对比保证结论可判断,而 CI 接入则让性能退化在合并前就被拦截。

【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询