perf(gsub-reachable): collectReachableGsubTargets 单 Set inSubset + 复用临时 Set

初夏明朝纯标点(最慢 CJK 用例 4ms)profile 显示 collectReachableGsubTargets 占 19.2% +
collectChainRefs 7.8%(合计 27%,超过 subsetGSUB 本身 6.4% 的 3 倍)。单次 1.22ms。

优化:
1. inSubset 闭包原为 seedGids.has(g) || reachable.has(g) 两次 Set 查询(热路径每 subtable
   多次调用)。改为 reachable 初始即含 seedGids,inSubset 只查 reachable 一个 Set。
   返回的 reachable 可能含 seed gid——调用方注入 extraSubsetGids 后 Font.create 去重,无害。
2. 每个 type6 subtable 原 new Set() × 2(refs + ctxGids),多轮迭代 × 51 lookup 累积 GC 压力。
   改为循环外复用 + clear。

实测初夏明朝 1.22→1.02ms(-16%),FiraCode 0.75→0.69ms(-8%)。
基准测试 29 用例 SSIM/ink/输出字节全部无回归(初夏纯标点 4.4→3.6ms)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-23 19:15:50 +08:00
parent bbc3d8b88a
commit 6ea19550a1

View File

@ -116,11 +116,19 @@ export function collectReachableGsubTargets(
* type6 ChainedContext input/backtrack/lookahead gid
* lookup target chainRefs type1/2/3/4 target
* type1/2/3/4 coverage gid target
* pyftsubset calt context glyph */
const reachable = new Set<number>();
* pyftsubset calt context glyph
*
* Set inSubsetreachable seedGidsinSubset reachable Set
* seedGids.has || reachable.has Set subtable
* reachable seed gid extraSubsetGids Font.create */
const reachable = new Set<number>(seedGids);
let changed = true;
/** 当前「在子集内」的 gid 集合 = seed reachable */
const inSubset = (gid: number) => seedGids.has(gid) || reachable.has(gid);
/** 当前「在子集内」= reachable已含 seed */
const inSubset = (gid: number) => reachable.has(gid);
/** 复用临时 Set避免每个 type6 subtable 两次 new Set 的 GC 压力(初夏明朝 51 lookup 多轮迭代) */
const refsReuse: Set<number> = new Set<number>();
const ctxGidsReuse: Set<number> = new Set<number>();
while (changed) {
changed = false;
@ -129,13 +137,13 @@ export function collectReachableGsubTargets(
for (const subAbs of lk.subtableAbsOffs) {
if (lk.effectiveType === LT_CHAIN) {
/** type6收集可触发规则引用的 lookup index 与所需 context gid */
const refs = new Set<number>();
const ctxGids = new Set<number>();
collectChainRefs(r, subAbs, refs, ctxGids, inSubset, covCache);
for (const g of ctxGids) {
refsReuse.clear();
ctxGidsReuse.clear();
collectChainRefs(r, subAbs, refsReuse, ctxGidsReuse, inSubset, covCache);
for (const g of ctxGidsReuse) {
if (!reachable.has(g)) { reachable.add(g); changed = true; }
}
for (const li of refs) {
for (const li of refsReuse) {
const refLk = lookups[li];
if (!refLk) continue;
for (const refSub of refLk.subtableAbsOffs) {