perf(gsub): fmt3 ChainContext 预检跳过 + 修复 covCache 共享语义污染

预检优化(type1-4 + fmt1 + fmt3 全覆盖):
- 重构 isPrimaryCoverageOutOfSubset → isSubtableSkipableByCoverage
  + 抽出 coverageAllOutOfSubset 辅助函数(内联遍历,不分配数组、不碰缓存)
- 新增 ChainContext format3 预检:遍历 back/input/look 三组 coverage,
  任一组「原非空且全子集外」即判定 subtable 失效,跳过深度解析
- FiraCode 403 lookup 中 ~330 个可预检跳过,子集化 23ms→16ms(-31%)

covCache 共享语义污染修复(fmt3 预检能安全启用的前提):
- CoverageCacheEntry 的 gids(原始 gid,fmt1 按 index 配对)与
  remapped(newGids,fmt3 序列化)两种语义原混写同一字段:
  旧 readCoverageRemapped 把 newGids 写进 gids,污染后续 fmt1 的
  readCoverageGids(拿到 newGids 当原始 gid 二次重映射,连字规则错位)
- 修复:readCoverageRemapped 只写 remapped,gids 留单例占位 EMPTY_GIDS;
  readCoverageGids 见占位即重新计算并 mergeGidsEntry(保留已有 remapped)
- 修复后 fmt3 预检开关下字节完全等价(8724==8724),SSIM 全程无回归

验证:pnpx tsx 基准测试.test.ts 全部 SSIM 无回归
(FiraCode=0.9923, otf-思源黑体=0.9310, 初夏明朝-纯标点=0.9920 不变)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-23 17:05:46 +08:00
parent 25da274c3a
commit 5da4cd5b06

View File

@ -65,6 +65,11 @@ interface CoverageCacheEntry {
}
type CoverageCache = Map<number, CoverageCacheEntry>;
/** readCoverageRemapped gids
* readCoverageGids entry gids miss
* set */
const EMPTY_GIDS: number[] = [];
/**
* gid gid
* origToNew Map<number,number> .get() coverage gid
@ -81,13 +86,16 @@ type GidLookup = number[];
function readCoverageGids(r: Reader, off: number, cache?: CoverageCache): number[] {
if (cache) {
const hit = cache.get(off);
if (hit !== undefined) return hit.gids;
/** hit.gids === EMPTY_GIDS entry readCoverageRemapped remapped/newGids
* gids gid miss gids
* newGids gid covCache Bug */
if (hit !== undefined && hit.gids !== EMPTY_GIDS) return hit.gids;
}
const dv = r.dv;
const len = dv.byteLength;
/** coverage 偏移合法性兜底:越界则按错误处理(返回空,调用方降级) */
if (off < 0 || off + 4 > len) {
if (cache) cache.set(off, { gids: [], remapped: null, outOfSubset: false });
if (cache) mergeGidsEntry(cache, off, []);
return [];
}
const format = dv.getUint16(off, false);
@ -96,7 +104,7 @@ function readCoverageGids(r: Reader, off: number, cache?: CoverageCache): number
const count = dv.getUint16(off + 2, false);
const base = off + 4;
if (base + count * 2 > len) {
if (cache) cache.set(off, { gids: [], remapped: null, outOfSubset: false });
if (cache) mergeGidsEntry(cache, off, []);
return [];
}
/** 预分配 + 索引赋值,避免 push 动态扩容format1 coverage 的高频热循环) */
@ -120,10 +128,24 @@ function readCoverageGids(r: Reader, off: number, cache?: CoverageCache): number
p += 6;
}
}
if (cache) cache.set(off, { gids, remapped: null, outOfSubset: false });
if (cache) mergeGidsEntry(cache, off, gids);
return gids;
}
/**
* gid covCache entry readCoverageRemapped
* remapped/outOfSubset readCoverageGids fmt3
* entry remapped=null readCoverageRemapped
*/
function mergeGidsEntry(cache: CoverageCache, off: number, gids: number[]): void {
const existing = cache.get(off);
if (existing !== undefined) {
existing.gids = gids;
} else {
cache.set(off, { gids, remapped: null, outOfSubset: false });
}
}
/**
* coverage gid
* format3 coverage index "子集内新 gid 集合"
@ -188,7 +210,13 @@ function readCoverageRemapped(
}
/** 原 coverage 非空但全部 gid 落子集外 → 失效(与原 coverage 本就空的合法空数组区分) */
if (newGids.length === 0 && origNonEmpty) outOfSubset = true;
cache.set(off, { gids: newGids, remapped: newGids, outOfSubset });
/** remapped gids
* covCache readCoverageRemappedfmt3 newGids readCoverageGids
* fmt1 ChainContext gid index ruleSet
* newGids gids fmt1 readCoverageGids off
* newGids gid FiraCode Bug
* gids readCoverageGids miss */
cache.set(off, { gids: EMPTY_GIDS, remapped: newGids, outOfSubset });
return outOfSubset ? null : newGids;
}
@ -918,59 +946,94 @@ function writeChainFormat3(
}
/**
* subtable coverage
* subtable subtable
*
* FiraCode lookup 403
* lookup coverage gid lookup
* entries退 writeEmptySubtable delta/sequence/ rule
* coverage gidLookup covCache
* lookup coverage gid entries
* 退 writeEmptySubtable gidLookup covCache
*
*
* coverage coverage subOff+2 Offset16
* - SingleSubst/Multiple/Alternate/Ligaturesource coverage entries
* - ChainContextSubst format1input coverage ruleSet
* coverage
* - ChainContextSubst format2 InputClassDef class coverage
* FiraCode calt format2 coverage class
* SSIM 0.99230.9368
* - ChainContextSubst format3 coveragebacktrack/input/lookahead coverage
* coverage
* - SingleSubst/Multiple/Alternate/Ligature coveragesubOff+2 entries
* - ChainContextSubst format1 coverageinput ruleSet
* - ChainContextSubst format3backtrack/input/lookahead coverage
*
* @returns true = coverage subtablefalse =
* ChainContextSubst format2 InputClassDef class coverage
* FiraCode calt format2 coverage class
* SSIM 0.99230.9368
*
* @returns true = subtablefalse =
*/
function isPrimaryCoverageOutOfSubset(
function isSubtableSkipableByCoverage(
r: Reader,
off: number,
type: number,
gidLookup: GidLookup,
): boolean {
/** ChainContextformat2class 驱动)和 format3多 coverage不预检 */
if (type === LT_CHAIN) {
if (off + 2 > r.dv.byteLength) return false;
const fmt = r.dv.getUint16(off, false);
if (fmt === 2 || fmt === 3) return false;
}
/**
* gidLookup coverage covCache
* covCache readCoverageRemapped newGids entry.gids readCoverageGids
* format1 entry.gids gid gids
* readCoverageRemapped format1 gids
* newGids gid FiraCode SSIM 0.99230.9368
* subtable coverage subtable
*/
const dv = r.dv;
const len = dv.byteLength;
/** ChainContext format3遍历 back/input/look 三个 coverage 组,任一组原非空且全子集外则失效 */
if (type === LT_CHAIN) {
if (off + 2 > len) return false;
const chainFmt = dv.getUint16(off, false);
if (chainFmt === 2) return false; /** format2 class 驱动,不预检 */
if (chainFmt === 3) {
let p = off + 2;
for (let grp = 0; grp < 3; grp++) {
if (p + 2 > len) return false;
const cnt = dv.getUint16(p, false);
p += 2;
let grpOrigNonEmpty = false;
let grpHasInSubset = false;
for (let k = 0; k < cnt; k++) {
if (p + 2 > len) return false;
const covOff = off + dv.getUint16(p + k * 2, false);
const st = coverageAllOutOfSubset(dv, covOff, len, gidLookup);
if (st === false) grpHasInSubset = true;
else if (st === true) grpOrigNonEmpty = true;
if (grpHasInSubset) break;
}
p += cnt * 2;
if (grpOrigNonEmpty && !grpHasInSubset) return true;
}
return false;
}
}
/** 主 coverage 在 subOff+2Offset16的类型Single/Multiple/Alternate/Ligature/Chain-format1 */
if (off + 4 > len) return false;
const covOff = off + dv.getUint16(off + 2, false);
return coverageAllOutOfSubset(dv, covOff, len, gidLookup) === true;
}
/**
* gidLookup coverage gid
* coverage format1 / format2 covCache
* subtable FiraCode 403 lookup × subtable
* gid readCoverageRemapped +
* GC
*
* @returns true=false= gid coverage
* false
*/
function coverageAllOutOfSubset(
dv: DataView,
covOff: number,
len: number,
gidLookup: GidLookup,
): boolean {
if (covOff + 4 > len) return false;
const format = dv.getUint16(covOff, false);
if (format === COV_LIST) {
const count = dv.getUint16(covOff + 2, false);
if (count === 0) return false; /** 原 coverage 本就空,不算 outOfSubset与 readCoverageRemapped 一致) */
if (count === 0) return false; /** 原 coverage 本就空,不算 outOfSubset */
const base = covOff + 4;
if (base + count * 2 > len) return false;
for (let i = 0; i < count; i++) {
if (gidLookup[dv.getUint16(base + i * 2, false)] >= 0) return false; /** 命中子集,不跳过 */
if (gidLookup[dv.getUint16(base + i * 2, false)] >= 0) return false;
}
return true; /** 全子集外 */
return true;
}
if (format === COV_RANGE) {
const rangeCount = dv.getUint16(covOff + 2, false);
@ -983,12 +1046,12 @@ function isPrimaryCoverageOutOfSubset(
if (end >= start && end - start < COVERAGE_MAX_EXPAND) {
for (let g = start; g <= end; g++) {
origNonEmpty = true;
if (gidLookup[g] >= 0) return false; /** 命中子集,不跳过 */
if (gidLookup[g] >= 0) return false;
}
}
p += 6;
}
return origNonEmpty; /** 原非空且无命中 → 全子集外 */
return origNonEmpty;
}
return false;
}
@ -1004,10 +1067,10 @@ function serializeSubtable(
gidLookup: GidLookup,
): boolean {
r.clearError();
/** coverage subtable
* FiraCode 403 lookup 253 coverage lookuptype1-4 + type6-fmt1
* entries / rule format2/format3 coverage */
if (isPrimaryCoverageOutOfSubset(r, off, type, gidLookup)) return false;
/** coverage fmt3 coverage subtable
* FiraCode 403 lookup ~330 type1-4 + fmt1 + fmt3
* format2 class coverage */
if (isSubtableSkipableByCoverage(r, off, type, gidLookup)) return false;
let ok: boolean;
switch (type) {
case LT_SINGLE: