From 7750a9ccf3b8d7872f794e79aeb088b1df1fae4f Mon Sep 17 00:00:00 2001 From: wangzhizhou <824219521@qq.com> Date: Fri, 13 Nov 2026 00:07:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20report=20265=20=E2=80=94=20after=20?= =?UTF-8?q?a=20reboot=20old=20and=20new=20packages=20measure=20the=20same;?= =?UTF-8?q?=20the=20gate=20metric=20is=20unsound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With zero swap both packages sit at about 37.4 MiB private committed across five rounds each and both fail the 34 MiB limit, while live heap is about 20 MiB in both. The metric moves inversely with memory pressure, so the released 1.2.1 fails on a clean system too. Signed-off-by: wangzhizhou <824219521@qq.com> --- HANDOFF.md | 1 + .../265-p1u-followup-latency-memory.md | 30 ++++++++++++++++++- docs/reviews/README.md | 2 +- ...36\344\270\216\347\247\257\345\216\213.md" | 3 ++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index e21b6f9..87423a4 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -117,6 +117,7 @@ make macos-offline-tests ## 9. 会话日志(最近) +- 2026-11-13 / Claude Code(air):**重启后内存重测(报告 265 §4.3):无回归,门禁口径失效**。swap 0、负载 1–4,新旧包各 5 轮:稳态「私有已提交」新 37.4 / 37.3、旧(1.2.1 发布版)37.5 / 37.3,两者都 5 轮全超 34 MiB;存活堆都是 20.1–20.2 MiB。该口径随内存压力反向变化(swap 满时 31–34,swap 空时 35–37.6),量的是系统状态。**待用户裁定**:把判定口径换成 `heap_live_mb`(需 ADR)。已装回新包 `3e428b7`。PR #274(ADR-337)CI 全绿(首跑 `macos` 是没分到运行机被取消,重跑通过),**未合并、等用户授权**;本条文档提交暂留本地,等 #274 合并后再推,免得重跑 CI。 - 2026-11-12 / Claude Code(air):**用户实机反馈两处缺陷 → ADR-337**。① `12:30` 得到 `12;30`:带 Shift 的标点到核心时是未加 Shift 的字符 → 宿主改用 `typedScalar`(按键码 + Shift 重算)。② 拼音上屏后五笔码提示不出现:探针复现属实(宿主无浮层变化、诊断日志无取光标记录),进程内重放逻辑通过,**根因未定位**;改为上屏前候选快照并给提示路径每个分支加诊断日志。用户确认练习「字根」「词组」题型正常。四道检查全绿,新增 3 条真机样本(共 26 条)。**两项修复都未在真机确认**:等本批 PR 合并出包后装机,先看 `lampyris-hud-diag.log` 里的 `hint skip=/show=`。内存门槛按用户决定:重启电脑后重测再定。DMG 完成页仍未验证。 - 2026-11-12 / Claude Code(air):**首键 +4.6 MiB 归因完成(报告 265 §4.2),不改产品代码**。读 `vmmap` 的 MALLOC ZONE 存活分配:首键时点只有两种状态——5.2 MiB(无会话存活,数据已随 `Weak` 缓存释放)或约 19 MiB(有会话存活);新包 8 轮里 3 轮撞上后者、旧包 5 轮 0 轮(偶然概率约 20%),无会话的轮次新旧完全相同。稳态存活堆新包 +0.5 MiB(21.0 vs 20.5)。`memory-gate.sh` 新增参考字段 `heap_live_mb` / `heap_allocations`。副产结论:冷首键 ~30 ms 很可能来自「最后一个会话释放后重新读盘解析」,与本批无关,是否常驻数据需 ADR。PR #273 待合并;已装仍是 `3e428b7`。 - 2026-11-12 / Claude Code(air):**内存新旧包 A/B(报告 265 §4.1)**。新→旧(1.2.1 发布版)→新,各经 `install.sh` 覆盖安装。稳态私有中位数:`six-words` 新 31.8 / 旧 33.0,`idle-8s` 新 31.6 / 旧 31.0 → **稳态无回归**;但学习区开启时两者都贴 34 MiB 门槛(旧包最高 33.7,新包 11 个样本 2 个超限,同包三次运行 FAIL/PASS/FAIL)。**新发现:首键时点新包 +4.6 MiB(27.8 vs 23.2),未归因。** 结束状态:已装新包 `f38e4e1a…`(`3e428b7`)、输入源萤火虫、config 未变;`updates/` 多出两份 A/B 产生的备份(`…T105406Z`、`…T110001Z`)未删。待用户裁定:门槛是否重测调整、首键内存是否发版前归因。 diff --git a/docs/reviews/265-p1u-followup-latency-memory.md b/docs/reviews/265-p1u-followup-latency-memory.md index b137eea..f0afe82 100644 --- a/docs/reviews/265-p1u-followup-latency-memory.md +++ b/docs/reviews/265-p1u-followup-latency-memory.md @@ -2,7 +2,8 @@ - 日期:2026-11-12 / Claude Code(air 本机) - 范围:报告 264 之后的收尾——仓库状态核对、把探针测不了的项尽量补上、延迟与内存门禁复测 -- 结论:**延迟 PASS(稳态 p95 7.33 ms,0 外联);内存门禁在学习区开启时不稳定——新旧包 A/B 显示稳态无回归,但两者都贴着 34 MiB 门槛**(§4.1)。 +- 结论:**延迟 PASS(稳态 p95 7.33 ms,0 外联);内存无回归——重启后新旧包同条件各 5 轮完全一致(§4.3),但门禁口径本身失效: + 已发布的 1.2.1 在干净系统上同样 5 轮全超 34 MiB,而存活堆只有约 20 MiB**。 新包「首键」时点高约 4.6 MiB 已归因(§4.2):**不是泄漏、也不是数据变多**,是采样时是否恰好有一个输入会话存活;稳态存活堆新包多约 0.5 MiB。 ## 1. 仓库状态(核对时) @@ -118,6 +119,33 @@ A/B 过程中的换包:旧包与新包各经 `install.sh` 覆盖安装一次 这很可能就是冷首键 30 ms 量级延迟(ADR-307)的主要来源,且与本批改动无关。是否改成「宿主存活期间常驻数据」是内存换延迟的取舍, 需单独立 ADR(会让无会话时的常驻内存从约 5 MiB 升到约 20 MiB)。 +### 4.3 重启后重测(2026-11-12 夜,用户重启电脑后) + +条件:开机后等 1 分钟负载降到 4 以下再开始;全程 **swap 用量 0**,负载 1.3–3.7。同一配置(学习区开,门槛 34 MiB),各 5 轮,新 → 旧 → 装回新。 + +| 包 | 时点 | 「私有已提交」逐轮 | 中位数 | 存活堆 | 判定 | +|---|---|---|---|---|---| +| 新 `f38e4e1a…` | six-words | 35.0 / 37.5 / 37.2 / 37.4 / 37.6 | **37.4** | 20.1–20.2 | FAIL | +| 新 | idle-8s | 34.9 / 37.3 / 37.0 / 37.3 / 37.5 | **37.3** | 20.1 | | +| 新 | first-key | 33.2 / 34.1 / 33.0 / 32.7 / 34.4 | 33.2 | 19.2 / 5.1 ×4 | | +| 旧 `3eeeb0aa…`(1.2.1 发布版) | six-words | 37.3 / 37.5 / 37.5 / 37.3 / 37.7 | **37.5** | 20.2 | FAIL | +| 旧 | idle-8s | 37.2 / 37.3 / 37.3 / 37.2 / 37.5 | **37.3** | 20.1 | | +| 旧 | first-key | 33.0 / 33.1 / 32.4 / 33.3 / 34.1 | 33.1 | 5.1 ×5 | | + +结论: + +1. **新旧包没有差别**:稳态中位数 37.4 对 37.5、37.3 对 37.3,存活堆都是 20.1–20.2 MiB,首键时点也一致。§4.1 里看到的差异全部是测量噪声。 + 本批改动(ADR-325…336)**没有内存回归**。§4.2 说的「稳态存活堆 +0.5 MiB」在干净状态下也不成立(那是用户词库与系统状态的差异)。 +2. **已发布的 1.2.1 在干净系统上同样过不了这道门禁**(5 轮全超,约 37.3–37.7 对 34)。门槛失效与本批无关。 +3. **「私有已提交」这个口径随系统内存压力反向变化**:swap 用满时 31–34 MiB,swap 为 0 时 35–37.6 MiB,而存活堆始终约 20 MiB。 + 差出来的 13–17 MiB 是分配器已释放但系统尚未回收的页——内存紧张时被回收(数字变低),内存宽裕时留着(数字变高)。 + 它量的主要是系统状态,不适合做有固定门槛的门禁。首键时点最能说明问题:存活堆只有 5.1 MiB,这个口径却报 33 MiB。 + +建议(需 ADR,未自行修改门槛或判定):把门禁的判定口径从「私有已提交」换成 `heap_live_mb`(本报告已让脚本输出), +门槛按实测约 20 MiB 留余量定(如 24 MiB,学习区开关不再需要两档),「私有已提交」与 footprint 降为参考项。 + +结束状态:已装回新包 `f38e4e1a…`(`3e428b7`),宿主跑新二进制,输入源为萤火虫。证据:`50-mem-reboot-*.txt`、`51-mem-reboot-*.txt`、`memory-reboot-new/`、`memory-reboot-old/`。 + ## 5. 仍未验证 `12:30`、五笔码浮层是否可见、练习「字根」「词组」题型、设置页 7 个新开关、DMG 安装完成页——都需人工目视。 diff --git a/docs/reviews/README.md b/docs/reviews/README.md index 5ec9e1e..cbb9c40 100644 --- a/docs/reviews/README.md +++ b/docs/reviews/README.md @@ -39,7 +39,7 @@ | 1.1.0 发布线 | 225–250 | 简码/排序、验收成本、延迟归因、1.1.0 发布与站点 | | 1.2.0 候选与发布 | 251–262 | B1 门禁、B0 运行时、学习区、练习四题型与键位图、1.2.0 发布 | | 1.2.1 清理 | 263 | 安装/无障碍补丁、死代码与文档审计 | -| P1-U 补充复验 | 265 | 分号选词真机 PASS;延迟稳态 p95 7.33 ms PASS;内存 A/B:稳态无回归但学习区开启时贴 34 MiB 门槛;首键 +4.6 MiB 归因为采样时会话是否存活(非泄漏),稳态存活堆 +0.5 MiB | +| P1-U 补充复验 | 265 | 分号选词真机 PASS;延迟稳态 p95 7.33 ms PASS;内存:重启后新旧包各 5 轮完全一致、无回归;门禁口径失效(1.2.1 发布版在干净系统上同样全超 34 MiB,存活堆约 20 MiB) | | P1-U 装机 | 264 | ADR-324…336 候选 `3e428b7` 装机 PASS;真机键入复验 22/22 PASS(首轮因无辅助功能权限 BLOCKED,授权后复跑);5 类目视项待人工 | ## 4. 常见查找 diff --git "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" index 5ad60ad..0a70f09 100644 --- "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" +++ "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" @@ -76,6 +76,9 @@ > 拼音上屏后的五笔码浮层、练习「字根」题型。**第二档剩余**:U2-2 五笔造词(要定启发式)、U2-3 扩展词库(要联网取数据并登记许可)、 > U2-5 分阶段解锁与跟打(要定口径与文本来源)——三项都需要你先拍板,不是能直接开工的活。 +> **内存结论(报告 265 §4.3,重启后重测)**:新旧包同条件各 5 轮完全一致,**本批无内存回归**;但门禁口径失效——1.2.1 发布版在干净系统上同样全超 34 MiB,存活堆只有约 20 MiB。 +> 待裁定:判定口径换成存活堆 `heap_live_mb`(需 ADR)。此前关于「贴线」「首键 +4.6」「稳态 +0.5」的说法以本条为准。 +> > **实机反馈(2026-11-12,ADR-337)**:`12:30` → `12;30`(Shift 标点取字符有误,已修);拼音上屏后五笔码提示不出现(已加固并加诊断,**根因未定位、未确认修好**)。 > 用户已确认练习「字根」「词组」题型正常;内存门槛定为「重启电脑后重测再定」;DMG 完成页待出 DMG 后确认。 > From 62f91db1e0ac2ca5153e48621ab8b482aec63624 Mon Sep 17 00:00:00 2001 From: wangzhizhou <824219521@qq.com> Date: Fri, 13 Nov 2026 00:13:24 +0800 Subject: [PATCH 2/2] test: judge the memory gate on live heap with a 24 MiB limit (ADR-338) Private committed memory moves inversely with system memory pressure, so the released 1.2.1 fails the old 34 MiB limit on a clean system while its live heap stays near 20 MiB. The gate now judges DefaultMallocZone allocated bytes at the steady samples, keeps the old figures as reference, and can re-judge saved snapshots with ANALYZE_ONLY=1. Signed-off-by: wangzhizhou <824219521@qq.com> --- HANDOFF.md | 1 + ...14\346\224\266\346\240\207\345\207\206.md" | 2 +- .../265-p1u-followup-latency-memory.md | 4 +-- ...63\347\255\226\350\256\260\345\275\225.md" | 28 +++++++++++++++ ...36\344\270\216\347\247\257\345\216\213.md" | 2 +- scripts/acceptance-evidence.py | 2 +- tools/macos-probe/memory-gate.sh | 35 ++++++++++--------- 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 87423a4..c6aa80c 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -117,6 +117,7 @@ make macos-offline-tests ## 9. 会话日志(最近) +- 2026-11-13 / Claude Code(air):**内存口径已定(ADR-338)**。`memory-gate.sh` 改以稳态存活堆 `heap_live_mb` 判定,门槛 24 MiB,不再按学习区分档;「私有已提交」与 footprint 只作参考;新增 `ANALYZE_ONLY=1` 复核旧快照。对报告 265 的 5 组快照(23 轮)重判全部 PASS(20.1–21.2 MiB)。取代 ADR-263/292/312 的口径与门槛。**发版阻塞里的内存一项已解除**;剩余:PR #274(ADR-337)未合并、其两项修复未实机确认,升版本号,DMG 完成页。本条与上一条文档提交都在本地 `dev`,等 #274 合并后再推。 - 2026-11-13 / Claude Code(air):**重启后内存重测(报告 265 §4.3):无回归,门禁口径失效**。swap 0、负载 1–4,新旧包各 5 轮:稳态「私有已提交」新 37.4 / 37.3、旧(1.2.1 发布版)37.5 / 37.3,两者都 5 轮全超 34 MiB;存活堆都是 20.1–20.2 MiB。该口径随内存压力反向变化(swap 满时 31–34,swap 空时 35–37.6),量的是系统状态。**待用户裁定**:把判定口径换成 `heap_live_mb`(需 ADR)。已装回新包 `3e428b7`。PR #274(ADR-337)CI 全绿(首跑 `macos` 是没分到运行机被取消,重跑通过),**未合并、等用户授权**;本条文档提交暂留本地,等 #274 合并后再推,免得重跑 CI。 - 2026-11-12 / Claude Code(air):**用户实机反馈两处缺陷 → ADR-337**。① `12:30` 得到 `12;30`:带 Shift 的标点到核心时是未加 Shift 的字符 → 宿主改用 `typedScalar`(按键码 + Shift 重算)。② 拼音上屏后五笔码提示不出现:探针复现属实(宿主无浮层变化、诊断日志无取光标记录),进程内重放逻辑通过,**根因未定位**;改为上屏前候选快照并给提示路径每个分支加诊断日志。用户确认练习「字根」「词组」题型正常。四道检查全绿,新增 3 条真机样本(共 26 条)。**两项修复都未在真机确认**:等本批 PR 合并出包后装机,先看 `lampyris-hud-diag.log` 里的 `hint skip=/show=`。内存门槛按用户决定:重启电脑后重测再定。DMG 完成页仍未验证。 - 2026-11-12 / Claude Code(air):**首键 +4.6 MiB 归因完成(报告 265 §4.2),不改产品代码**。读 `vmmap` 的 MALLOC ZONE 存活分配:首键时点只有两种状态——5.2 MiB(无会话存活,数据已随 `Weak` 缓存释放)或约 19 MiB(有会话存活);新包 8 轮里 3 轮撞上后者、旧包 5 轮 0 轮(偶然概率约 20%),无会话的轮次新旧完全相同。稳态存活堆新包 +0.5 MiB(21.0 vs 20.5)。`memory-gate.sh` 新增参考字段 `heap_live_mb` / `heap_allocations`。副产结论:冷首键 ~30 ms 很可能来自「最后一个会话释放后重新读盘解析」,与本批无关,是否常驻数据需 ADR。PR #273 待合并;已装仍是 `3e428b7`。 diff --git "a/docs/MVP\344\272\247\345\223\201\351\252\214\346\224\266\346\240\207\345\207\206.md" "b/docs/MVP\344\272\247\345\223\201\351\252\214\346\224\266\346\240\207\345\207\206.md" index 150198c..e6710e4 100644 --- "a/docs/MVP\344\272\247\345\223\201\351\252\214\346\224\266\346\240\207\345\207\206.md" +++ "b/docs/MVP\344\272\247\345\223\201\351\252\214\346\224\266\346\240\207\345\207\206.md" @@ -20,7 +20,7 @@ MVP是首个准备正式分发的macOS产品版本:核心输入功能齐全、 | 交互完整 | 首次安装可照说明完成;设置分组连续、名称清楚;词库错误保留输入、可纠错;反馈/复制结果明确;错误不反复打断 | 用户从安装到输入、修改设置、纠错、反馈、升级/卸载完整任务走查;不要求新增引导弹窗或助手 | | 视觉良好 | 候选/设置/词库/版本窗口/菜单风格统一;选中、首选、悬停、错误状态可辨;布局无重叠/不可操作截断;亮暗与Retina/非Retina下文字清楚,图标实际显示合格 | 完整界面状态截图和人工评审;正文/次要文字实际合成背景对比度≥4.5:1,不能以全图极值证明;长词可读且提交完整文本 | | 可访问性 | 核心操作可用键盘完成;VoiceOver能识别候选/选择/设置标签,辅助动作选词不夺走客户端焦点 | 原生AX及VoiceOver实机结果;安全测试环境中复验 | -| 性能与体积 | 按键→候选p95<16ms,正常输入稳态常驻内存≤30MB,安装包≤15MB;无可感知持续卡顿或异常空闲负载 | 真实IMK与FFI分别记录;明确计时边界/环境/预热/样本数;冷简繁与面板另报告,不用单点或不同内存单位混算 | +| 性能与体积 | 按键→候选p95<16ms,正常输入稳态常驻内存≤30MB(MVP 时的口径;自 ADR-338 起门禁改为稳态存活堆 ≤24 MiB,原口径随系统内存压力漂移,只作参考),安装包≤15MB;无可感知持续卡顿或异常空闲负载 | 真实IMK与FFI分别记录;明确计时边界/环境/预热/样本数;冷简繁与面板另报告,不用单点或不同内存单位混算 | | macOS兼容 | 至少macOS13 Intel与设备B macOS27 arm64的目标覆盖;多屏/缩放/Spaces/全屏测试;支持范围和未验证版本明确 | 最终包commit/SHA/架构/签名/系统/应用版本矩阵;目标必测项不得SKIP冒充通过 | | 隐私安全与数据 | 核心零网络/遥测、唯一配置、合规数据;测试与异常路径保护已有数据 | make verify零诊断、安全脚本正常/失败/中断证据,独立第三方离线重建及零外联核验 | | 分发就绪 | 签名、公证、Gatekeeper、二进制/源码对应、附件摘要、安装/升级/卸载恢复及发布说明齐全 | 锁最终提交与候选;实机安装和第三方核验;同页长期源码交付;发布操作单独记录 | diff --git a/docs/reviews/265-p1u-followup-latency-memory.md b/docs/reviews/265-p1u-followup-latency-memory.md index f0afe82..8acdd50 100644 --- a/docs/reviews/265-p1u-followup-latency-memory.md +++ b/docs/reviews/265-p1u-followup-latency-memory.md @@ -141,8 +141,8 @@ A/B 过程中的换包:旧包与新包各经 `install.sh` 覆盖安装一次 差出来的 13–17 MiB 是分配器已释放但系统尚未回收的页——内存紧张时被回收(数字变低),内存宽裕时留着(数字变高)。 它量的主要是系统状态,不适合做有固定门槛的门禁。首键时点最能说明问题:存活堆只有 5.1 MiB,这个口径却报 33 MiB。 -建议(需 ADR,未自行修改门槛或判定):把门禁的判定口径从「私有已提交」换成 `heap_live_mb`(本报告已让脚本输出), -门槛按实测约 20 MiB 留余量定(如 24 MiB,学习区开关不再需要两档),「私有已提交」与 footprint 降为参考项。 +**已落地(ADR-338,2026-11-13)**:门禁判定口径改为 `heap_live_mb`,门槛 24 MiB,不再按学习区分档;「私有已提交」与 footprint 降为参考项。 +用 `ANALYZE_ONLY=1` 对本报告的 5 组快照(共 23 轮)重新判定:全部 PASS,稳态存活堆 20.1–21.2 MiB。 结束状态:已装回新包 `f38e4e1a…`(`3e428b7`),宿主跑新二进制,输入源为萤火虫。证据:`50-mem-reboot-*.txt`、`51-mem-reboot-*.txt`、`memory-reboot-new/`、`memory-reboot-old/`。 diff --git "a/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" "b/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" index befd526..e426acd 100644 --- "a/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" +++ "b/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" @@ -3432,3 +3432,31 @@ `make verify`、离线回归、预览冒烟、`build.sh --strict` 全绿。**两项修复都待换包后实机确认。** - **状态**:已采纳(2026-11-12)。 - **关联**:ADR-325、ADR-331、ADR-335、报告 264/265。 + +### ADR-338:常驻内存门禁改以「存活堆」判定,门槛 24 MiB + +- **背景**:`memory-gate.sh` 此前以「私有已提交」(vmmap 的 Writable regions resident + swapped_out)判定,门槛 30 MiB、学习区开启时 34 MiB + (ADR-263/292/312)。报告 265 的三轮实测表明这个口径不可用: + - 同一份包,swap 用满时稳态 31–34 MiB,重启后 swap 为 0 时 35–37.6 MiB——**随系统内存压力反向变化**; + - 已发布的 1.2.1 在干净系统上 5 轮全部超过 34 MiB; + - 首键时点存活堆只有 5.1 MiB,该口径却报 33 MiB。多出的是分配器已释放、系统尚未回收的页。 + - 而存活堆在全部条件下稳定:新旧包、重启前后共 23 轮,稳态 20.1–21.2 MiB。 +- **决策**: + 1. 判定口径改为**稳态时点的存活堆**:`vmmap -summary` 的 MALLOC ZONE 表里 `DefaultMallocZone` 的已分配字节(`heap_live_mb`)。 + 2. 门槛 **24 MiB**,**不再按学习区开关分档**(实测约 20–21 MiB,余量约 3 MiB / 15%)。`LIMIT_MB` 仍可由调用方覆盖。 + 3. 「私有已提交」与 physical footprint 继续采样、写入 JSON,但**只作参考**,不参与判定。 + 4. 读不到存活堆(vmmap 输出格式变化)按 **FAIL** 处理,不退回旧口径。 + 5. 新增 `ANALYZE_ONLY=1`:只对已有快照目录重新判定,不重启宿主、不发键——用于复核旧证据与验证脚本改动。 +- **取代关系**:本条取代 ADR-263/292 的判定口径与 ADR-312 的两档门槛;那三条的测量方法与历史数字保留作背景,不改写。 + 对用户的承诺由「常驻内存 ≤30 MB」改述为「输入法自身持有的数据约 20 MiB,门禁 24 MiB」; + 系统「活动监视器」里看到的数字含共享库与可回收页,会更高,且随系统状态变化。 +- **理由**:门禁要回答的是「这次改动有没有让输入法多占内存」,只有不随系统状态漂移的量才能设固定门槛。 + 存活堆直接对应码表、拼音表、用户词库与候选对象的大小,新增数据或泄漏都会如实反映。 +- **边界 / 已知限制**: + - 存活堆不含映射文件与代码段;若将来改用 mmap 加载词典,需要把映射的常驻页另算进门禁。 + - 学习区**关闭**时的存活堆没有单独实测(用户配置为开启);预期不高于开启时,门槛同为 24 MiB。 + - 只统计 `DefaultMallocZone`;宿主若出现大额的其它 zone 分配不会被计入(当前其它 zone 合计不足 1 MiB)。 +- **验收**:用 `ANALYZE_ONLY=1` 对报告 265 的 5 组快照(新旧包 × 重启前后,共 23 轮)重新判定,全部 PASS,稳态存活堆 20.1–21.2 MiB; + `test-acceptance-tooling.py`、`test-probe-cleanup.py` 与离线回归全绿。 +- **状态**:已采纳(2026-11-13,用户指示「先定内存口径」)。 +- **关联**:ADR-263、ADR-292、ADR-312、报告 198/209/256/265。 diff --git "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" index 0a70f09..22c5dfe 100644 --- "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" +++ "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" @@ -77,7 +77,7 @@ > U2-5 分阶段解锁与跟打(要定口径与文本来源)——三项都需要你先拍板,不是能直接开工的活。 > **内存结论(报告 265 §4.3,重启后重测)**:新旧包同条件各 5 轮完全一致,**本批无内存回归**;但门禁口径失效——1.2.1 发布版在干净系统上同样全超 34 MiB,存活堆只有约 20 MiB。 -> 待裁定:判定口径换成存活堆 `heap_live_mb`(需 ADR)。此前关于「贴线」「首键 +4.6」「稳态 +0.5」的说法以本条为准。 +> **已定(ADR-338)**:门禁改以存活堆 `heap_live_mb` 判定、门槛 24 MiB;用旧快照复核 23 轮全部 PASS(20.1–21.2 MiB)。此前关于「贴线」「首键 +4.6」「稳态 +0.5」的说法以本条为准。 > > **实机反馈(2026-11-12,ADR-337)**:`12:30` → `12;30`(Shift 标点取字符有误,已修);拼音上屏后五笔码提示不出现(已加固并加诊断,**根因未定位、未确认修好**)。 > 用户已确认练习「字根」「词组」题型正常;内存门槛定为「重启电脑后重测再定」;DMG 完成页待出 DMG 后确认。 diff --git a/scripts/acceptance-evidence.py b/scripts/acceptance-evidence.py index 3f1f159..5f32402 100755 --- a/scripts/acceptance-evidence.py +++ b/scripts/acceptance-evidence.py @@ -28,7 +28,7 @@ "hotkeys": ("快捷键", "热键", "shift 切", "⌥p", "⌥f", "⌥t", "⌥m"), "clients": ("客户端", "client matrix", "文本编辑/safari", "safari"), "quality": ("质量样本", "quality-samples"), - "memory": ("内存门禁", "memory-gate", "adr-263", "adr-292"), + "memory": ("内存门禁", "memory-gate", "adr-263", "adr-292", "adr-338"), "soak": ("久用", "soak"), "network": ("零外联", "外联", "tcp/udp", "0 条"), "voiceover": ("voiceover", "播报", "axannouncementrequested"), diff --git a/tools/macos-probe/memory-gate.sh b/tools/macos-probe/memory-gate.sh index 99ba170..59c0e4c 100755 --- a/tools/macos-probe/memory-gate.sh +++ b/tools/macos-probe/memory-gate.sh @@ -1,24 +1,23 @@ #!/bin/sh -# ADR-263/292「常驻内存 ≤30MB」口径复测:新启动宿主 → marker 先行 → 四个固定时点采样 → 至少 3 个独立轮次。 -# 判定:稳态时点的「私有已提交」(Writable regions resident + swapped_out)≤ LIMIT_MB —— 这是进程自己持有的量。 -# footprint 仍然采样并打印,但只作参考:它含共享只读库页,随 macOS 版本变化(报告209:同一份 0.1.13 与 0.1.14 -# 在 macOS 27 上 footprint 29–34M、私有 26–29M;在 macOS 13 上 footprint 20.6–20.9M),跨版本不可比。 -# 注意:这也不是探针 RSS 口径(报告198)。 -# 用法:memory-gate.sh [ROUNDS=3];输出 JSON 摘要 + 每轮 vmmap 全文到 OUT。 +# 常驻内存门禁(ADR-338):新启动宿主 → marker 先行 → 四个固定时点采样 → 至少 3 个独立轮次。 +# 判定:稳态时点的**存活堆**(vmmap 的 MALLOC ZONE 表里 DefaultMallocZone 的已分配字节)≤ LIMIT_MB(默认 24 MiB)。 +# 这是进程此刻真正持有的数据量,不随系统内存压力变化。 +# 「私有已提交」(Writable regions resident + swapped_out)与 footprint 仍然采样并打印,但**只作参考**: +# 它们含分配器已释放、系统尚未回收的页,swap 用满时偏低、swap 为空时偏高(报告265 §4.3:同一份包 31–34 对 35–37.6 MiB), +# footprint 还含共享只读库页、随 macOS 版本变化(报告209)。两者都不是探针 RSS 口径(报告198)。 +# 用法:ROUNDS=3 memory-gate.sh;输出 JSON 摘要 + 每轮 vmmap 全文到 OUT。 +# ANALYZE_ONLY=1 OUT=<已有目录> ROUNDS=<轮数> memory-gate.sh 只对已有快照重新判定,不重启宿主、不发键。 set -u PROBE="$(cd "$(dirname "$0")" && pwd)" BIN="$PROBE/bin" SOURCE="${SOURCE:-cn.jokerhub.inputmethod.Lampyris.default}" ROUNDS="${ROUNDS:-3}" -# ADR-312:默认配置(学习区关闭)守 30 MiB;学习区开启时放宽到 34 MiB(实测稳定差约 3 MiB,报告 256)。 -CALLER_LIMIT_MB="${LIMIT_MB:-}" -LIMIT_MB="${LIMIT_MB:-30}" +# ADR-338:存活堆实测约 20–21 MiB(学习区开启、新旧包一致),门槛 24 MiB,不再按学习区开关分档。 +LIMIT_MB="${LIMIT_MB:-24}" +ANALYZE_ONLY="${ANALYZE_ONLY:-0}" OUT="${OUT:-/tmp/lampyris-memory-gate-$(date -u +%Y%m%dT%H%M%SZ)}" SUPPORT="${SUPPORT:-$HOME/Library/Application Support/Lampyris}" APP="$HOME/Library/Input Methods/Lampyris.app" -if [ -z "$CALLER_LIMIT_MB" ] && grep -qE '^show_wubi_learning[[:space:]]*=[[:space:]]*true' "$SUPPORT/config.toml" 2>/dev/null; then - LIMIT_MB=34 -fi mkdir -p "$OUT" host_pid() { pgrep -x Lampyris | head -1; } @@ -38,6 +37,7 @@ restart_host() { type_keys() { "$PROBE/type-session.sh" "$1" --settle 2 >/dev/null 2>&1; } ROUND=1 +[ "$ANALYZE_ONLY" = "1" ] && ROUND=$((ROUNDS + 1)) while [ "$ROUND" -le "$ROUNDS" ]; do restart_host sleep 2 @@ -52,7 +52,7 @@ while [ "$ROUND" -le "$ROUNDS" ]; do { date -u +%FT%TZ; uptime; vm_stat | head -1; sysctl -n vm.swapusage; } > "$OUT/round-$ROUND-env.txt" ROUND=$((ROUND + 1)) done -"$BIN/tissrc" select com.apple.keylayout.ABC >/dev/null 2>&1 +[ "$ANALYZE_ONLY" = "1" ] || "$BIN/tissrc" select com.apple.keylayout.ABC >/dev/null 2>&1 python3 - "$OUT" "$ROUNDS" "$LIMIT_MB" "$(shasum -a 256 "$APP/Contents/MacOS/Lampyris" | awk '{print $1}')" \ "$(shasum -a 256 "$SUPPORT/config.toml" 2>/dev/null | awk '{print $1}')" <<'PY' @@ -76,8 +76,7 @@ def parse(path): if not footprint or not writable: return None resident, swapped = to_mb(writable.group(1)), to_mb(writable.group(2)) - # 报告265:「私有已提交」含分配器已释放但未归还的页,受采样时机影响很大; - # 同时记下 MALLOC ZONE 表里的存活分配(字节与个数),用来区分「数据真变多了」与「分配器残留」。只作参考,不参与判定。 + # ADR-338:判定用 MALLOC ZONE 表里的存活分配(字节);「私有已提交」含分配器已释放但未归还的页,只作参考。 zone = re.search(r"^DefaultMallocZone\S*\s+\S+\s+\S+\s+\S+\s+\S+\s+(\d+)\s+([0-9.]+[KMG]?)", text, re.M) return { "physical_footprint_mb": round(to_mb(footprint.group(1)), 1), @@ -107,12 +106,16 @@ steady = [point for point in POINTS if point in ("six-words", "idle-8s")] for row in results.values(): for point in steady: sample = row.get(point) - if not sample or sample["private_committed_mb"] > limit: + live = sample.get("heap_live_mb") if sample else None + # 读不到存活堆(vmmap 输出格式变了)按失败处理,不退回旧口径——宁可红也不要用一个不可信的数放行。 + if live is None or live > limit: failed = True summary = { "rounds": rounds, "limit_mb": limit, + "metric": "heap_live_mb", "footprint_is_informational": True, + "private_committed_is_informational": True, "steady_points": steady, "exec_sha": exec_sha, "config_sha": config_sha,