perf(gsub-reachable): format3 闭包消除 + 固定点迭代稳定性记忆

collectChainRefs 在固定点迭代中高频调用(初夏纯标点 280 个 format3 subtable ×
多轮),每次创建 readCovGidsChecked 闭包有分配开销。

优化315: format3 分支消除闭包,改显式三段循环手动推进 p,保留 triggerable 短路。
优化316: collectChainRefs 返回稳定标志(format2 总稳定 / format3 triggerable=true
稳定),主循环用 settledChain 记忆已稳定 subtable 偏移,后续轮直接跳过。

稳定测量(N=300 预热):
  FiraCode       3.757 → 3.529ms (-6%)
  初夏-纯标点     1.847 → 1.548ms (-16%)
  初夏-汉字标点   1.503 → 1.260ms (-16%)
reach 阶段(单测): FiraCode 0.449→0.318ms, 初夏 0.544→0.315ms
无 GSUB 字体(令东/思源)无影响。所有用例 reach 输出与黄金基准逐字节一致。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-24 09:43:55 +08:00
parent a1c7312722
commit 56e8b6db4e

View File

@ -128,6 +128,14 @@ export function collectReachableGsubTargets(
/** 复用临时 Set避免每个 type6 subtable 两次 new Set 的 GC 压力(初夏明朝 51 lookup 多轮迭代) */
const refsReuse: Set<number> = new Set<number>();
const ctxGidsReuse: Set<number> = new Set<number>();
/**
* 316
* collectChainRefs format2 format3 triggerable=true true subtable
* refs/contextGids reachable settledChain
* 280 format3 2 triggerable=false
* reachable 使 coverage gid
*/
const settledChain: Set<number> = new Set<number>();
while (changed) {
changed = false;
@ -135,10 +143,11 @@ export function collectReachableGsubTargets(
const lk = lookups[i];
for (const subAbs of lk.subtableAbsOffs) {
if (lk.effectiveType === LT_CHAIN) {
if (settledChain.has(subAbs)) continue;
/** type6收集可触发规则引用的 lookup index 与所需 context gid */
refsReuse.clear();
ctxGidsReuse.clear();
collectChainRefs(r, subAbs, refsReuse, ctxGidsReuse, inSubset, covCache);
const stable = collectChainRefs(r, subAbs, refsReuse, ctxGidsReuse, inSubset, covCache);
for (const g of ctxGidsReuse) {
if (!reachable.has(g)) { reachable.add(g); changed = true; }
}
@ -152,6 +161,7 @@ export function collectReachableGsubTargets(
}
}
}
if (stable) settledChain.add(subAbs);
} else {
const newTargets = collectSubtableTargets(r, subAbs, lk.effectiveType, inSubset, covCache);
for (const g of newTargets) {
@ -264,7 +274,7 @@ function collectChainRefs(
contextGids: Set<number>,
inSubset: (gid: number) => boolean,
covCache: CoverageCache,
): void {
): boolean {
const format = r.u16(off);
if (format === 1) {
/** format1: coverage(gid) + SubRuleSet 数组,按 coverage gid 索引 */
@ -284,6 +294,9 @@ function collectChainRefs(
collectChainRuleRefs(r, ruleOff, refs, contextGids, inSubset, true);
}
}
/** format1 coverage gid SubRuleSet reachable
* falseFiraCode 90 format1 */
return false;
} else if (format === 2) {
/** format2: ClassDef + input class SubClassSet
* class index backtrack/input/lookahead class index gid
@ -299,42 +312,45 @@ function collectChainRefs(
collectChainRuleRefs(r, ruleOff, refs, contextGids, inSubset, false);
}
}
/** format2 保守收集全部 ClassDef gid不判 inSubset首轮即收全结果不随 reachable 扩展变化。 */
return true;
} else if (format === 3) {
/** format3: coverage + SubstLookupRecords
* coverage backtrack/input/lookahead gid
* triggerable false coverage
* readCovGids + allGids collectChainRefs
* 840 /calltriggerable gid add contextGids allGids */
*
* 315 readCovGidsChecked collectChainRefs
* 280 format3 subtable × 2 = 560 /call
* p triggerable coverage
* gid coverage refs */
let p = off + 2;
let triggerable = true;
const readCovGidsChecked = (cnt: number): void => {
/** 三段 coveragebacktrack/input/lookahead逻辑相同读 count逐 coverage 校验 gid 全在子集 */
for (let seg = 0; seg < 3 && triggerable; seg++) {
const cnt = r.u16(p);
p += 2;
for (let k = 0; k < cnt; k++) {
const covGids = readCoverageGids(r, off + r.u16(p + k * 2), covCache);
for (const g of covGids) {
if (!inSubset(g)) {
triggerable = false;
return;
break;
}
contextGids.add(g);
}
}
p += cnt * 2;
};
const backtrackCount = r.u16(p); p += 2;
readCovGidsChecked(backtrackCount);
if (triggerable) {
const inputCount = r.u16(p); p += 2;
readCovGidsChecked(inputCount);
}
if (triggerable) {
const lookaheadCount = r.u16(p); p += 2;
readCovGidsChecked(lookaheadCount);
}
if (triggerable) {
const substCount = r.u16(p);
for (let k = 0; k < substCount; k++) refs.add(r.u16(p + 2 + k * 4 + 2));
/** triggerable=true coverage gid reachablereachable
* refs/contextGids reachable */
return true;
}
/** triggerable=false某 coverage gid 不在 reachable后续 reachable 扩展可能使其进入 → 不稳定。 */
return false;
}
return false;
}
/**