Ripple 基准测试基线报告:16 个操作级对比套件、统计方法与回归护栏解读
【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/ripple
benchmarks/results/baseline-report.md是 Ripple 在性能优化工作开始之前,基于 3 次独立 normal 运行生成的操作级基线报告:它按「套件 / 操作」粒度记录 Ripple 相对于 Octane TSRX、Octane JSX、Solid、Vue Vapor 等参照框架的配对比值,并列出值得后续优化的最大时间差距。读完本文,你能掌握该报告中 score、p95、Max RME、Δ 记号等每列统计含义的来源,以及如何用 report.mjs 自行重新生成、用 compare.mjs 做变更前后离线对比的完整方法。
报告定位:优化前的观测基线,不是"谁更快"的结论
报告开头的元信息明确了它的来源与定位(见 baseline-report.md):
- 数据来自3 次独立的 normal(非
--quick)运行,硬件为 Intel Core i9-9980HK @ 2.40GHz,平台 darwin/x64,Node v24.18.0; - 附带两个指纹:Workload SHA-256
57f55895502acec9d5c48a9b6defe353a316bd6023feec24a5e1d3d62329d394与 Lockfile SHA-256d5ccf4ee34c36a86ca23851dbf4fb8c938a4919fdac9c61ad7a0faf4d5ed4a7d; - 原文明确声明:"These are baseline observations before Ripple optimization"(这是 Ripple 优化之前的基线观测),比值低于 1 才表示该操作上有利于 Ripple,且这些数字"不是计时优势的证据"。
三次运行的完整结果分别保存在 baseline-1、baseline-2、baseline-3 三个目录中,每个目录包含各套件的<suite>.json与summary.json;机器可读的合并数据则写在 baseline-report.json 中,其directories字段指向上述三个目录。
已验证的环境版本
报告内嵌了一份"Verified environment"清单,即生成基线时锁定的全部框架与工具链版本。这份清单本身也来自元数据中的packageVersions字段(可对照 baseline-report.json 的 metadata):
{ "ripple": "0.3.126", "@ripple-ts/vite-plugin": "0.3.126", "@tsrx/ripple": "0.1.63", "octane": "0.2.4", "solid-js": "2.0.0-rc.3", "@solidjs/web": "2.0.0-rc.3", "vite-plugin-solid": "3.0.0-next.5", "vue": "3.6.0-rc.1", "@vue/runtime-vapor": "3.6.0-rc.1", "react": "19.2.7", "react-dom": "19.2.7", "preact": "10.29.8", "svelte": "5.56.7", "inferno": "9.1.0", "vite": "8.1.5", "babel-plugin-react-compiler": "1.0.0" }几个值得注意的前提:
- 浏览器测量使用 Playwright 1.63.0 固定的 headless Chromium 153.0.8010.12(Chrome for Testing),浏览器二进制由 Playwright 依赖钉死;SSR 计时在 Node 中执行,bundle-size 套件额外在 Chromium 中运行构建产物做验证(见 benchmarks/README.md);
- React 一侧同时固定了
babel-plugin-react-compiler1.0.0,即 React 对照是带 React Compiler 的构建; - 该基线录制于Octane 0.2.4版本之上。benchmarks/README.md 说明此后 fixture 依赖已更新到 Octane 0.2.6,但原始基线录制与护栏被保留为 0.2.4 的历史参考,未重新录制;因此本报告中的 Octane TSRX/JSX 列对应的是 0.2.4 工作负载。
统计口径:score、p95、Max RME、Samples 各是什么意思
报告表格的列定义为:
| 列 | 含义 |
|---|---|
| Ripple score [run range] | 3 次运行中每次运行"headline score"的中位数;方括号内是 3 次运行的最小/最大值,即运行间波动 |
| p95 | 3 次运行各自 p95 值的中位数 |
| Max RME | 3 次运行中最大的相对误差边界(per-run 诊断量) |
| Samples | 3 次运行的样本数之和 |
| Octane TSRX / Octane JSX / Solid / Vue Vapor | Ripple 分数除以该参照框架分数的配对比值,同一进程内同机测量 |
| Best matching competitor | 同一操作下所有竞争方(含 preact、svelte、inferno、react、octane-jsx)中分数最低者及其分数 |
比值列有两条特殊记号规则,由 report.mjs 的 comparison 函数 实现:
- 低于 1 有利于 Ripple(延迟、字节、计数的方向都是越小越好);
- 当计时值低于 0.01 ms 或参照分数为零时,比值失去意义,改用绝对差值
Δ表示(例如Δ -0.003); - N/A表示该操作没有对应的竞争方 fixture(能力缺口保持显式,而不是静默省略)。
headline score 本身的计算在 lib/stats.mjs 中:UI 工作负载属于延迟基准而非紧凑的 ops/sec 循环,因此score取的是"后段稳定窗口"的均值——先在全部样本上滑动一个窗口(默认约为总样本的 40%),找到均值最低的窗口位置附近(允许 8% 的 warm 容差),再对该窗口取均值,以此保留 JIT 预热痕迹的可见性;样本不足 5 个时回退到中位数策略。p95、min、mean等则基于全部分布样本计算,RME 使用 95% 置信度的 t 分布临界值(stats.mjs 的 T_CRITICAL_95 表):RME = (SEM × t / mean) × 100%。这也解释了为什么短耗时操作的 Max RME 经常很高(如recursive-context / partial_unmount的 236.5%)——分母极小时置信区间天然宽,所以报告强调 RME"只是逐次运行的诊断量"。
如何生成这份报告(以及它的硬性校验)
基线报告的生成命令在 benchmarks/README.md 中给出:
node benchmarks/report.mjs benchmarks/results/baseline-1 benchmarks/results/baseline-2 benchmarks/results/baseline-3report.mjs 在合并数据前执行严格的前置校验,任何一项不满足直接抛错退出:
- 至少提供 3 个结果目录;
- 任一目录的
summary.metadata.quick为 true 即拒绝(--quick冒烟结果不能建立 normal 基线); - 任一套件
status !== 'PASS'即拒绝(失败或缺失的测量无法产出成功基线); - 三个目录的
cpu、platform、arch、node、lockfileSha256、workloadSha256、rippleSourceSha256必须完全一致,否则报Incompatible baseline metadata。
合并逻辑本身也很克制(report.mjs):Ripple 与各竞争方的 score 都取 3 次运行的中位数,"Best matching competitor"按中位数分数升序排序取第一个;缺失任一操作或竞争方数据都会抛错,而不是填 0。"Largest timing gaps" 列表的筛选条件是unit === 'ms'且score - best > 0.1 ms,按绝对差距降序取前 10 条(report.mjs)。README 还建议用不同的--targets=顺序重复录制,以暴露目标顺序效应。
待调查的最大时间差距(Top 10)
报告专门单列了一节 "Largest timing gaps to investigate later",按"与最佳匹配竞争方的绝对时间差"排序,且明确说明"这只是候选排序,尚未开始性能工作"。完整 10 条如下:
| 套件 / 操作 | Ripple | 最佳参照 | 差距 |
|---|---|---|---|
| recursive-context / mount | 18.612 ms | inferno 6.075 ms | 12.537 ms |
| dbmon / mount | 23.717 ms | inferno 14.133 ms | 9.583 ms |
| portal-swarm / open_all | 10.550 ms | inferno 3.188 ms | 7.362 ms |
| js-framework / runlots | 65.600 ms | solid 58.800 ms | 6.800 ms |
| portal-swarm / open_close_distinct | 10.625 ms | inferno 4.177 ms | 6.448 ms |
| effectful-list / remount | 42.592 ms | inferno 36.467 ms | 6.125 ms |
| portal-swarm / open_close_cycle | 9.600 ms | inferno 4.305 ms | 5.295 ms |
| js-framework / select_lots | 4.820 ms | vue-vapor 0.160 ms | 4.660 ms |
| uibench / tree/[2,2,2,2,2,2,2,2,2,2]/render | 10.556 ms | inferno 6.285 ms | 4.272 ms |
| portal-swarm / mount_closed | 8.962 ms | inferno 5.425 ms | 3.537 ms |
从结构上看,差距集中在三类操作:深层递归树的首次挂载(recursive-context)、portal 的批量打开/开合(portal-swarm),以及大批量 keyed 行的插入与批量选择(runlots、select_lots)。值得注意的是js-framework / select_lots:Ripple 4.820 ms 对照 vue-vapor 0.160 ms,比值高达 30.12×,是单操作比值最大的条目之一,说明大批量选择类交互是明确的可优化点。
代表性套件数据
报告共覆盖 16 个可比套件加 1 个 Ripple 专属诊断(reconcile-anchors),下面选取几组信息密度最高的表格。
js-framework:keyed 行的创建、更新、选择、交换、删除与清空
| Operation | Unit | Ripple score [run range] | p95 | Max RME | Samples | Octane TSRX | Octane JSX | Solid | Vue Vapor | Best matching competitor |
|---|---|---|---|---|---|---|---|---|---|---|
| run | ms | 6.940 [6.200, 8.160] | 7.500 | 8.6% | 24 | 0.93× | 0.81× | 1.00× | 0.81× | solid: 6.920 |
| replace | ms | 13.320 [11.900, 15.800] | 13.700 | 7.6% | 24 | 0.85× | 0.81× | 0.85× | 0.84× | inferno: 14.700 |
| add | ms | 6.780 [6.020, 7.160] | 7.700 | 11.0% | 24 | 0.97× | 0.63× | 0.99× | 0.91× | solid: 6.840 |
| update | ms | 0.600 [0.600, 0.800] | 1 | 52.6% | 24 | 0.64× | 0.22× | 0.21× | 0.83× | vue-vapor: 0.720 |
| select | ms | 0.640 [0.620, 0.840] | 1.100 | 19.8% | 24 | 2.00× | 0.30× | 0.82× | 2.91× | vue-vapor: 0.220 |
| swap | ms | 0.940 [0.920, 1.360] | 1.300 | 59.0% | 24 | 1.24× | 0.33× | 0.96× | 1.74× | vue-vapor: 0.540 |
| remove | ms | 0.660 [0.620, 0.760] | 1.200 | 35.8% | 24 | 0.97× | 0.27× | 0.97× | 1.83× | vue-vapor: 0.360 |
| runlots | ms | 65.600 [58.740, 77.140] | 73.500 | 11.2% | 24 | 0.99× | 0.85× | 1.12× | 0.96× | solid: 58.800 |
| select_lots | ms | 4.820 [4.300, 4.900] | 6.100 | 19.5% | 24 | 17.21× | 0.24× | 0.88× | 30.12× | vue-vapor: 0.160 |
| clear | ms | 65.720 [62.860, 85.480] | 74.200 | 15.0% | 24 | 0.89× | 0.81× | 0.87× | 1.00× | vue-vapor: 65.400 |
(live_inserts_*、fragment_commits_*等 count 类正确性计数在该基线上全部为 0,竞争方同样为 0,故略。)
可以看到:批量场景(run、replace、runlots、clear)Ripple 与 Octane/Solid/Vue Vapor 基本处于同一量级(比值 0.81×–1.12×),而单条select/select_lots的细粒度 DOM 写入明显落后于 vue-vapor,这直接进入了上文的 Top 10 差距榜。
recursive-context:深层递归树的创建、上下文更新与销毁
这是差距榜第一名所在的套件,完整 6 行如下:
| Operation | Unit | Ripple score [run range] | p95 | Max RME | Samples | Octane TSRX | Octane JSX | Solid | Vue Vapor | Best matching competitor |
|---|---|---|---|---|---|---|---|---|---|---|
| mount | ms | 18.612 [16.738, 22.975] | 21.100 | 4.6% | 60 | 0.76× | 0.64× | 1.00× | 0.46× | inferno: 6.075 |
| update_root | ms | 1.850 [1.813, 2.138] | 2.800 | 19.3% | 60 | 0.37× | 0.36× | 0.83× | 1.35× | inferno: 1.137 |
| update_partial | ms | 0.100 [0.063, 0.112] | 0.200 | 122.6% | 60 | 0.32× | 0.25× | 0.50× | 0.89× | inferno: 0.100 |
| partial_unmount | ms | 0.063 [0.013, 0.075] | 0.200 | 236.5% | 60 | 0.33× | 0.36× | 0.31× | 0.45× | inferno: 0.088 |
| partial_remount | ms | 0.450 [0.412, 0.538] | 0.800 | 14.0% | 60 | 1.00× | 0.63× | 0.88× | 0.97× | inferno: 0.138 |
| unmount | ms | 1.150 [0.975, 1.213] | 1.800 | 58.3% | 60 | 0.67× | 0.63× | 0.72× | 0.39× | inferno: 0.250 |
该套件同时保留了上游 Octane 的重复、顺序平衡方言对照组(见 benchmarks/README.md),max RME在两行短耗时操作上超过 100%,再次说明小数值只能当作方向性观察。
streaming-ssr:shell 投递与完整异步服务端流
| Operation | Unit | Ripple score [run range] | p95 | Max RME | Samples | Octane TSRX | Octane JSX | Solid | Vue Vapor | Best matching competitor |
|---|---|---|---|---|---|---|---|---|---|---|
| shell_staggered | ms | 0.258 [0.240, 0.301] | 0.524 | 17.1% | 90 | 0.78× | N/A | 0.36× | N/A | inferno: 0.190 |
| total_staggered | ms | 50.776 [50.554, 51.143] | 52.363 | 0.9% | 90 | 0.99× | N/A | 1.00× | N/A | preact: 50.386 |
| shell_allfast | ms | 0.168 [0.146, 0.187] | 0.272 | 13.0% | 90 | 0.75× | N/A | 0.37× | N/A | inferno: 0.140 |
| total_allfast | ms | 1.643 [1.550, 1.653] | 2.239 | 24.9% | 90 | 0.96× | N/A | 1.26× | N/A | solid: 1.303 |
| shell_cpu_10 | ms | 0.160 [0.115, 0.162] | 0.224 | 10.7% | 90 | 0.82× | N/A | N/A | N/A | octane-tsrx: 0.195 |
| total_cpu_10 | ms | 0.662 [0.493, 0.678] | 0.778 | 11.2% | 90 | 1.12× | N/A | N/A | N/A | octane-tsrx: 0.593 |
| shell_cpu_100 | ms | 0.642 [0.532, 0.649] | 1.192 | 21.6% | 90 | 0.56× | N/A | N/A | N/A | octane-tsrx: 1.156 |
| total_cpu_100 | ms | 4.593 [3.931, 4.622] | 6.061 | 15.9% | 90 | 1.02× | N/A | N/A | N/A | octane-tsrx: 4.497 |
| shell_cpu_800 | ms | 3.708 [3.697, 3.735] | 5.176 | 3.7% | 90 | 0.43× | N/A | N/A | N/A | octane-tsrx: 8.530 |
| total_cpu_800 | ms | 35.211 [34.943, 35.561] | 43.679 | 3.2% | 90 | 0.99× | N/A | N/A | N/A | octane-tsrx: 35.634 |
| shell_cpu_waves_50 | ms | 0.239 [0.239, 0.241] | 0.405 | 5.0% | 90 | 0.45× | N/A | N/A | N/A | octane-tsrx: 0.535 |
| total_cpu_waves_50 | ms | 1.868 [1.865, 1.944] | 2.144 | 6.5% | 90 | 0.24× | N/A | N/A | N/A | octane-tsrx: 7.870 |
total_cpu_waves_50(50 个 CPU 密集波次的完整流)Ripple 1.868 ms 对 octane-tsrx 7.870 ms(0.24×),是该套件中 Ripple 相对优势最明显的操作;而 consumer-paced 的total_staggered与 preact 几乎打平(0.99×)。README 强调:选择 streaming 对照时"不把缓冲 HTML 包装成合成流",没有可用流式渲染器的框架以 N/A 显式标注。
ssr-throughput 与 bundle-size:吞吐与体积
ssr-throughput在 Node 中持续渲染新闻页:news-50/render单次 0.146 ms(对 octane-tsrx 0.081 ms,1.80×),news-500/render单次 2.511 ms(对 vue-vapor 1.264 ms,1.99×),即该基线上 Node 端渲染吞吐落后于两个参照,是明确的后续调查项。
bundle-size记录三组应用(js-framework 行、TodoMVC、chat)的 JS 字节数(raw / gzip / brotli,分别对应 js_、app_、fw_*),核心数字如下(完整 27 行表格见原报告):
| 操作 | Ripple 字节 | 最佳参照 | 备注 |
|---|---|---|---|
| js_raw / js_gzip / js_brotli | 35047 / 13410 / 12006 | preact 25247 / 9990 / 9056 | 整包(含框架) |
| app_raw / app_gzip / app_brotli | 7209 / 2312 / 1991 | svelte 5149(raw) | 纯应用代码 |
| fw_raw / fw_gzip / fw_brotli | 27838 / 11098 / 10015 | preact 19940 / 7985 / 7273 | 纯框架部分 |
可以分两层读:纯框架体积(fw_*)Ripple 约为 preact 的 1.4 倍(gzip 后 11098 vs 7985 字节,比值 0.41× 表示 Ripple 更大);但纯应用代码(app_*)与 svelte/solid 相当甚至更小。README 同时提醒,bundle-size 使用"归一化 esbuild 压缩 + 逐文件 gzip/Brotli",与其余套件的 production 构建是不同的构建契约,不可互换比较(见 benchmarks/README.md)。
其他值得留意的数据点
- memo-wall:
one_change_A0.273 ms,对 octane-tsrx 0.023 ms(12.12×),是全报告中最大的单操作比值之一;而parent_rerender_equal_A/B(输入未变化的父级重渲染)各框架都在 0.17–0.20 ms 的 Δ 量级上打平; - uibench:
tree/[10,10,10,10]/no_changeRipple 2.774 ms 对 inferno 6.641 ms(0.26×)、tree/[2,2,…,2]/no_change0.262 ms 对 1.325 ms(0.10×),空重渲染(no-change)上 Ripple 优势明显;但深层树的 render/removeAll 落后于 inferno(如tree/[2×10]/render0.75×); - reconcile-anchors是 Ripple 专属诊断(direct / wrapped / single / switch 四组锚点列表),只有 Ripple 自身数值,例如
direct.mount10.640 ms、switch.shuffle5.990 ms,无竞争列(全 N/A),用于内部 reconcile 锚点机制的纵向对比; - todomvc:
add1004.060 ms(对 vue-vapor 4.860 ms,0.60×)、edit102.180 ms(0.20×),DOM 计数nodes_100为 727(react 为 623),comments_100为 102 而 react 为 0。
基线如何接入回归护栏
这份报告不只是一次性快照,它还是整个回归护栏体系的"证据来源"。按 baselines/README.md 的政策:
- 初始护栏由三次独立 normal 运行播种:共311 条 guard,覆盖 16 个可比套件,录制工具链为 Node 24.18.0,全部 guard 已对三份录制数据回放通过。护栏明细提交在 baselines/ratios.json,跳过原因记录在 regression-threshold-initialization.json(例如 js-framework 的
update/select/swap/remove/runlots/select_lots因"低于 0.1 ms 或波动过大"未设初始计时 guard); - 计时 guard 政策:允许"三次运行中观察到的最大配对比值 + 50% 余量";两侧分数低于 0.1 ms、比值区间超过 1.5× 或 score/full-sample RME 超过 20% 的,不设初始计时 guard。字节类 guard 在观察值上加 32 字节余量;确定性计数直接用观察到的最大配对比值,零参照不参与比值;
- 本地绝对值回归检查(
--compare/ compare.mjs):计时回退需同时满足"分数增长超过 15%"且"最小增长超过 10%",低于 1 ms 的基线额外要求 0.1 ms 的绝对阈值;确定性度量使用严格比较。compare.mjs 还会校验两次运行元数据一致(cpu、platform、arch、node、quick、lockfileSha256、workloadSha256、CPU throttle),失败、缺失或不兼容一律报错; - 护栏只防回退,不代表胜出:README 明确"passing them does not mean Ripple beats a competitor",且录制本地基线永远不会覆盖已提交的 ratios guard;调整 guard 必须逐条附书面理由,不允许因为某次运行慢就放松限制。
变更前后的标准离线对比工作流(不重新跑基准):
pnpm bench --results-dir=benchmarks/results/run-before js-framework # 在此做运行时/编译器变更,保持基准工作负载不变 pnpm bench --results-dir=benchmarks/results/run-after js-framework node benchmarks/compare.mjs benchmarks/results/run-before benchmarks/results/run-after js-framework阅读与引用时的注意事项
- 绝对计时是机器相关的:基线数值只对 i9-9980HK / darwin / Node 24.18.0 / Chromium 153 这套指纹成立;跨机器、跨 quick/normal 模式、不同 Octane 版本(0.2.4 与 0.2.6)的数值都不可直接比较,README 明确禁止此类混比;
- 比值也受竞争方版本影响:"一个更快的竞争方会推高 Ripple 的比值,即使 Ripple 自身没有变慢";
- 工作负载指纹与 runner 指纹分离:录制/报告工具的改动不会使工作负载指纹失效,
metadataCorrections字段保留了这类仅元数据的修正记录(例如将 TodoMVC 的row_class_writes_complete25由 ms 更正标注为 count,原始测量值未改动); - 历史 Octane 数字不是本 checkout 的基线:报告与护栏全部基于本仓库锁定依赖的本地配对测量生成,
raw timing values were not imported from Octane。
相关文件索引
| 文件 | 作用 |
|---|---|
| benchmarks/results/baseline-report.md | 本文解析的基线报告(Markdown 版) |
| benchmarks/results/baseline-report.json | 报告机器可读版(metadata + 每操作完整行) |
| benchmarks/results/baseline-1/ 等 3 个目录 | 三次独立 normal 运行的原始结果 |
| benchmarks/report.mjs | 报告生成器:元数据校验、中位数合并、差距排序 |
| benchmarks/compare.mjs | 两次保存结果之间的离线回归对比 |
| benchmarks/lib/stats.mjs | 统计口径:稳定窗口 score、p95、RME |
| benchmarks/README.md | 运行命令、选项、套件矩阵与结果解释 |
| benchmarks/baselines/README.md | 护栏政策、刷新规则与初始录制说明 |
| benchmarks/baselines/ratios.json | 已提交的 311 条配对比值 guard |
| benchmarks/suites.json | 套件、端口、迭代次数矩阵声明 |
【免费下载链接】ripplethe elegant TypeScript UI framework项目地址: https://gitcode.com/GitHub_Trending/ripple25/ripple
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考