首页
/ Deno 基准测试体系:deno_bench 基准套件的运行、过滤与结果产出全解析

Deno 基准测试体系:deno_bench 基准套件的运行、过滤与结果产出全解析

2026-09-06 11:58:00作者:乔或婵

本文围绕 Deno 仓库 tests/bench/README.md 所讲的两个核心点——如何用 cargo bench --bench deno_bench -- <名称> 对基准测试进行过滤运行,以及基准结果如何被产出与可视化——展开,并结合 tests/bench/main.rs 的完整实现,说明各指标(执行时间、内存峰值、二进制体积、系统调用数等)的采集方式与结果 JSON 的聚合流程。读完后你可以独立跑通 Deno 的官方基准套件、理解每个过滤参数对应哪一组测量,并知道 target/bench.json 中各字段的真实来源。

1. 基准套件入口:deno_bench 目标与过滤命令

tests/bench/README.md 给出的第一条实战命令就是基准过滤的入口:

cargo bench --bench deno_bench -- bundle

其中 -- 之后的参数(README 中示例为 bundle)是基准名称过滤器。这个 deno_bench 基准目标在 tests/bench/Cargo.toml 中声明:

[package]
name = "bench_tests"
autobenches = false

[[bench]]
name = "deno_bench"
harness = false
path = "main.rs"

[[bench]]
name = "lsp_bench_standalone"
harness = false
path = "lsp_bench_standalone.rs"

几个关键点:

  • harness = false 表示该目标不依赖 libtest 默认测试框架,main.rs 是完全自托管的执行器;
  • 该包被列入了根 Cargo.toml 的 workspace 成员("tests/bench""tests/bench_util"),且根工作区以 deno_bench_util = { version = "0.252.0", path = "./tests/bench_util" } 的形式导出了基准工具库;
  • deno_bench 外还有一个独立的 lsp_bench_standalone 目标,专门用于 LSP 基准的独立运行。

tests/bench/main.rs#L363-L384 中的过滤逻辑与 README 的命令一一对应:程序启动时读取第一个位置参数,若它是某个基准名则 benchmarks.retain(|s| s == &filter) 只保留该组;程序同时要求命令行中出现 --bench 开关(cargo 在基准模式下传给自定义 harness 的约定参数),否则直接退出,避免误当作普通二进制执行。

需要说明的一点是:README 中的 bundle 是历史基准名(对应早期对 deno bundle 子命令的计时)。从当前源码的基准清单看,现存的过滤值已变为下面第 2 节列出的六组,README 示例更多是演示“如何传过滤参数”这一机制本身。

2. 可用的过滤参数:六组基准

tests/bench/main.rs#L363-L370 中定义了当前支持的基准组清单:

过滤参数 测量对象 实现位置
exec_time 典型命令(deno run 等)的执行耗时分布 main.rs#L151-L218
binary_size deno 可执行文件、swc/v8 rlib、快照文件体积 main.rs#L247-L293
cargo_deps Cargo.lock 中的依赖包总数 main.rs#L321-L334
lsp LSP 请求回放(补全、悬停、编辑)耗时 lsp.rs
strace 系统调用总数、线程数(仅 Linux) main.rs#L432-L476
mem_usage 峰值内存(time -v 采样) main.rs#L295-L319

例如只跑 LSP 一组:cargo bench --bench deno_bench -- lsp;不带过滤参数则六组全部执行。

3. exec_time:hyperfine 驱动的执行时间基准

这是套件中最核心的一组。tests/bench/main.rs#L34-L147EXEC_TIME_BENCHMARKS 常量表声明了全部被测命令,每一项是 (名称, deno 参数, 期望退出码) 三元组,覆盖的场景包括:

  • 冷启动:cold_hellocold_relative_import(带 --reload 强制重新加载模块图),以及对应热路径的 hellorelative_import
  • 错误路径:error_001,并断言进程退出码为 1;
  • Worker 相关:workers_startupworkers_round_robinworkers_large_message
  • 内置功能热点:text_decodertext_encodertext_encoder_intoresponse_string

执行细节(main.rs#L151-L218):

  • 使用预编译好的 hyperfine 工具(test_util::prebuilt_tool_path("hyperfine"))一次性跑完所有命令,参数为 --export-json hyperfine_results.json --warmup 3,即每组命令 3 次预热;
  • 源码注释明确要求 cold_* 用例必须排在热用例之前——因为冷启动基准顺带完成了模块缓存的填充,先执行才能保证后续热用例读到的缓存状态正确;
  • error_001 这类带期望退出码的用例,命令尾部会追加 ; test $? -eq 1 的 shell 断言,防止“命令跑挂了但耗时很短”污染数据;
  • 从 hyperfine 的 JSON 输出中只保留 RESULT_KEYS = ["mean", "stddev", "user", "system", "min", "max"] 六个统计量,按基准名归入结果字典。

4. mem_usage、strace、binary_size 与 cargo_deps

内存峰值(mem_usagerun_max_mem_benchmark 对每个 EXEC_TIME_BENCHMARKS 用例执行 time -v <deno> <args>,解析 stderr 中的最大驻留内存。这意味着该组指标依赖 Linux 的 time 命令,非 Linux 环境需自行保证可用。

系统调用与线程数(strace:仅在 cfg!(target_os = "linux") 下启用。以 strace -c -f -o <临时文件> 包裹每条被测命令,并强制 LC_NUMERIC=C 保证数字格式可解析;线程数取 clone/clone3 调用数加 1,系统调用总数取 totalmain.rs#L432-L476)。

二进制体积(binary_sizeget_binary_sizes 汇总四类体积——deno 可执行文件本身、target/release/deps 下所有 libswc*libv8* rlib 的累计大小,以及三个编译期快照文件 CLI_SNAPSHOT.binRUNTIME_SNAPSHOT.binCOMPILER_SNAPSHOT.bin。由于 cargo 的 OUT_DIR 不可预测,快照文件通过 walkdir 全树搜索取得,且同名文件以 mtime 最新者为准。

依赖数量(cargo_deps:统计根目录 Cargo.lock[[package]] 段的行数,作为依赖膨胀的长期趋势指标,并带 count > 10 的健全性断言(main.rs#L321-L334)。

5. LSP 基准:真实请求回放

LSP 基准由 tests/bench/lsp.rs 实现,思路是“录制回放”:用 include_bytes! 把录制的 JSON-RPC 请求序列(如 testdata/db_messages.jsontestdata/deco_apps_requests.json)编译进二进制,运行时通过 tower-lspLspClientBuilder 启动 deno lsp,回放悬停、补全、高亮、代码编辑等请求并计时,例如 bench_deco_apps_edits 会回放一个完整应用仓库的编辑请求流。结果以 lsp_exec_time 计入总输出。该模块同时可经由独立目标 lsp_bench_standalonetests/bench/lsp_bench_standalone.rs)单独执行。

6. 同目录下的 JS 侧基准脚本

tests/bench/ 目录下还有一批使用 Deno.bench API 编写的 JavaScript 基准,用于测量 JS 侧热点,例如:

  • tests/bench/deno_common.jsDate.now()performance.now()(注释标注为“非常轻量的 op,理应高度可优化”)、new URL(...) 解析、atob/btoa Base64 往返、open_file_sync、从 /dev/zero 读字节等,多数用例显式指定迭代次数(如 { n: 5e5 });
  • tests/bench/command.js:以 Deno.Command 启动外部进程(echocat 128kb)的开销基准;
  • 其余 streams.jstcp.jssqlite.jswebstorage.jsurl_parse.js 等文件则覆盖对应运行时 API。

这些脚本与 main.rs 的进程级基准互补:前者测量 Deno API 的函数级吞吐,后者测量整条 CLI 链路的端到端表现。

7. 结果产出:target/bench.json 与数据管道

所有指标最终汇聚为 main.rs#L337-L357 中的 BenchResult 结构并序列化为 JSON,字段与来源的对应关系如下:

字段 来源
created_at / sha1 当前 UTC 时间戳与 git rev-parse HEAD,用于按提交对齐历史数据
benchmark exec_time 的六项统计量(源码 TODO 注释说明其历史名称应为 exec_time
binary_size 第 4 节的四类体积
cargo_deps 依赖包数量
lsp_exec_time LSP 请求回放耗时
max_memory time -v 解析的峰值内存
syscall_count / thread_count strace 统计

该 JSON 被写入 target/bench.jsonmain.rs#L483-L486)。后续的聚合由 tools/build_benchmark_jsons.js 完成:它以构建目录下的 bench.json 为输入,为基准数据仓库生成按基准维度拆分的 JSON 序列。README 中“benchmark plots”一节即指向这一数据管道的下游——基准趋势图由外部托管的仪表盘渲染(README 给出的两个地址为外部站点,本文按规范不输出该链接)。源码中的 TODO 注释也表明,历史上数据曾提交至独立的 benchmark_data 仓库,该流程已标记为弃用。

8. 实践要点与前提条件

结合源码,实际跑这套基准时值得注意:

  1. 指定被测二进制main() 优先读取环境变量 DENO_BENCH_EXE,缺省使用 test_util::deno_exe_path()(即本树构建出的 deno 可执行文件,main.rs#L388-L393),因此测量的是当前工作区代码而非安装的发布版;
  2. 执行目录:程序会 set_current_dir 到仓库根,用例中出现的 tests/testdata/... 相对路径依赖这一点;
  3. 过滤语义-- 后参数等于 --bench 被视为模式开关而非过滤器;传入未知名称会使基准列表清空,但仍会走完整流程写出空结果,建议过滤前先对照第 2 节的清单;
  4. 平台差异strace 组仅在 Linux 生效,mem_usage 依赖 GNU timehyperfine 通过 test_util 的预构建工具路径获取,需构建流程已准备好该工具;
  5. 结果解读:exec_time 采用 mean/stddev/user/system/min/max 六元组,跨提交对比时建议以 mean 为主、stddev 为噪声参考;sha1 字段可用于把一次运行精确对齐到源码提交。

9. 小结

tests/bench/README.md 虽然简短,但点出了 Deno 基准体系的两条主线:cargo bench --bench deno_bench -- <组名> 的过滤式运行,以及结果向外部基准图谱的持续上报。深入源码后可以确认,这套体系由 tests/bench/Cargo.toml 声明的 deno_bench/lsp_bench_standalone 目标驱动,tests/bench/main.rs 以 hyperfine、strace、time -v 等系统工具组合出执行时间、内存、系统调用、二进制体积、依赖数五类进程级指标,tests/bench/lsp.rs 提供 LSP 请求回放,同目录的 Deno.bench 脚本补充 JS 侧函数级基准;最终所有指标连同提交号写入 target/bench.json,经 tools/build_benchmark_jsons.js 拆分为时序数据供趋势分析使用。理解这条从“过滤命令”到“趋势图”的完整链路,是复现和解读 Deno 官方性能数据的起点。

登录后查看全文
热门项目推荐
相关项目推荐