Notist
On this page
  1. 求值器 A/B 与运行时成本(2026-11-04)
  2. 结果摘要
  3. 1. 实验边界
  4. 2. 正确性与测量分组
  5. 3. 整个运行时的数据流
  6. 3.1 宿主先建一轮固定输入视图
  7. 3.2 先分析所有页面,再逐页求值
  8. 3.3 每页 evaluator:先 bind,再 present
  9. 3.4 execute 的内层逻辑
  10. 3.5 求值不是流水线终点
  11. 3.6 Updated 与 CLI wall 的边界不同
  12. 3.7 各阶段到底花多久:独立轻量探针
  13. 4. 无插桩 A/B:哪些改变真正有用
  14. 系统噪声没有隐藏
  15. 5. 深度计数:607 万次调用是什么
  16. 对 Arc 深拷贝假设的修正
  17. 6. 分配流量:对象组织的证据
  18. 6.1 整个 Rust CLI 的公共 allocator API 事件
  19. 6.2 evaluator 内部,按最内层范围归属
  20. item_shell 成为最明确的剩余目标
  21. 实例缓存确实命中,但不是消除必要检查
  22. Scope 的局部收益不能当整站收益
  23. 7. 深度抽样时间:用于找热点,不冒充普通版秒数
  24. 观察器开销有多大
  25. 8. 内存和真实 serve 补充
  26. RSS 没有随分配流量明显下降
  27. 真实 Updated
  28. 9. 对“架构问题还是实现问题”的实测回答
  29. 源码与证据位置

求值器 A/B 与运行时成本(2026-11-04)

本轮沿用完整 IceXIV 输入和 Notist 85ab69e13bb99cb4016538accdd55c65f0cb6d7c,在独立源码副本中测试四种局部改变与组合版,不修改生产实现。原始证据目录:/tmp/opencode/notist-evaluator-ab-20261104/。

结果摘要

四项组合的整轮 CLI 中位数从 14.989s 降到 13.701s,少 1.288s。按同轮配对的耗时降幅中位数为 8.53%,六轮都更快。 主要可确认收益来自实例查找和签名共享;本次不能确认父链作用域或 Vec 复用的单独耗时收益。

更重要的运行时结论:最终有 427 万个 Item,却发生 1,678 万次边界复制、2.61 亿次 evaluator 范围内分配/重分配事件。瓶颈证据集中在元数据复制、参数/调用准备、通用实例查找以及反复构造/挂载内容的组织成本,而不是用户函数递归了数百万层或反复解析完整 JSON。

这轮也否定了两个过强推断:Arc 森林的消费边界不是主要的共享深拷贝来源;减少分配流量并没有显著降低 RSS 峰值。 完整闭包转换的收益仍未测,不能由本轮局部原型直接推算。

1. 实验边界

六种普通二进制都使用相同 release 配置、默认功能、同一 Cargo.lock;没有高频探针或替换 allocator。每种先完整 warmup,再按平衡 Williams 顺序测六轮。主 A/B 的指标是新进程 notist build 的完整 wall/user/system,包含组装、完整现有输出比较及退出,不是只比较某个局部计时标签。

另外的 probe 二进制、分配 API interposer、RSS 采样和 serve 观测均单独归档,不能混进普通版的加速分布。CPU 频率、其它系统任务没有完全隔离;主要缺页和上下文切换保留在每轮记录中,不事后任意剔除慢样本。

版本

唯一改变 / 主要验证的问题

层次

base

原样基线

对照

instance

仅 Type/无 comptime 参数的成功实例,在每次 evaluator 内保留解析结果;data/float 参数保留原 key 路径

实例查找策略

signature

Invocation 的 FunctionDef 从 Box 改成 Arc,捕获副本共享签名;初次构造签名不省略

对象共享表示

scope

Scope 改父链帧;词法 fork 避免整张表复制;移除绑定用 tombstone

环境表示

present

消费式 map/collect,使 Rust 可以复用输入 Vec 的缓冲;每个边界仍返回一个 Item

容器实现

combined

上述四项同时开启

交互效应,不假设收益相加

这不是完整 closure conversion、slot VM 或跨重建结果缓存的实验。没有把读资源、预算、来源、错误恢复、调用或插值边界删除来制造收益。

API 注意: signature 原型保留语言和 serde 值的表示,不保证 Rust 字段级源码兼容:Box→Arc 改变了直接可变访问的方式,落地时需要封装/COW 接口或明确迁移。实验通过不等于可以不经设计直接合并。

2. 正确性与测量分组

  • 六个普通版本,各通过原有 498 项 core/pipeline/library 单元与集成测试,合计 2,988 项执行;没有改断言或跳过预算/transform 测试。

  • 额外四个案例 × 六个版本的完整公开 analysis/evaluation JSON 一致,覆盖词法遮蔽与重复挂载、错误恢复、泛型和 Data 读取。不只是比较 HTML 文本。

  • 每个完整 CLI 构建都在进程计时结束后核对全部 78,004 个输出文件 SHA-256。

  • 不以 Python launcher 继承的高水位解释 Rust RSS;另采样 Rust PID 的 VmRSS。

  • 固定数组记录 evaluator 指标,避免在临界析构期首次创建计时记录而分配内存。细分计时按每 256 个物品页、每 8 个目录页及首页取样,重复三次;它是确定性分层取样,不是随机 CPU 栈采样。

  • 所有分配计数都是流量,不是 peak live heap。realloc 的新大小计入请求字节;deallocation 可能释放其它范围先前分配的对象。

准备阶段修复过观察器/实验构造器,并保留未测量的 preparation-drafts。首轮并行 Rust 测试曾在模块共享 scope 的 Arc 指针断言处失败,该断言发生在进入 evaluator 之前;该测试单线程重跑通过,后续正式验证统一单线程,未删除断言。基线第一次完整测试本身也通过 498 项。

3. 整个运行时的数据流

3.1 宿主先建一轮固定输入视图

CLI 的 assemble 为这一轮新建 Vault / ResourceSession,绑定配置与读取能力。批次内首次读取的字节或错误被固定;相同文件的重复请求仍做逻辑记账,但可以复用物理字节、解析 Data 树。

Python 扩展生成 51,225 个物品文档、513 个目录文档,加手写首页。这里得到的是源文本与路由,不是最终 HTML,也不是提前求值后的整站树。

3.2 先分析所有页面,再逐页求值

SSG 先对每页执行前端 lowering、环境/导入组装、Resolve→Shape→Materialize 和具体实例检查,得到:

Analysis:checked Item 树 + diagnostics + CheckedProgram
DocumentHandle:source + environment + 宿主身份

全部文档先分析完,是为了在任意页面读取其它生成文档之前,将这些文档的源码固定进同一个输入视图。不能不考虑这一约束,就把两轮循环机械改成完全独立的流式单页构建。

CheckedProgram 的实例表和记录已经由 Arc 共享;求值入口的 program.clone() 不是复制全部函数实现。Environment 的导入图也有批次缓存。不要把运行时“607 万次调用”理解成“607 万次模块编译”。

3.3 每页 evaluator:先 bind,再 present

checked Item
  └─ bind_content(scope)
       ├─ 复制结构边界
       ├─ 处理 splice 表达式
       └─ 捕获 pending Call / Repeat 中的自由输入
  └─ present(bound forest)
       ├─ 普通节点:展开 children、验证结构
       ├─ Call:present_call → execute → 再 present 返回内容
       └─ Repeat:求 subject、逐项求 body、挂载、累计预算
  └─ 完成的 Item 树 + diagnostics + dependencies

bind 不是纯粹零执行的复制:splice 表达式可求值,其内部严格数据 let 也可读取资源。但 opaque 内容调用和延迟 for 的实现仍在挂载时执行,未挂载内容不会因为做了捕获就被执行。

3.4 execute 的内层逻辑

  1. 根据当前 Context 的 transform 得到实际目标,同时保留原语义身份。

  2. 求值参数;处理 optional forwarding、类型/约束、fields。

  3. Code 调用另建 inputs Scope,传入 children 和 comptime 参数。

  4. 查找具体 InstanceRecord;缺失时按需检查,而非每次重新编译模块。

  5. 执行 native,或递归解释 checked body。

  6. 数据结果检查返回契约;内容结果保留调用边界、挂载 children、附加展开来源。

当前捕获仍然主要通过 capture_expr/capture_call 递归代换:把自由输入写进表达式/Item 副本。scope 和 signature 实验只改其中一部分组织成本,没有把它整体替换为共享代码加最小环境。

3.5 求值不是流水线终点

每页完成后,Analysis 在求值循环中逐项释放;求值树进入 documents: Vec<Document>,保留到整站处理完成。SSG 随后:

  • 准备静态资源和模板;

  • 利用全站 DocumentIndex 解析链接、embed、HTML registry,生成正文 HTML;

  • 建立共享模板值,再以生产策略最多 8 个线程渲染页面模板;

  • 将 files/pages 移进 BuildOutput;

  • 释放 SSG 临时对象,再释放 Vault / ResourceSession / DataCache。

最终发布文件字节并未在 SSG 返回时销毁;它们已经移动给宿主。没有为了改善数字提前丢掉输出。

3.6 Updated 与 CLI wall 的边界不同

serve 的 Updated 包含组装及其临时对象/Vault 收尾,但不包含静态站点的磁盘 publish。前一个内存站点的替换发生在记录耗时之后,其最后一个 Arc 引用可能在那时或稍后释放。

所以本轮完整 CLI A/B 不能简单改名为 Updated 加速比例;serve 补充组直接读取真实生产报告,但样本和进程顺序少于主实验,结论强度也不同。

3.7 各阶段到底花多久:独立轻量探针

这组只保留阶段/模块计时,不链接深度 evaluator observer 和 global allocator wrapper;SSG 自动销毁顺序不变,所有 metric/edge 记录提前分配。两版各一次 warmup、三次交替正式测量,完整输出核对。以下为该组的均值,不与主 A/B 的中位数混算。

互斥组装阶段

base

base 组装占比

combined

求值

7.201s

50.2%

6.191s

SSG 临时对象 + Vault 收尾

3.059s

21.3%

2.883s

正文 HTML、链接、registry、页面元数据

1.722s

12.0%

1.704s

分析:前端、环境、检查、物化

0.923s

6.4%

0.920s

静态资产准备与模板加载

0.773s

5.4%

0.781s

扩展、生成输入和路由准备

0.297s

2.1%

0.294s

共享模板上下文

0.088s

0.6%

0.090s

页面模板渲染、写入内存输出

0.075s

0.5%

0.070s

其余管线/宿主准备等

0.220s

1.5%

0.224s

组装合计

14.358s

100%

13.157s

另外 CLI 磁盘发布平均 0.406 → 0.401s,其中完整输出内容比较 0.371 → 0.365s;它不属于组装/Updated,也不是本次加速主来源。轻量探针进程 wall 三次分别为 base 15.005 / 15.012 / 14.978s,combined 13.786 / 13.852 / 13.751s,与无插桩主组量级接近,但仍是独立的 profiling-feature 诊断二进制。

该诊断组观察到组装少 1.201s,其中求值少 1.009s,约 14.0%,收尾少 0.176s;其它阶段基本不变。这里说明收益主要落在何处,不把这几个差值硬凑成主组的 1.288s。

收尾内,SSG 尾部为 1.378 → 1.214s,Vault 为 1.681 → 1.669s。仍不应把每个 Drop 标签都解释成对象自身固有成本;此前已证明 allocator 历史和记账分配可改变慢路径落点。本次关键是总组装和总进程都确实缩短,而不是把销毁移出计时。

4. 无插桩 A/B:哪些改变真正有用

正数表示耗时减少,不是吞吐倍数。配对指标先在每轮计算 (base - variant) / base,再取六轮中位数;它与“两个中位数相除”不是同一个统计量。

版本

wall 中位数

六轮范围

配对降幅中位数

配对中位数 bootstrap 95% 区间

更快轮数

base

14.989s

14.919–15.074s

—

—

—

instance

14.428s

14.363–14.464s

3.78%

3.56–4.07%

6 / 6

signature

14.559s

14.380–14.848s

2.81%

1.30–3.96%

6 / 6

scope

14.848s

14.750–14.999s

0.94%

−0.16–1.74%

5 / 6

present

14.868s

14.801–15.236s

0.69%

−0.25–1.10%

5 / 6

combined

13.701s

13.584–13.774s

8.53%

8.02–9.36%

6 / 6

普通版 user 中位数从 13.578s → 12.311s,system 从 1.637s → 1.588s。减少的主要是用户态工作,不只是磁盘发布更快。

两个 wall 中位数的相对差为 8.59%;本文采用较保守、定义明确的“配对 8.53%”概括。bootstrap 只有六个配对样本,不证明其它输入、机器或编译器上也有同样区间。

四项单独降幅不能相加:代码生成、分配器历史、缓存及不同表示之间会相互作用。更不能据此推断完整 slot VM 或闭包转换还能额外快多少。

系统噪声没有隐藏

36 个正式进程合计记录 106 次 major fault,单进程最多 30 次;所有样本均保留。scope 和 present 的区间跨零,所以不把约 1% 的点估计当稳定收益。这批数据足以支持 instance / signature 的当前工作负载收益,却不足以支持“父链环境一定更快”等普遍判断。

5. 深度计数:607 万次调用是什么

下列 count 组是全站精确计数,不是从少量页面放大出来的估算。base / combined 的以下工作量保持一致:

工作

每轮整站

evaluator run

51,739

最终 Item 节点

4,273,237

execute

6,073,286

其中 Code 实现

51,738

其中 native 实现

6,021,548,99.15%

present 函数进入

8,492,164

present 处理的节点位置

8,389,713

空 forest 的 present

2,161,205,进入次数的 25.45%

bind_content 进入 / 处理节点

4,426,913 / 4,272,724

item_shell 边界复制

16,784,043

capture_call / capture_expr

4,125,710 / 2,589,974

词法分支/捕获处显式 scope clone / fork

126,793

递归 origin expansion 访问

4,169,741

Data read / 实际 parse

51,738 / 565

Data decode

926,154

这澄清了三个问题:

  1. “607 万次调用”绝大多数是 native 小操作和内容构造器;Code 函数调用基本是一页一次,不是复杂用户函数递归失控。

  2. present 是 forest 入口,可能为空,也可能同一内容经历不同的挂载/呈现边界,不能把函数进入次数当成唯一节点数。

  3. Scope 复制只有约 12.7 万次,远少于签名捕获、边界复制和 native 调用,不能仅看到 scope.clone() 就认定它是主瓶颈。

按页面类型,物品页恰为 73 个最终 Item、100 次 execute / 页;目录页平均约 1,041 个最终 Item、1,853 次 execute / 页。目录页只占约 1% 的页面数量,却占约 15.7% 的 execute,进一步说明不能只按“页面数”比较内部工作。

对 Arc 深拷贝假设的修正

观察 Arc::unwrap_or_clone 的 forest 消费边界:

  • strong count 为 1:8,284,177 次;

  • strong count 大于 1:1,539 次,占 0.0186%。

因此不能再把当前主要成本解释为“每次拿到 Arc 内容都因共享而深拷贝”。绝大多数这些边界可以直接取得所有权。

这个计数不覆盖所有 clone/COW:capture_expr 中显式复制的 Content forest、Arc::make_mut 的 origin 处理等不在该分支计数里。正确结论是限制一个具体假设,而不是宣称内容复制都不存在。

6. 分配流量:对象组织的证据

6.1 整个 Rust CLI 的公共 allocator API 事件

独立 interposer 将调用转发给同一个 glibc allocator,不改变策略;按可执行文件路径排除 Python。表中不含 libc 内部自行分配、aligned 入口和 constructor 激活之前的事件。

版本

分配/重分配事件

请求字节,十进制 GB

事件降幅

请求字节降幅

base

327.078 百万

24.189

—

—

instance

309.785 百万

24.027

5.29%

0.67%

signature

308.038 百万

22.899

5.82%

5.33%

scope

326.491 百万

24.131

0.18%

0.24%

present

320.747 百万

21.303

1.94%

11.93%

combined

283.827 百万

19.792

13.22%

18.18%

这批 interposer 进程耗时 16–18s,明显有额外开销,不用它们的 wall 算加速。请求字节是多次分配的累积流量,realloc 计完整新大小;24.189GB 不代表同时占用 24GB,更不代表复制过同样多字节。

很有说明力的反例是 present:少请求约 2.886GB,单独 wall 收益却未被本次样本确认。分配字节、分配次数、真实执行时间不是线性关系。

6.2 evaluator 内部,按最内层范围归属

另一组 Rust System wrapper 仅统计 evaluator 内部,固定数组记账。它与上面的整进程 C API 观察器是不同边界,不应要求两者总量相等。

base 内部合计 260.985 百万次、17.797GB;combined 为 218.044 百万次、13.428GB。

最内层范围

base 事件数,百万

combined 事件数,百万

base 请求 GB → combined

item_shell

85.293

85.293

2.753 → 2.753

arguments / target 准备

41.183

41.235

3.329 → 3.331

capture_call

36.714

18.064

2.089 → 0.832

instance 查找

22.346

5.052

0.370 → 0.208

origin expand

8.339

8.339

0.830 → 0.830

返回内容适配

8.337

8.337

1.586 → 1.586

present 自身

6.798

0.467

2.934 → 0.048

bind 自身

6.550

6.471

2.343 → 2.338

实际 JSON parse

3.050

3.050

0.376 → 0.376

scope clone / fork 自身

0.869

0.127

0.150 → 0.006

这些是 Self 分配事件,子操作已记到子范围,避免父子重复累加;不是内存保留对象图,也不是某个 Rust 类型的分配总数。例如 arguments 包含实际 target、参数求值之外的准备、fields 和 Code inputs 等组织工作,不能把全部 41 百万事件都叫作字典分配。

item_shell 成为最明确的剩余目标

item_shell 会清空 children 和 pending,但复制 ctor、instance、target、evaluation、origin、fields、attrs 等边界数据。因此它的 8,529 万次分配不是“复制 children 子树”的直接证据,而是反复复制身份和来源等元数据的证据。

fields/attrs 已使用共享 Dict,仍有 FunctionId 的 String、comptime 参数容器、Origin 的 String/expansion 等拥有数据。哪个字段贡献多少还没有单独分解,不能把整项全部归给某一个字段。

该项占 base evaluator 分配事件的 32.68%;四项优化没有减少它的调用或分配,组合版中其相对份额升到 39.12%。下一步比“再缓存 JSON parser”更值得测的是廉价恢复 checkpoint、身份/SourceId intern、共享来源链以及减少重复边界物化。

这些 shell 请求的平均大小约 32B / 事件,体现出细粒度元数据分配;它只是均值,不是完整大小分布,也不代表所有事件都是同一种字符串或容器。

实例缓存确实命中,但不是消除必要检查

combined 命中 5,293,620 次,占 87.16%,字符串 key 的逻辑生成字节从 136,956,763 → 21,411,064。实际 target 仍参与身份;只有成功、非 checking、Type-only 实例复用,失败和 data/float comptime 仍走原路径。它没有缓存整页结果,也没有合并逻辑资源请求。

Scope 的局部收益不能当整站收益

scope_clone 自身请求字节少约 144MB,但部分 Frame/容器的成本落在其它范围,且多了父链查询和引用管理。单独 scope 的整进程请求流量只少约 58MB,wall 的配对区间跨零。这是“局部标签改善很大、整体收益并不明确”的实例。

7. 深度抽样时间:用于找热点,不冒充普通版秒数

time 组每轮选 201 个物品页、65 个目录页和首页,同一选择重复三次。下面是各类样本内部的 Self wall 占比,不是正常运行的 CPU 百分比;抽样页内仍有密集计时和 allocator wrapper,较短操作受探针影响更大。

base Self

物品页样本

目录页样本

present 自身

15.9%

15.7%

item_shell

13.4%

11.8%

arguments / target 准备

10.8%

11.8%

execute 其余自身

8.7%

10.2%

instance 查找

7.5%

8.9%

present_call 自身

7.6%

6.7%

capture_call 自身

7.5%

6.2%

bind 自身

7.3%

4.7%

combined 的实例查找和 capture_call 份额下降,shell、参数准备依然显眼,与独立分配统计相符。目录页单页要构造约百条记录,远重于一个详情页,不能直接把两类样本总秒数相加后当整站比例。

这里明确不把样本秒数外推成正常整站秒数,也不把父函数 inclusive 和子函数相加。解析器逐类校验所有 Self 之和等于观察到的 run 根范围。

观察器开销有多大

即使 evaluator 细分采样关闭,仍链接深度 observer 的 base-probe wall 就是 16.259s,而普通版中位数 14.989s,约高 8.5%;combined 也有相近量级差异。因此这一组只用于解释内部组织,不拿其 stage 秒数当无插桩真值。阶段图采用另一个不链接该 allocator/高频 observer 的轻量补充组。

8. 内存和真实 serve 补充

RSS 没有随分配流量明显下降

每个普通版本另测两次 Rust PID VmRSS,25ms 采样;不把这些进程纳入主 A/B:

版本

两次观测峰值 GiB

base

6.484 / 6.484

instance

6.481 / 6.481

signature

6.502 / 6.502

scope

6.483 / 6.483

present

6.486 / 6.486

combined

6.491 / 6.491

结论不是“省了 18% 内存”,而是“少了 18% 请求字节流量,峰值 RSS 基本没变”。 最终仍保留相同的 4,273,237 个 Item;仅这些节点的 344B 内联布局就约 1.369 GiB,尚未计其拥有的字符串、容器、Data、模板/输出及 allocator 驻留页面。

本轮没有 compact Item、改变整站保留策略或真正共享最终树。减少短命分配和深复制并不自动减少高峰时同时存活的数据,也不保证 allocator 向 OS 归还更多页面。

真实 Updated

普通 serve 版本

初次组装

三次同内容重建

重建中位数

base

16.576s

17.009 / 13.908 / 13.839s

13.908s

combined

13.431s

14.244 / 12.344 / 12.303s

12.344s

重建中位数少约 1.564s、11.25%,方向与主 CLI A/B 一致;但只有两个顺序运行的 serve 进程,首次重建明显慢于后两次,不把 11.25% 当比 8.53% 更精确的普遍加速结论。所有 Updated 都报告 51,739 页,写回只发生在输入副本且不改变文件内容。

9. 对“架构问题还是实现问题”的实测回答

  1. 确实存在能带来实质收益的局部组织问题。 实例缓存和签名共享并未改语言求值策略,就各自取得稳定的整轮改善;不需要先重写整个解释器。

  2. 不是所有架构化写法都更快。 父链环境省了 clone 范围的分配,却没有在本轮证明稳定 wall 收益;保留它需要更多工作负载和更强的理由。

  3. 成本重心不是资源本身。 Code 调用仅约 5 万、JSON parse 565 次,而数百万 native 操作周围伴随数亿次边界/身份/参数容器相关分配。

  4. 更大的运行时表示问题仍在。 约 427 万节点、1,678 万次 shell、约 6.5 GiB 峰值和未减少的参数/来源/边界流量,提示共享身份、冷元数据、恢复 checkpoint、减少中间物化值得继续做单变量实验。

  5. 尚不能宣称闭包转换、arena、字节码能快多少。 本轮没有实现这些架构,不能把剩余热点的时间简单当可回收收益;语义树权威性、挂载身份、资源授权和预算仍是约束。

这里最稳妥的结论是:先落实已被 A/B 支持的局部表示改进,再针对最大的剩余分配源设计架构实验;不要为了“看起来更函数式”而整体惰性化或无条件共享展开结果。

源码与证据位置

源码逻辑对应冻结版本的 crates/notist-pipeline/src/code/eval.rs、crates/notist-core/src/evaluation.rs、crates/notist-core/src/item.rs、crates/notist-core/src/definitions.rs、src/resource_session.rs、src/vault.rs 和 crates/notist-ssg/src/build.rs。

工具:tools/profile_evaluator_ab.py、tools/evaluator_ab_probe.rs、tools/evaluator_ab_alloc.c、tools/analyze_evaluator_ab.py;协议:tools/EVALUATOR_AB.md。

更早的完整站点基线见 IceXIV profile,释放边界和记账分配的陷阱见 收尾归因。它们的旧秒数不混入本轮 A/B。

本轮证据分组:samples.json / summary.json 为普通 A/B,diagnostics.json / analysis.json 为深度计数/取样/分配/RSS,heap.json 为整进程 API 事件,light.json / light-summary.json 为独立轻量阶段组,semantic.json 与 logs/semantic-*.json 为完整语义对照,serve.json 为真实 Updated。轻量组重现工具另存 light-reproducer.py,不修改主批次脚本或既有样本。

最终共 80 次全量 CLI 构建和完整输出哈希核对、8 次 serve 组装、24 次小案例 JSON 对照。主/轻量源码、二进制、输入、主批次重现脚本指纹及逻辑工作量不变量再次通过校验;消费者自己的 scripts/check_site.py 验证全部详情、目录和本地 HTML 链接通过。结果保存在 integrity.json。本次没有修改 Notist 生产实现,也没有自动应用这些实验补丁。