From f053112e44abd50ee37825181d768d99cfec494c 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: Fri, 24 Jul 2026 17:07:13 +0800 Subject: [PATCH] =?UTF-8?q?perf(gsub-reachable):=20type1=20fmt1=20+=20type?= =?UTF-8?q?3=20=E6=9D=A1=E4=BB=B6=E5=8F=8D=E8=BD=AC=E9=81=8D=E5=8E=86?= =?UTF-8?q?=E6=96=B9=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 延续 9d6e982(type1 fmt2 反转)的思路,对 SingleSubst format1 与 AlternateSubst(type3)同样反转遍历方向:遍历 reachable 二分查 covGids (按 gid 升序)得 index,替代遍历 covGids 逐个查 reachable。 初夏明朝纯标点实测 fmt1 covLen 405、type3 covLen 987(命中率 2.3%), 均属「展开全量命中极少」模式,反转后遍历量从 covGids.length 降到 reachable.size。 关键:加条件 covGids.length > reachable.size 才反转。无条件反转在 霞鹜/FiraCode 上负优化(covGids 短,FiraCode type1 avg 5.5,二分开销 > Set.has 遍历)。条件反转使短 covGids 走原 Set.has 路径。 type4(Ligature)不反转:firstHit 判定反转后遍历 reachable(53)× 18 calls = 954 次二分 > 原 684 次 Set.has,必负。 A/B vs 9d6e982(3000 次 p50,reachable sha 全一致): - 初夏标点 102→92μs(-10%) - 初夏8字 54→51μs(-5%) - 霞鹜标点 12→10μs(-12%,无条件版 +8% 已修正) - 霞鹜8字 6.8→6.4μs(-7%) - FiraCode 标点 70→69μs(-1%,无条件版 +2% 已修正) 相比最初 baseline(fee9e49 前)初夏标点 129→92μs(-29%)。基准测试 SSIM 全不变。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/gsub-reachable.ts | 77 ++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/backend/font_util/gsub-reachable.ts b/backend/font_util/gsub-reachable.ts index 044d6b1..f334a0a 100644 --- a/backend/font_util/gsub-reachable.ts +++ b/backend/font_util/gsub-reachable.ts @@ -271,25 +271,46 @@ function collectSubtableTargets( const covGids = readCoverageGids(r, covOff, covCache); if (format === 1) { const delta = r.i16(off + 4); - for (const g of covGids) { - if (inSubset(g)) targets.push((g + delta) & 0xffff); + /** 条件反转:仅当 covGids 明显多于 reachable 时反转遍历方向(遍历小集合二分查大集合)。 + * covGids 短(FiraCode type1 avg 5.5)时原 Set.has 路径更快(二分开销 > 遍历)。 */ + if (covGids.length > reachable.size) { + const lim = covGids.length; + for (const g of reachable) { + let lo = 0, hi = lim; + while (lo < hi) { + const mid = (lo + hi) >> 1; + const mg = covGids[mid]; + if (mg < g) lo = mid + 1; + else if (mg > g) hi = mid; + else { targets.push((g + delta) & 0xffff); break; } + } + } + } else { + for (const g of covGids) { + if (inSubset(g)) targets.push((g + delta) & 0xffff); + } } } else if (format === 2) { - /** 反转遍历方向:coverage(avg 103 gid,命中率 <4%)远大于 reachable(初夏标点 53)。 - * 遍历 covGids 查 reachable 是 9180 次 Set.has/round;改为遍历 reachable 二分查 covGids 得 index。 + /** 条件反转遍历方向:coverage 命中率低(初夏 fmt2 covLen 9180/round,reachable 53,命中率<4%)。 + * 仅当 covGids 明显多于 reachable 时遍历 reachable 二分查 covGids 得 index;否则原 Set.has 路径。 * covGids 按 gid 升序(fmt1 list / fmt2 range 展开),可二分。 */ const count = r.u16(off + 4); const lim = covGids.length < count ? covGids.length : count; const gidArrBase = off + 6; - for (const g of reachable) { - /** 二分 covGids[0..lim) 找 g 的 index */ - let lo = 0, hi = lim; - while (lo < hi) { - const mid = (lo + hi) >> 1; - const mg = covGids[mid]; - if (mg < g) lo = mid + 1; - else if (mg > g) hi = mid; - else { targets.push(r.u16(gidArrBase + mid * 2)); break; } + if (lim > reachable.size) { + for (const g of reachable) { + let lo = 0, hi = lim; + while (lo < hi) { + const mid = (lo + hi) >> 1; + const mg = covGids[mid]; + if (mg < g) lo = mid + 1; + else if (mg > g) hi = mid; + else { targets.push(r.u16(gidArrBase + mid * 2)); break; } + } + } + } else { + for (let i = 0; i < lim; i++) { + if (inSubset(covGids[i])) targets.push(r.u16(gidArrBase + i * 2)); } } } @@ -304,14 +325,34 @@ function collectSubtableTargets( for (let k = 0; k < glyphCount; k++) targets.push(r.u16(seqOff + 2 + k * 2)); } } else if (type === LT_ALTERNATE) { + /** 条件反转:初夏 type3 covLen 987/3 calls(avg 329),命中率 2.3%;covGids 短时走原路径。 */ const covOff = off + r.u16(off + 2); const altCount = r.u16(off + 4); const covGids = readCoverageGids(r, covOff, covCache); - for (let i = 0; i < covGids.length && i < altCount; i++) { - if (!inSubset(covGids[i])) continue; - const altOff = off + r.u16(off + 6 + i * 2); - const cnt = r.u16(altOff); - for (let k = 0; k < cnt; k++) targets.push(r.u16(altOff + 2 + k * 2)); + const lim = covGids.length < altCount ? covGids.length : altCount; + if (covGids.length > reachable.size) { + for (const g of reachable) { + let lo = 0, hi = lim; + while (lo < hi) { + const mid = (lo + hi) >> 1; + const mg = covGids[mid]; + if (mg < g) lo = mid + 1; + else if (mg > g) hi = mid; + else { + const altOff = off + r.u16(off + 6 + mid * 2); + const cnt = r.u16(altOff); + for (let k = 0; k < cnt; k++) targets.push(r.u16(altOff + 2 + k * 2)); + break; + } + } + } + } else { + for (let i = 0; i < lim; i++) { + if (!inSubset(covGids[i])) continue; + const altOff = off + r.u16(off + 6 + i * 2); + const cnt = r.u16(altOff); + for (let k = 0; k < cnt; k++) targets.push(r.u16(altOff + 2 + k * 2)); + } } } else if (type === LT_LIGATURE) { /** LigatureSubst: 全部 component 在子集 → target gid */