perf(gsub-reachable): collectChainRuleRefs 消除 3 个临时数组分配

ChainSubRule 解析原先读 backtrack/input/lookahead 到 3 个 number[] 再两遍遍历
(先 inSubset 校验再 contextGids.add),format1 每 rule 分配 3 数组
(FiraCode 424 次/call × 3 = 1272 次数组分配 + GC 压力)。

改为两遍扫描同一段字节,不分配中间数组:
- 第一遍仅 inSubset 校验全部 gid(遇子集外即 return,放弃规则)
- 通过校验后再第二遍收集 context gid + 读 subst records

reachable 输出字节级一致(FiraCode 132 / 初夏 63 均通过 cmp),
FiraCode reach 0.368→0.343ms(-6.8%),30 项基准 SSIM 零变化。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-24 10:30:05 +08:00
parent dd4f7ba55a
commit 6d897f1987

View File

@ -358,6 +358,11 @@ function collectChainRefs(
* @param isGidFormat true=format1 gid inSubset gid
* false=format2 class index
* context gid refsformat1format2 class index
*
* backtrack/input/lookahead 3
* format1 rule 3 number[]FiraCode 424 /call × 3 = 1272
* inSubset gid gid return
* context gid subst recordsformat1
*/
function collectChainRuleRefs(
r: OTReader,
@ -370,27 +375,37 @@ function collectChainRuleRefs(
/** format1/2 rule: backtrackCount + backtrack[] + inputCount + input[] + lookaheadCount + lookahead[] + substCount + substRecords[] */
let p = ruleOff;
const backtrackCount = r.u16(p); p += 2;
const backRaw: number[] = [];
for (let k = 0; k < backtrackCount; k++) backRaw.push(r.u16(p + k * 2));
p += backtrackCount * 2;
const inputCount = r.u16(p); p += 2;
const inputRaw: number[] = [];
for (let k = 0; k < inputCount - 1; k++) inputRaw.push(r.u16(p + k * 2));
p += (inputCount - 1) * 2;
const lookaheadCount = r.u16(p); p += 2;
const lookRaw: number[] = [];
for (let k = 0; k < lookaheadCount; k++) lookRaw.push(r.u16(p + k * 2));
p += lookaheadCount * 2;
const substCount = r.u16(p); p += 2;
const backtrackEnd = p + backtrackCount * 2;
const inputCount = r.u16(backtrackEnd); p = backtrackEnd + 2;
/** input 数组长度 = inputCount - 1第一分量在 coveragerule 内只存后续分量) */
const inputLen = inputCount > 0 ? inputCount - 1 : 0;
const inputEnd = p + inputLen * 2;
const lookaheadCount = r.u16(inputEnd); p = inputEnd + 2;
const lookaheadEnd = p + lookaheadCount * 2;
const substCount = r.u16(lookaheadEnd); p = lookaheadEnd + 2;
/** format1全部 context gid 在子集才触发format2class index 始终「可触发」(保守) */
/** format1全部 context gid 在子集才触发先校验再收集避免中间数组format2class index 始终「可触发」(保守) */
if (isGidFormat) {
for (const arr of [backRaw, inputRaw, lookRaw]) {
for (const g of arr) {
if (!inSubset(g)) return; /** 含子集外 gid规则不触发 */
}
for (const g of arr) contextGids.add(g);
/** 第一遍:校验 backtrack + input + lookahead 全部 gid 在子集(遇子集外即放弃规则) */
let q = ruleOff + 2;
for (let k = 0; k < backtrackCount; k++) {
if (!inSubset(r.u16(q + k * 2))) return;
}
q += backtrackCount * 2 + 2;
for (let k = 0; k < inputLen; k++) {
if (!inSubset(r.u16(q + k * 2))) return;
}
q += inputLen * 2 + 2;
for (let k = 0; k < lookaheadCount; k++) {
if (!inSubset(r.u16(q + k * 2))) return;
}
/** 第二遍:全部在子集,收集 context gid */
q = ruleOff + 2;
for (let k = 0; k < backtrackCount; k++) contextGids.add(r.u16(q + k * 2));
q += backtrackCount * 2 + 2;
for (let k = 0; k < inputLen; k++) contextGids.add(r.u16(q + k * 2));
q += inputLen * 2 + 2;
for (let k = 0; k < lookaheadCount; k++) contextGids.add(r.u16(q + k * 2));
}
for (let k = 0; k < substCount; k++) {
/** SubstLookupRecord: sequenceIndex(2) + lookupListIndex(2) */