Notist
On this page
  1. 跟进 main 后的跨页面求值并行复核
  2. 结论
  3. 最终基线上的首轮数据
  4. 同一二进制的直接配对补测
  5. 创建与数据搬运成本
  6. 验证
  7. 比较范围
  8. 站点与协议适配
  9. 方法
  10. 原型仍有的限制
  11. 中间基线与证据

跟进 main 后的跨页面求值并行复核

结论

跟进 main 后,跨页面批量求值并行仍然可行,且最新提交的两轮测量重复了收益。 4 线程整站配对变化中位数为 -14.89%/-14.64%;8 线程为 -18.35%/-18.16%。两种配置各十组比较全部更快。不是沿用旧提交的百分比。

需要修正之前对两线程的判断:当前提交五组比较全部更快,整站配对中位数为 -5.46%,不再是旧实验中没有整站收益的状态。但这不是对所有站点或单页编辑的承诺。

不建议因“平均每页只有几十微秒”放弃整站批量并行;每阶段仅创建少量线程,开销能够摊薄,checked 输入无需深复制。也不建议将现有原型直接默认启用:共享预算和跨页扩展顺序合同尚未解决,接口仍有临时 Send 约束。

若继续工程化,在当前大型站点上,8 线程适合优先降低延迟,4 线程适合减少相对的 CPU 增量;小批次/单页仍应有串行路径。没有测量 16/32 线程,也没有证明 128 页是最佳批大小。

最终基线上的首轮数据

模式

整站 wall 中位数

求值阶段中位数

峰值 RSS 中位数

整站配对变化

未修改 main

7.678 s

未加计时

2699.7 MiB

基准

候选原串行路径

7.674 s

2.355 s

2700.1 MiB

-0.09%

适配器单线程,无工作线程

7.906 s

2.628 s

2710.8 MiB

+2.91%

2 线程

7.259 s

1.704 s

2910.7 MiB

-5.46%

4 线程

6.535 s

1.159 s

2860.0 MiB

-14.89%

8 线程

6.257 s

0.947 s

2740.1 MiB

-18.35%

候选原串行相对未修改 main 的五组配对变化在 -0.62%~+0.50%,中位数 -0.09%,控制项接近不变。2/4/8 线程各五组全部更快;适配器单线程五组全部更慢。这直接区分了接入路径成本与创建线程成本:不开工作线程也会产生额外成本。

同一二进制的直接配对补测

配对实验

串行整站中位数

并行整站中位数

整站配对变化中位数

求值阶段配对变化中位数

4 线程

7.634 s

6.519 s

-14.64%

-51.14%

8 线程

7.654 s

6.264 s

-18.16%

-59.35%

4 线程组的求值阶段中位数为 2.347 → 1.145 s;8 线程组为 2.357 → 0.956 s。后者按配对比值约为串行的 2.46 倍速度,不是八倍加速;整站约为 1.22 倍速度。

逐次 user + system 后取中位数,4 线程由 7.908 → 9.450 CPU s,增加 19.51%;8 线程由 7.926 → 10.345 CPU s,增加 30.52%。峰值 RSS 对应增加 160.1/40.6 MiB。内存变化不随线程数单调,不能将它解释成固定的每线程开销或推导“线程越少越省内存”。

逐次 wall 减本次求值阶段时间后再取中位数,4 线程组为 5.284 → 5.372 s,8 线程组为 5.289 → 5.311 s。这不是精确的析构或 IPC 计时,也不是可被求值并行消除的部分。当前求值以外仍有约 5.3 s 工作,因此即使继续加核,整站收益也不能随求值阶段线性增长。

创建与数据搬运成本

空任务 scope 创建并 join 2/4/8 个线程的本机中位数为 20.661/33.541/59.572 µs,p95 为 25.540/45.251/88.422 µs。实际创建成本会随环境和任务变化,但这里每阶段只付一次少量线程的成本,不是每页付一次;相比秒级阶段收益不是主要障碍。

没有新增整份 AST/DOM/输出树的序列化或深复制。已有 clone、分配及引用计数仍存在,借用和 Arc 共享也不是零成本;本轮没有独立量化锁等待、跨核引用计数或分配器争用,不能声称已确定并行扩展性的主要瓶颈。

验证

最终候选默认全工作区 99 个测试套件,918 项通过,0 失败、0 忽略。原有三项并行回归继续覆盖冷缓存、数据错误/来源、顺序、依赖、句柄隔离及逐页预算恢复。

最终基线共 56 次成功完整构建,每次 78,004 项产物、451,812,631 bytes,其中 51,739 个 HTML 页面,长度与 SHA-256 全部一致;输出清单也与 3d17013 的清单完全一致。没有将中间基线的慢样本删去后混入当前统计。

比较范围

最终基线固定为 f6cca8d2de7cdf417cc823ef09e9f4542a6bb0f9。实验分支 perf/eval-batch-parallel 已合入该提交;没有向 main 合并实验代码。本轮重新构建 main 基线和候选,不能把之前 49a643a 上的百分比当作当前结果。

main 中与本轮有关的改动包括 checked 调用参数绑定、统一内容发射、Empty/One/Many 内容表示、comptime where 与泛型 Path 链接,以及 build-scoped SSG 扩展事件。随后加入的 LSP 分析快照及导航重构也包含在最终基线内。

并行方案保持不变:页内串行、每批 128 页、最多 8 个 scoped 工作线程;共享资源与数据缓存通过 Mutex 适配器访问,不复制整个 Vault,不通过进程 IPC 传递 AST 或结果树。最终归并顺序与页面顺序一致。此次只解决 main 的阶段顺序冲突并格式化接入代码,没有增加新的求值优化。

站点与协议适配

同一份冻结 icexiv-db 文档、JSON、模板和原页面生成函数,51,739 页。旧站点生成脚本使用单请求扩展协议;独立夹具增加 eval_parallel_events.py,将 before_compile 交给原 extend 函数处理,继续执行原导出/catalog 校验,返回相同的生成文档。catalog 从原 data/catalog.json 读取。

适配器按当前主机合同接收 before_template 的完整请求后返回无修改响应,不跳过该事件、不省略页面/HTML 的序列化或 Python 解析。原生成器没有模板阶段修改,因此此处不添加新的模板行为。主仓库与实际 consumer 未修改;两侧使用相同适配器。

需要区分两种数据传递:本实验工作线程共享借用的 checked 输入,并转移输出容器所有权;SSG 命令扩展则确实会经 JSON Lines/子进程传递数据。后者包含当前 Page.html,是公共串行阶段,不能算作工作线程复制开销,也不能用本次求值并行消除。

方法

  • 冻结源码后测试与构建,baseline/candidate 各有独立 Cargo target。release、debug=1、incremental=0、locked、offline、编译 jobs=2。

  • 全工作区默认 feature 测试,test-threads=2;没有声称全 feature 或其他平台回归。

  • 正式测量不与本会话编译/测试重叠。未修改系统设置,未控制其他程序、OS cache 或 CPU 频率。

  • 首轮包含未修改 main、候选原串行路径、适配器单线程、2/4/8 线程;各预热,再进行五轮轮换/反转顺序。

  • 补测使用同一候选可执行文件,原串行路径与 4/8 线程分别五组直接配对,交替先后顺序。

  • 每次完整构建在计时外校验产物路径、长度、SHA-256。保留所有慢样本;整站变化采用逐对 wall 比值的中位数,不冒充两组中位数相除。

  • 求值阶段计时包括文档装配及收尾;wall 包括完整构建、扩展和子进程生命周期。user/system CPU 和 maxrss 是进程资源指标,不是求值独占成本。

  • 空任务线程测量独立执行,各配置先预热 20 次,再保留 500 次 scope 创建/join,不与整站计时混跑。

原型仍有的限制

ResourceSession/DataCache 的共享预算和缓存未复制为线程私有副本,但并行请求交错会改变共享预算耗尽时的页面归属。输出扩展的跨页可观察行为也需要单独定义;Send + Sync 只保证线程安全,不保证业务顺序不变。

当前普通 SSG 泛型 API 临时增加 Send 约束,并行阶段还会延长全部 Analysis 与输出树的重叠持有期。适配器单线程控制项用于观察这类接入开销,不应将所有额外时间都归咎于线程创建。

因此实验仍是显式启用:未设置 NOTIST_EVAL_WORKERS 走原串行路径;设为 1 使用适配器但不创建工作线程;2/4/8 使用对应工作线程数。保留独立分支,不默认启用、不直接作为生产合入方案。

中间基线与证据

先对启动时 main 3d17013 完成一次独立实验。911 项默认工作区测试通过,56 次成功整站构建逐次产物一致,但首轮 wall 出现 41.537~141.158 s 的异常慢值;候选原串行控制相对未修改基线的配对中位数甚至为 -13.10%。该轮不适合给出稳定的整站加速幅度。

3d17013 的八线程补测虽为 -17.77%,仍不替代后续 f6cca8d 的独立测量。原始样本全部保留于 /tmp/opencode/notist-eval-parallel-3d17013,不删除或合并进最终提交的数据集。首次测试因为独立源码副本缺少 target 目录导致两项 CLI 测试创建临时目录失败;补齐目录后完整重跑,没有修改测试或跳过用例。

最终证据目录:/tmp/opencode/notist-eval-parallel-f6cca8d。baseline-source、candidate-source、candidate-source-hashes.json 和 candidate.patch 保留源码;bin、binaries.json 保留被测二进制及摘要;inputs.json、output-inventory.json 保留完整输入/输出清单;samples.json、summary.json、confirmation-samples.json、confirmation-summary.json 与 logs 保存全部样本和日志。tools/profile_eval_parallel.py 的 event-protocol 准备选项和 tools/thread_scope_cost.rs 提供复现入口。