From fb721a216b47fc012bffd8b2786bfaf941644cd6 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 18:08:49 +0800 Subject: [PATCH] =?UTF-8?q?perf(gpos):=20=E9=A2=84=E6=89=AB=E6=8F=8F=20cov?= =?UTF-8?q?erage=20=E6=9C=AA=E5=91=BD=E4=B8=AD=E7=9A=84=20subtable=20?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E5=86=99=E7=A9=BA=E8=B7=B3=E8=BF=87=20serial?= =?UTF-8?q?izeSinglePos/PairPos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit anyHit lookup 内逐 subtable 序列化时,subtableCoverageHits 预扫描已判定 coverage 是否含子集字形。初夏 96~98% SinglePos subtable coverage 完全不含 子集字形(hits[j]==false),原实现仍 readCoverageGids + 构建 kept(空) + emit 空 coverage 走完整 serializeSinglePos,纯浪费。 coverageHit=false 时直接 writeEmptyPosSubtable(与 lookup 全空折叠同性质 等价变换:空 coverage 浏览器查不到字形即跳过,valueFormat 0 与原值渲染等价), 跳过 readCoverageGids/kept/emit 全部开销。 A/B(min 微基准,checksum 因空 subtable valueFormat 0 替代原值变化,SSIM 全不变): - 初夏纯标点 subsetGPOS 0.110→0.084ms (-24%) - 初夏汉字标点 0.101→0.073ms (-28%) - FiraCode 0.002ms(无 GPOS 热点,符合预期) 全量基准 SSIM 全部不变(初夏纯标点 0.9920/汉字标点 1.0000/霞鹜 0.9987/FiraCode 0.9923 等固有值)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/gpos-subset.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/font_util/gpos-subset.ts b/backend/font_util/gpos-subset.ts index a516a4f..421abda 100644 --- a/backend/font_util/gpos-subset.ts +++ b/backend/font_util/gpos-subset.ts @@ -312,7 +312,7 @@ export function subsetGPOS( for (let j = 0; j < lk.subtableAbsOffs.length; j++) { subtableAbsPositions[j] = w.length; r.clearError(); - const ok = serializeSubtable(w, r, lk.subtableAbsOffs[j], lk.effectiveType, origToNew); + const ok = serializeSubtable(w, r, lk.subtableAbsOffs[j], lk.effectiveType, origToNew, hits[j]); /** 越界读取(异常表)也降级为保留原始 GPOS 字节(调用方安全降级) */ if (!ok || r.errorFlag) return null; w.writeInt16At(subtableSlotsStart + j * 2, subtableAbsPositions[j] - lookupStart); @@ -357,7 +357,16 @@ function serializeSubtable( subAbs: number, type: number, origToNew: Map, + /** 预扫描结果:该 subtable coverage 是否含任意子集字形。false 时 coverage 必空,直接写空 subtable 跳过全量解析 */ + coverageHit: boolean, ): boolean { + if (!coverageHit) { + /** coverage 无任何子集字形:serializeSinglePos/serializePairPos 必然产出空 kept → 空 coverage。 + * 空 coverage 浏览器查不到字形即跳过,与逐字段重序列化的空 subtable 渲染语义等价。 + * 直接写空 subtable 跳过 readCoverageGids/kept 构建/emit 全部开销(初夏 96% subtable 命中此路径)。 */ + writeEmptyPosSubtable(w, type); + return true; + } if (type === LT_SINGLE_POS) return serializeSinglePos(w, r, subAbs, origToNew); if (type === LT_PAIR_POS) return serializePairPos(w, r, subAbs, origToNew); return false;