From 6ea19550a1df11ad414cf6407fbc311c75c8d01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=AE=E7=94=9F=EF=BC=88=E5=AD=90=E8=99=9A=EF=BC=89?= <2234839456@qq.com> Date: Thu, 23 Jul 2026 19:15:50 +0800 Subject: [PATCH] =?UTF-8?q?perf(gsub-reachable):=20collectReachableGsubTar?= =?UTF-8?q?gets=20=E5=8D=95=20Set=20inSubset=20+=20=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=20Set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初夏明朝纯标点(最慢 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) --- backend/font_util/gsub-reachable.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/font_util/gsub-reachable.ts b/backend/font_util/gsub-reachable.ts index e3fe390..6df31f8 100644 --- a/backend/font_util/gsub-reachable.ts +++ b/backend/font_util/gsub-reachable.ts @@ -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(); + * 这模拟了 pyftsubset 的「实际触发路径」分析,避免保留无关 calt 规则的海量 context glyph。 + * + * 优化(单 Set inSubset):reachable 初始即含 seedGids,inSubset 只查 reachable 一个 Set + * (原为 seedGids.has || reachable.has 两次 Set 查询,热路径每 subtable 多次调用)。 + * 返回的 reachable 可能含 seed gid——调用方注入 extraSubsetGids 后 Font.create 会去重,无害。 */ + const reachable = new Set(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 = new Set(); + const ctxGidsReuse: Set = new Set(); 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(); - const ctxGids = new Set(); - 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) {