cnt-dict-tools bench data/cnt.dict data/lm.cntl 20(18~20 个样例 × 20 轮,
本机实测 2026-08-27,trace 关闭):
| 平均 | 中位 | 90% | 99% | |
|---|---|---|---|---|
| 热(词键缓存命中,= 连续打字的第 2 键起) | 83µs | 81µs | 119µs | 190µs |
| 冷(首次进入该输入上下文) | 169µs | 150µs | — | — |
测量环境:Intel Core Ultra 5 336H / governor
powersave/cargo build --release(trace 关闭;开启 trace 约 +4%)。绝对值只在同一台机器同一状态下有意义:同一份二进制在降频状态下实测会慢 2~3 倍(74µs → 200µs+)。改动前后要比性能,用
git worktree建旧版本 交替跑两个二进制看相对差值,别跨时间点比绝对值(见 AGENTS.md)。
都是先埋 span 看数据再改(见 AGENTS.md 的可观测性约定):
- 词键缓存跨按键:打字是增量的,敲
womenzaigongzuo的每个字母都会把整个 前缀重新解码,同一批词键(wo/women/zai/gongzuo…)反复查词库。缓存词键 → 候选词表(含 LM 下标、句首基础分、用户计数) learn()精确失效:只丢这次动过的键,不整体清空 —— 否则每次上屏后的下一句 都退回冷路径(实测每键 +26%)。真实打字的增量特性也在这里体现:追加一个字母只 新增「以新末尾结束」的少数词键,前面位置的键全部命中,所以连续打字几乎全是热的- 每个位置的词键只枚举一次:词键只取决于「位置 + 音节格」,与假设无关;
此前每条假设都重做字符串拼接与词库前缀查询,
BEAM=8就是 8 倍冗余 - 假设是
Copy的小结构,路径存在 arena:此前每条假设持有Vec<(Arc, Arc)>, 每次展开都要克隆整个 Vec(一次堆分配 + 全部引用计数),而每次解码有约 500 次展开 - bigram 按「行」查:同一前词的后继在 bigram 表里是连续区间。存活假设 (≤ BEAM 条)先定位一次行,其所有后继在行内二分——把「几十次跨 60MB mmap 的 随机二分」压成「一次定位 + 行内小范围二分」。注意行定位本身是两次全表二分, 放到「每次展开」里会反而更慢(实测 +50%)
- 排序键先算好再排:
ranked_words的比较器里曾调user_count,每次比较都要 抢用户库的锁 + 两次哈希查找,20 个候选就是上百次加锁 - 词库词零拷贝:
ranked_words返回Cow,词库词直接借 mmap,少一次String中转
本轮优化(词键缓存等上述设计)用「旧版/新版交替各跑 3 次」验证: 旧版 714~760µs、新版(热)~200µs —— 同一降频状态下的 3.5x 提升。
语音链路实测见 docs/voice-input.md(bench:平均 88~92ms,RTF 0.016;
ctc_beam 去掉多余 softmax 归一化后 20.1ms → 4.9ms)。