IceXIV DB 当前工作区重新 profile(2026-11-05)
结论
改动有明确的整体收益:同批 CLI 耗时降低 35.34%,Rust RSS 观测峰值降低 48.91%;求值器仍是最大单项,但当前更应一起关注正文循环中的销毁与分配流量。
同一批次、同一完整输入 | 旧版 85ab69e | 当前冻结工作区 | 变化 |
|---|---|---|---|
CLI wall 中位数,各 6 次 | 14.856s | 9.606s | 耗时 −35.34%,1.547× |
CLI user CPU 中位数 | 13.477s | 8.747s | 不只是 wall 等待减少 |
CLI system CPU 中位数 | 1.624s | 1.115s | 线程/子进程 CPU 可相加,不等于 wall |
serve 初次 Updated 中位数,各 2 次 | 15.410s | 9.118s | −40.83%,仅两个初始样本 |
serve 重建 Updated 中位数,各 6 次 | 15.661s | 8.636s | −44.86%,范围较宽 |
独立 Rust RSS 采样最大观测值 | 6.536 GiB | 3.339 GiB | −48.91% |
独立分配/重分配 API 成功次数均值,各 2 次 | 3.271 亿 | 1.985 亿 | −39.32% |
独立 requested 分配流量均值 | 24.296 GB | 15.050 GB | −38.06%,不是存活堆 |
本轮没有实现任何新优化,差异来自用户已经完成的改动。CLI 六对样本全部更快,当前按中位数折算吞吐约 5,386 页/s;不把上轮报告的 15.124s、最近十轮实验的首次发布成绩或单次 18.222s 混入本批计算收益。
共 32 次完整 CLI 构建并逐文件哈希核对、16 次真实 serve 组装,消费者静态链接检查通过;生产源码在最终复核时仍与冻结版本一致。本轮只证明这里覆盖的输出和性能表现,不声称重新完成了所有语言/wire/九种编辑回归门禁。
测量版本与边界
本轮只做性能测量与分析,不修改生产代码、不继续十轮优化实验、不提交或推送。
当前版本是 HEAD
029bcb8加冻结时全部未提交及未跟踪源码,不是干净 HEAD。完整冻结树 SHA-256:
164e20ae82334082cc1d1959bbcd02fa338aab92e845a95eeaee10e155da8df8。该摘要也涵盖工具/文档;逐文件指纹另存 manifest。生产源码核对与上一活动最后接受的 r04 相同;这不表示当前工作区是干净提交,也不将被拒绝的实验列为已实现的优化。
对照是上次全量 profile 的
85ab69e13bb99cb4016538accdd55c65f0cb6d7c。本轮重新编译两版,不直接减去旧报告的历史耗时。原始证据独立保存于
/tmp/opencode/notist-icexiv-refresh-20261105/,不覆盖任何旧实验。
实际使用时注意旧 pin: icexiv-db 的 flake.lock 仍锁定 85ab69e,.envrc 是 use flake,开发 shell 的 notistPackage 来自该 pin。因此默认 shell 的命令不会自动包含当前未提交的优化;本次明确编译/运行工作区副本,不能把默认 shell 的旧可执行文件误当当前版。若要在消费者目录体验本次冻结版本,可显式运行 /tmp/opencode/notist-icexiv-refresh-20261105/bin/current serve。本轮没有修改该锁文件或安装/替换用户命令。
完整输入与输出
消费者是冻结时的 icexiv-db 实际工作区,包括未提交修改、忽略的游戏导出、隐藏 .data、全部图标和模板。79,362 个输入文件的完整指纹与上次 profile 全部相同,因而还能复用上次输出指纹作为独立正确性标准,而不是让新版本自我生成 expected。
工作量 | 数量 / 逻辑字节 |
|---|---|
详情 / 目录 / 首页 | 51,225 / 513 / 1 页 |
全部 HTML | 51,739 页 |
私有 JSON | 52 个物品块 + 513 个目录文件 = 565 |
最终输出 | 78,004 文件,451,812,631 B |
其中 HTML | 118,097,122 B |
不改页面数量、字段、语言预算、Data 模型、扩展脚本、资产或依赖锁。输入复制和所有构建输出只在外部结果目录中。
方法
硬件为 AMD Ryzen 9 9950X3D2,32 个逻辑 CPU;保留 CPU 0–31 的默认 affinity,保留生产最多 8 个模板线程。两版都用 rustc 1.97.1、LLVM 22.1.6、default-feature release、--locked、debug symbols 1、关闭 incremental。诊断版另启用 profiling feature。编译与本会话的计时工作不并行,整机其它负载与 CPU 频率不锁定,保留慢样本。
CLI 主样本: 每版一次 warmup,再各六个新进程,逐轮 AB/BA 交替。正式样本已有相同磁盘输出,publish 仍实际读取比较全部输出,不是无条件跳过。进程 wall 从 Popen 前到 wait4 返回,包含扩展、组装、发布、析构和退出。
阶段诊断: 当前源码 coarse / detail 各一次 warmup、三次正式样本,独立二进制;不与正常秒数混算。没有显式移动任何 SSG 对象的销毁边界,保留当前 consume-during-HTML 生命周期及自动 Drop 顺序。
真实 serve: before/current/current/before 四个独立普通进程,每个初次构建后同字节写回首页触发三次重建;每版两次初始、六次重建。记录生产终端的 Updated 毫秒,不用 CLI wall 代替。这里验证报告的完整页数,不声称逐修订核对全部 HTTP 字节或覆盖九种编辑语义。
RSS: 另跑 AB/BA 四个普通进程,20ms Rust-only
/procRSS 采样。内核 wait4 高水位也记录;它是进程及已等待子进程的保守最大值,不是各进程相加。其它: 当前版独立 strace、Python cProfile、消费者全量静态链接检查。编译、指纹核对、这些独立观察器均不混入普通计时。
分配流量补充: Linux/glibc 下独立拦截 malloc/calloc/realloc/free,两版各两次 AB/BA,原样转发到 glibc,仅对准确匹配的 Rust executable 计数,Python 子进程不计入。对齐分配 API 和 libc 内部调用不完整;requested bytes 是重复分配流量,不是存活堆、峰值或泄漏证据。该观察器的时间不作加速数据。
Updated 仍只覆盖 target 组装,不含磁盘发布、watcher debounce、旧站点替换、HTTP 传输或浏览器渲染。因此与 CLI 的绝对秒数不能直接等同。
源码变化的解释边界
相对原始 85ab69e,当前生产包括共享/COW 身份与来源文本、来源链、求值局部实例查找缓存、PresentCheckpoint、小字典索引/容量优化,以及 HTML 阶段消费已渲染 Document 等修改。当前 profile 只测它们合在一起的表现,不把每项历史实验收益相加,也不从总差值反推每一项独立贡献。
尤其要注意:现在 documents.into_iter().zip(handles) 在正文渲染循环中消费并释放树。正文阶段包含了原先一部分函数返回时的销毁工作;若它占比提高,不代表 HTML renderer 单独变慢。诊断分别记录 renderer、registry 子项与整个正文循环,但未命名 Self 仍可能包含链接、局部析构、分配器工作和计时开销。
密集探针记录的是嵌套 inclusive/Self wall time,不是 perf sampled CPU Self。递归 present / bind_content 的 inclusive 可超过进程 wall,不能相加。模板范围由主线程包围 worker joins,不把多线程 CPU 秒误加进 wall 分区。
正常版完整原始样本
配对轮次 | 旧版 CLI wall | 当前 CLI wall | 本对耗时下降 |
|---|---|---|---|
0 | 14.963793s | 9.748135s | 34.86% |
1 | 14.875821s | 9.769324s | 34.33% |
2 | 14.908702s | 9.582209s | 35.73% |
3 | 14.836035s | 9.610406s | 35.22% |
4 | 14.763421s | 9.595285s | 35.01% |
5 | 14.778758s | 9.601261s | 35.03% |
旧版范围 14.763–14.964s,当前 9.582–9.769s。两版各六次、新进程、交替次序,六对均更快,user CPU 也明显下降,所以这不是只从某一次快样本选数。
warmup 单列:当前 12.323s、旧版 20.145s。第一份输出由当前 warmup 创建,旧版 warmup 已有输出;缓存和分配器状态也不同,不把这两个不等场景的 warmup 用来计算收益。正式结果是相同输出已存在时的重复 CLI 构建,不是空输出首次发布。
真实 serve 的波动
进程次序 | 版本 | 初次 Updated | 三次同内容重建 Updated |
|---|---|---|---|
0 | 旧版 | 16.312s | 21.848 / 16.494 / 13.740s |
1 | 当前 | 9.125s | 9.376 / 8.638 / 8.616s |
2 | 当前 | 9.111s | 11.350 / 8.633 / 8.566s |
3 | 旧版 | 14.507s | 18.546 / 13.493 / 14.827s |
新进程按 ABBA 次序,避免只把越跑越热当成新版本收益。旧版重建范围 13.493–21.848s,当前 8.566–11.350s,所有慢样本保留;本批当前最大值仍低于旧版最小值,但只覆盖同内容写回触发的全站重建,不能据此保证每一种错误/编辑场景都同样快。
当前组装阶段:求值、正文与分配仍最重要
coarse 三次的 cli.assemble 均值 9.947s,以下为互斥分区,不与 normal 秒数混算:
阶段 | coarse 均值 | 组装占比 | 解释 |
|---|---|---|---|
求值 | 4.049s | 40.7% | 三次范围 4.016–4.106s,最大稳定计算单项 |
正文 HTML / 链接 / 消费已渲染树 | 2.735s | 27.5% | 范围 2.309–3.579s,含对象销毁,不只是 renderer |
静态资源与模板加载 | 1.205s | 12.1% | 范围 0.768–2.067s,波动较大 |
分析 | 0.929s | 9.3% | 范围 0.922–0.936s |
Python 扩展及生成路由准备 | 0.300s | 3.0% | 不是主瓶颈 |
共享模板上下文 | 0.150s | 1.5% | 页面/目录投影 |
页面模板渲染及内存输出写入 | 0.071s | 0.7% | 仍保留最多八线程 |
其它管线 / SSG 尾部 / Vault 与宿主 | 0.509s | 5.1% | 没有把析构移到测量外 |
合计 | 9.947s | 100% | 各分区和严格核对 |
SSG 尾部/计时器间隙均值 0.161s,assemble - ssg.total 的 Vault 及宿主余项 0.145s。这不意味着旧报告约 3.1s 的全部释放工作消失:现在 Document 的释放已经进入正文循环,剩余成本也受共享所有权和分配器影响。不能用单个旧/新 Drop 边界相减算收益。
CLI publish 另为 0.498s,其中读取比较现有完整输出约 0.450s;它不在 Updated 内。
探针波动不隐藏: coarse 进程 wall 是 12.556 / 9.687 / 9.650s,detail 是 13.484 / 12.054 / 12.161s。coarse 首个正式慢样本明显抬高资产/正文均值,仍然纳入上述分区;因此这些百分比是本诊断批的均值,不是声称正常 9.606s 恰好按该比例花费。detail 中位数比普通当前版高约 26.6%,不能把这个差值全当纯计时器成本,更不能扣除/缩放探针数字来伪造正常版子阶段秒数。
密集调用树
detail 三次均值;Self 排除已计时子范围,但包含未插桩工作、分配器与探针记账影响,不是 sampled CPU Self:
位置 | 整站调用次数 | detail Self |
|---|---|---|
| 6,073,286 | 2.474s |
| 8,492,164 | 1.997s |
| 4,426,913 | 1.294s |
正文范围未命名工作 | 1 个范围 | 1.209s |
HTML renderer | 51,739 | 0.916s |
| 6,073,286 | 0.433s |
Notist 前端 compile | 51,739 | 0.405s |
HTML registry | 51,739 | 0.164s |
present 递归 inclusive 是 54.597s,不等于额外用了 54 秒,不能加到进程 wall 上。正文 Self 也不能直接叫作“精确 Drop 时间”:它同时含路径/链接查找、页面构造、局部释放及未插桩工作。
求值器的数百万次小调用与 bind/present 遍历仍然存在。已实现的实例缓存减少重复 key/记录查找,并没有把整个函数求值结果缓存或跳过执行;后续若继续调查,应看通用分派、参数/字段与临时内容的分配,不是再次建议已经存在的同类模块/实例缓存。
Data 与模块缓存仍然有效
观察 | 每次整站 |
|---|---|
Data read | 51,738 |
实际 JSON parse | 565 |
parse cache hit | 51,173(98.91%) |
Data decode | 926,154 |
| 1,955,784 |
模块 compile 请求 / 实际工作 | 2 / 1 |
模块 bind 请求 / 实际工作 | 51,741 / 3 |
Data 实际 parse 约 0.101s,decode 0.050s,data.native inclusive 0.368s(以上均为 detail)。52 个物品块加 513 个目录文件恰好解释 565 次 parse;不是每页重解析全量 Item。模块工作计数在 coarse/detail 的全部主样本一致。JSON parser、模块编译和 Tera 仍不是优先优化对象。
内存与分配流量
同口径 Rust RSS
两个独立诊断进程的 Rust VmRSS 峰值(KiB):
旧版:
6,853,364 / 6,853,436。当前:
3,501,252 / 3,501,352。
各取最大观测值,同口径从 7,017,918,464 → 3,585,384,448 字节,即 6.536 → 3.339 GiB,下降 48.91%。这是 20ms 采样,可能漏掉极短峰值;只采 Rust 进程,不含 Python 子进程,也没有测 serve 同时保留旧/新两版站点时的峰值。
wait4 内核高水位也全部保留,但其进程/子进程范围和 RSS 记账口径与 /proc 采样不完全相同,不把二者混成同一序列计算下降。没有把 Python 启动器的历史高水位当作 Rust 排名,也没有把多个进程的最大 RSS 相加。
当前仍有约 3.34 GiB 工作集,不能说“内存问题已解决”。完整拥有的输出字节本身就是 451.8 MB;若后续目标是 300,000,000 字节的 live memory,则必须讨论输出/树驻留策略,不能仅靠更小的 Dict 就宣称达到。live allocation 与 RSS 不是同一个指标,本轮没有做精确存活堆归因。
观察到的分配 API 流量
独立 glibc API 观察器,两次均值;realloc 成功也包含在“分配/重分配”总次数里,不重复相加:
指标 | 旧版 | 当前 | 下降 |
|---|---|---|---|
成功分配/重分配次数 | 327,078,361.5 | 198,475,261.5 | 39.32% |
requested bytes | 24,295,855,009 | 15,050,053,062 | 38.06% |
非空 free 调用 | 304,643,767.5 | 184,828,346.5 | 39.33% |
realloc 次数(上面总数的子集) | 22,432,926 | 13,645,247 | 39.17% |
这支持“共享/紧凑容器确实减少了大量分配流量”,不只是把释放挪到更晚。仍有近 2 亿次 API 分配事件 / 15 GB requested 流量,与数百万通用求值调用一起,是当前值得进一步做堆/CPU 栈采样的方向。
但不能把 15GB 当成存活内存:重复临时分配会累加,realloc 按完整新请求长度计数,且对齐入口和 libc 内部调用不完整。分配数/free 数不守恒也不能推导泄漏。拦截会明显改变运行时间,因此 alloc-*.json 的 wall 从不参与加速/RSS 成绩。该观察器没有分配栈,不把全部流量归罪到某一个函数或某一类节点。
Python 与文件系统
coarse transport 均值:请求编码 0.017ms、子进程/管道 200.135ms、响应解码 33.915ms、总计 234.230ms。独立 cProfile 的 Python 自身合计 0.354s / 1,174,293 次调用,其中 parse_items Self 108.0ms、JSON raw_decode 68.1ms、dict.get 43.9ms、extend Self 29.1ms。cProfile 有开销,不能拿它的 0.354s 替代正常子进程的约 0.200s。
当前 CLI 的独立 strace 仍记录约 200 万次所选 syscall:
syscall | 次数 |
|---|---|
statx | 1,024,104 |
read | 772,388 |
openat2 | 78,571 |
openat | 26,539 |
close | 105,030 |
getdents64 | 157 |
所选 syscall 总计 | 2,007,254 |
这些包含扩展子进程及 CLI 完整输出比较,不能全部算到 Updated/serve 上。trace wall 为 20.649s,只用于计数,不混入普通计时;strace 的 syscall % time 也不是整站耗时占比。大小上限、regular-file/metadata 检查和能力路径授权不能为了降低 syscall 数而绕过。
下一步调查优先级(本轮不实施)
求值器小操作与分配。 当前约 4.05s 的 coarse 求值范围最稳定,数百万 execute/present/bind 仍在;下一步先用 CPU/分配栈把通用参数、字典、临时身份、捕获/内容缓冲区细分。密集探针的 Self 不能替代真实采样,不能再把已经实现的实例缓存当新方案。
正文循环中的树生命周期和内存表示。 现在它同时承担 HTML、链接和逐页释放,应区分 renderer 与百万级节点/容器释放;在不删权威树、字段、来源、预算和语义的前提下研究实际存活工作集。不要用“挪到 Updated 后”制造加速。
静态资产及安全 IO 的可复用部分。 约 333.7 MB 的静态输入和全量文件比较仍在;共享不可变资产需要可靠失效/授权机制,不能仅信 mtime。publish 优化只改善 CLI,不能直接解释 Updated。
暂不优先 JSON parser、模块编译、Python 编码和 Tera。也不因本轮观察而重新尝试上一活动已拒绝的候选或开启第十一轮优化;那需要新的实现授权和独立验收。
本轮所有性能下降百分比只对应明确列出的相同口径样本;不是对每种消费者/编辑负载的普遍保证。
证据与复现
工具:tools/profile_icexiv_refresh.py;说明:tools/ICEXIV_REFRESH.md;专项测试:tools/test_profile_icexiv_refresh.py。
结果目录内:
manifest.json、workspace.patch、sources/、bin/、reproducer/保存冻结工作区、编译器、普通及诊断二进制和指纹。expected-output.json是旧 profile 的完整输出大小/SHA-256;每个成功 CLI 均在计时外核对全部文件。samples.json、logs/*.json保存每个原始样本、CPU、fault/context switch、负载、诊断 caller edges;summary.json汇总本批次。serve.json/logs/serve-*.stderr保存生产终端原始报告。rss.json/logs/rss-*.rss.json、strace-current.txt、python.pstats、semantic-check.stdout保存独立诊断。allocations.json、bin/allocation-observer.so、allocation-reproducer.py保留分配计数、C 源码/库/编译器指纹及补充驱动,不覆盖主批次 reproducer。integrity.json最终复核冻结源码、二进制、输入、重现脚本与全部输出;正常与诊断结果不混算。最终工作区生产文件与冻结源逐文件一致,没有其它并行源码修改混进结果。
新增工具两项测试通过;这是 profile 工具验证,不把此前完整 Rust/语义门禁冒充为本轮重新运行。