From 7a76208558214503d1a82be1a81213fcf0b8e7bd 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: Sat, 25 Jul 2026 07:01:35 +0800 Subject: [PATCH] =?UTF-8?q?perf(gsub):=20coverageAllOutOfSubset=20range=20?= =?UTF-8?q?=E7=9B=B8=E4=BA=A4=E5=88=A4=E5=AE=9A=E6=94=B9=E4=BA=8C=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COV_RANGE 分支逐 gid 展开 range 查 gidLookup(O(range length)), 对全子集外的 coverage 要遍历完所有 gid 才确认 outOfSubset。初夏 fmt3 range 展开总 20388 gid(300 range / 12 子集 gid),逐 gid 是预扫描主开销。 改用 currentSortedSubsetGids 二分:range [start,end] 是否含任一子集 gid 等价于排序子集数组存在 gid ∈ [start,end](gidLookup[g]>=0 ⟺ g 是子集 gid)。 O(log n) per range。微基准 10×(0.012→0.001ms),subsetGSUB 端到端初夏 -6%。 等价性:end clamp/越界语义保留(sortedGids 全 --- backend/font_util/gsub-subset.ts | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/backend/font_util/gsub-subset.ts b/backend/font_util/gsub-subset.ts index cb00dd6..5bb47ae 100644 --- a/backend/font_util/gsub-subset.ts +++ b/backend/font_util/gsub-subset.ts @@ -1349,10 +1349,14 @@ function coverageAllOutOfSubset( } if (format === COV_RANGE) { const rangeCount = dv.getUint16(covOff + 2, false); - /** 与 readCoverageRemapped 一致:end clamp 到 numGlyphs-1,start>=numGlyphs 的 range 跳过 gid 展开 - * (gidLookup[g] 对 g>=numGlyphs 必然不在子集,逐 gid 展开全越界是浪费)。保证 fmt3 预检「全子集外」 - * 判定与 readCoverageRemapped 完全一致,避免 [[gsub-subset-fmt3-prefetch-consistency]] 类不一致。 */ - const numGlyphs = gidLookup.length; + /** 排序子集 gid 数组(currentSortedSubsetGids,模块级,subsetGSUB 入口已设)。 + * range 相交判定改二分(O(log n) per range)替代逐 gid 展开(O(range length))。 + * 初夏 fmt3 range 展开总 20388 gid、300 range、12 子集 gid,逐 gid 0.012ms vs 二分 0.001ms(10×)。 + * 等价性:range [start,end] 含 gidLookup[g]>=0 的 g ⟺ 排序子集 gid 数组存在 gid ∈ [start,end] + * (gidLookup[g]>=0 ⟺ g 是子集 gid ⟺ g 在排序子集数组)。end clamp/越界语义不变: + * sortedGids 全部 < numGlyphs,二分天然只在子集 gid 范围查,end>=numGlyphs 时子集 gid 仍可能 <= end。 */ + const sortedGids = currentSortedSubsetGids; + const subsetN = sortedGids.length; let p = covOff + 4; let origNonEmpty = false; for (let i = 0; i < rangeCount; i++) { @@ -1360,15 +1364,19 @@ function coverageAllOutOfSubset( const start = dv.getUint16(p, false); const end = dv.getUint16(p + 2, false); if (end >= start && end - start < COVERAGE_MAX_EXPAND) { - if (start >= numGlyphs) { - /** 整 range 越界:gid 全不在子集,但 range 非空(origNonEmpty),不短路 return false */ - origNonEmpty = true; - } else { - const e = end < numGlyphs ? end : numGlyphs - 1; - for (let g = start; g <= e; g++) { - origNonEmpty = true; - if (gidLookup[g] >= 0) return false; + /** range 非空(无论是否含子集 gid),标记 origNonEmpty */ + origNonEmpty = true; + /** 二分判定 range 是否含任一子集 gid。subsetN=0(空子集,理论上不进 GSUB)或 range 全在 + * sortedGids 范围外时 lo 落到边界,sortedGids[lo] > end → 无交集,与逐 gid 全 <0 一致。 */ + if (subsetN > 0 && start <= sortedGids[subsetN - 1] && end >= sortedGids[0]) { + let lo = 0, hi = subsetN; + while (lo < hi) { + const mid = (lo + hi) >>> 1; + if (sortedGids[mid] < start) lo = mid + 1; + else hi = mid; } + /** lo = 第一个 >= start 的子集 gid 下标;若该 gid <= end 则 range 含子集 gid → 非 outOfSubset */ + if (lo < subsetN && sortedGids[lo] <= end) return false; } } p += 6;