Notist
On this page
  1. 资源/Data 重构新增耗时定位
  2. 主要结论
  3. 单次主探针:开销位置
  4. 为什么会反复检查
  5. 每页组件副本的扫描分布
  6. 为什么不是 Data 解析
  7. CLI 包装器的额外放大
  8. 诊断性消融与多轮验证
  9. 普通历史二进制,同批三轮中位数
  10. 同一临时二进制:全检查与跳过重复扫描
  11. 每页组件副本:分别省略什么
  12. 独立系统调用证据
  13. 验证与边界
  14. 后续方向

资源/Data 重构新增耗时定位

主要结论

主要退化来自资源会话反复校验全部已观察文件,不是语言 Data 的 JSON/TOML 解析。

当前每页组件副本负载的一次主探针耗时 39.581 s,其中 ResourceSession.validate 占 38.394 s(97.0%)。共执行 7,205 次 validate、23,382,587 次已观察文件检查。

所有临时改写仅发生在 /tmp/opencode/notist-resource-profile-20261103/sources/。正式引擎没有修改,原有未提交 SSG 宿主政策及历史矩阵工作保留。

单次主探针:开销位置

当前版本负载

CLI s

validate s

文件版本循环 s

文件检查次数

实际物理内容读取 s

完整 XIV DB

3.6648

0.2207

0.2069

310,435

0.000004

无模块正文

1.9237

1.4354

1.4351

951,867

0.001595

共享组件

6.5402

5.6775

5.6765

3,782,598

0.001350

每页组件副本

39.5810

38.3939

38.3928

23,382,587

0.016476

宽导出/通配绑定

5.3811

3.8862

3.8856

2,583,192

0.001423

这是完整 CLI 的单次计时探针,用于定位大项,不替代普通二进制的多轮排名。文件内容读取指 FsReader.read_bounded,不是整条源码加载链;validate 与它的文件子循环是包含关系,不能相加。

kind 检查循环在五种负载中均实际迭代零次;计时只有空循环及计数开销。此次不能将退化归因于 kind 目录探测。

为什么会反复检查

调用链中的关键位置:

  • src/resource_session.rs:353:source_bytes 在查询源码字节缓存之前调用 validate;缓存命中也要先扫全批。

  • src/resource_session.rs:199:validate 遍历全部已观察输入,对每项重新取得 revision。

  • src/vault.rs:370:每页 evaluate_in 前、后各调用一次 validate_inputs。

  • crates/notist-cli/src/publication.rs:123:CLI 的 ObservedReader.revision 重新拼路径、加锁并向观察集合插入相同路径,然后调用底层 revision。

  • src/resources.rs:358:FsReader.revision 重新 capability-open 文件,检查普通文件类型,再读取 metadata 并构造版本。

直观地说:读第 100 个输入前,先重新检查前 99 个;读第 101 个前,再检查前 100 个。加载模块也经过这条路径。等所有页面分析完,每页求值前后又把完整集合各检查一遍。

每页组件副本的扫描分布

阶段

validate s

文件检查次数

初始物理页面读取

0.1990

135,460

分析/导入加载

28.5104

17,362,521

每页求值前后

9.6749

5,878,964

最终构建边界

0.0095

5,642

确定性计数与代码吻合:

  • 首先读取 521 个物理页面,产生 521 × 520 / 2 = 135,460 次检查。

  • 最终观察集合是 521 个页面、5,120 个模块及一个虚拟目标配置,共 5,642 项。

  • 521 页各在求值前后检查一次,产生 2 × 521 × 5,642 = 5,878,964 次检查。

  • 重构点 ef38d1b 与当前 083ff6c 的这些检查次数逐情形完全一致。

comemo 的编译/绑定复用没有消除源码版本检查;共享图即使只实际编译 18 个模块,仍有约 378 万次文件检查。

XIV 大量页面由 prepare 作为虚拟文档提供,版本会话最终只观察两个输入(其中一个是虚拟目标配置)。人工站点至少观察 522 项。因此不能只按 HTML 页数推断资源校验成本。

为什么不是 Data 解析

五种完整负载的 data.native、实际 Data parse、Data decode/cache 调用均为零。XIV 的 Python prepare 与模板 site.data 是另一条通道,不是语言 Data API。

另用真实的 JSON/TOML 阳性样例连续调用两次组件,两个版本都测到:

  • 4 次实际 Data 读取调用;

  • 2 次实际解析;

  • 2 次解析缓存命中;

  • 非零的显式类型解码调用。

因此零计数不是探针未接线。这个小样例只验证接线与复用,不构成通用 Data 大文件性能基准。

补充细探针也显示 site.data 载入在本批只是微秒量级。正式 Data 解析器是否有其他负载瓶颈,不属于这次已观察退化的结论。

CLI 包装器的额外放大

对同一批调用进一步逐次计时,观察到:

当前共享组件图,额外细探针

包含式耗时 s

外层 ObservedReader.revision

6.1588

其中路径拼接

0.1708

其中锁/路径克隆/集合维护

1.7859

内层 FsReader.revision

3.7310

其中 capability open

2.1512

细探针更侵入,不能和主探针或普通 CLI 跨运行相减;父子行不能重复相加。它表明重复扫描不只增加内核 metadata 调用,也反复维护本来已经记录过的观察集合。

诊断性消融与多轮验证

同一临时二进制分别保留全检查、跳过文件循环、跳过 kind 循环、跳过取源码触发的扫描、跳过求值前后扫描,以及跳过全部重复扫描。关闭细粒度资源计数与计时,粗粒度阶段探针保持相同。

这些非 full 模式刻意削弱批次一致性,只用于归因。它们不是候选生产实现;固定输入下产物一致,也不能证明构建中修改时安全。

多轮汇总与完整范围由工具写入结果目录的 report.md、summary.json、summary.csv。普通历史二进制、全检查临时二进制与消融分别记录,不把探针秒数替代普通构建秒数。

普通历史二进制,同批三轮中位数

完整负载

重构前 0d81985 s

重构点 ef38d1b s

当前 083ff6c s

XIV DB

3.2684

3.5970

3.6414

无模块正文

0.4875

1.9312

1.9180

共享组件

1.0218

6.8296

6.5032

每页组件副本

1.0344

39.6486

39.3904

宽导出/通配绑定

3.1510

7.4642

5.3872

同一临时二进制:全检查与跳过重复扫描

当前完整负载

全检查 s

不扫描 s

差额 s

XIV DB

3.6757

3.4546

0.2211

无模块正文

1.9281

0.4659

1.4622

共享组件

6.5492

0.8047

5.7445

每页组件副本

39.5257

1.0665

38.4592

宽导出/通配绑定

5.3849

1.4676

3.9172

重构点的同二进制对照也一致:每页组件副本 39.1828 → 1.0900 s,共享组件 6.7391 → 1.0243 s,无模块正文 1.9198 → 0.4678 s。消融因此不只是证明当前某个后续优化点的偶然现象。

每页组件副本:分别省略什么

当前临时二进制模式

三轮中位数 s

min–max s

全检查

39.5257

39.5182–40.4818

只跳过文件 revision 循环

1.0707

1.0613–1.0739

只跳过 kind 循环

39.5125

39.3240–39.6799

跳过取源码触发的全批扫描

10.8077

10.7824–10.8783

跳过求值前后的全批扫描

29.8098

29.6993–29.8109

跳过全部重复扫描

1.0665

1.0604–1.0764

省略取源码扫描回收约 28.7 s,省略求值边界扫描回收约 9.7 s,与独立主探针的分项量级一致;仅省略 kind 没有明显收益。这把主要增量定位到具体的文件 revision 循环,而不只是笼统归因于资源系统。

当前所有消融模式的实际编译/绑定次数也与 full 主探针一致;私有图不扫描时仍完整编译 5,120 个不同模块身份,并未跳过模块工作。

不扫描后仍可能存在共享值/求值/SSG 变更及探针框架的残余成本,例如重构点宽导出消融后为 3.5036 s,重构前为 3.1510 s。本轮没有声称扫描解释了每一微秒的差额。

独立系统调用证据

额外构造 64 详情、66 HTML 的无模块辅助站点,每种模式先完整预热,再 strace -c。它不混入 521 页主计时,追踪时的 wall 不作为性能结果。

当前二进制/模式

openat2

statx

close

read

普通发布二进制

15,477

32,014

15,573

493

临时全检查

15,477

32,014

15,573

493

临时不扫描

198

1,456

294

493

扫描额外增加 15,279 次 openat2、30,558 次 statx、15,279 次 close,但 read 次数不变;正好对应每次版本检查的一次打开、两次元数据查询及关闭。再次印证增加的不是重读内容或解析 JSON。

验证与边界

  • 完整 XIV 为 51,739 HTML;其余各 521 HTML。不删输入、页面、字段或正文,不修改消费者、site.data 或页面枚举模型。

  • 三个普通历史点为 0d81985、ef38d1b、083ff6c;核对二进制和源码指纹,不复用旧秒数。

  • 所有构建子进程退出后才做逐文件 SHA-256 校验,测量不与编译、探针、strace 或 HTML 校验并发。

  • 主探针按初始读取/分析/求值/其他分组;高频文件调用只计数,额外 detail 运行才逐调用读时钟。

  • 全检查临时副本通过资源会话、读取、路径及 Data 共 58 项 Rust 回归。

  • 43 格各一次预热、三轮计时,共 172 次主构建全部成功,所有产物逐文件 SHA-256 与完整基准一致。另有 14 次资源探针、3 次观察包装器细探针及 5 次预热后的 strace。

  • 工具共 22 项 Python 回归通过;integrity.json 自动核对两版本相同的扫描次数、二次增长公式及系统调用差额。

  • CPU 8、温文件缓存、未锁频、不 fsync;RSS 为 wait4 高水位,不是进程树同时驻留总量。

  • 本轮不作 RSS 排名:wait4 还包括从 Python 启动器 fork→exec 前继承的高水位,部分小负载出现约 165 MiB 下限。独立控制给启动器分配 256 MiB 后,连 true 子进程也报告约 271 MiB;因此原始 RSS 字段不能直接视作 Rust 自身峰值,也不能与旧批次混比。

  • 原始记录、输入、补丁、源码与二进制指纹保存在 /tmp/opencode/notist-resource-profile-20261103。复现说明为 tools/RESOURCE_OVERHEAD_PROFILE.md。

  • dashboard.html 为交互归因图,区分单次主探针与三轮同二进制消融;summary.json/summary.csv 保留完整范围和样本。

后续方向

需要调整的是批次快照/版本与失效策略,避免每次缓存命中和每页求值边界都重查全部集合。不能直接提交 no-scans:要保留授权、单次读取变化检测、跨文档一致性、缺失/错误输入失效、prepared retries,以及既有构建中修改检测契约。

本轮只定位与验证,不修改正式引擎。