perf(gsub-reachable): coverageFirstExcludedGid 边解析边查不展开 coverage 数组

原实现调用 readCoverageGids(off, cache) 完整展开 coverage 到数组再遍历找首个 excluded,
对 format2 range(初夏明朝 coverage 常覆盖上千 gid)即使首个 gid 就 excluded 也要展开全量,
是「展开全量命中极少」浪费(同类见 gsub-classdef-format2-bsearch)。

改为 cache 命中遍历缓存数组,miss 时直接边解析 coverage 边查 inSubset,命中即返回,
不分配数组、不填 cache。fmt3 失败路径的 coverage 永不被 collectSubtableTargets 读
(失败不产 target),故不填 cache 无碍。

reachable 集合 sha256 三字体完全一致,SSIM/ink 全不变。
初夏纯标点 collectReachable p50 0.159→0.136ms(-14%)。
This commit is contained in:
崮生(子虚) 2026-07-24 16:52:23 +08:00
parent 694364769e
commit fee9e49164

View File

@ -61,12 +61,19 @@ function readCoverageGids(r: OTReader, off: number, cache: CoverageCache): numbe
* coverage inSubset false gid
*
* format1 list u16 + inSubset
* format2 range gid start..end+ inSubset readCoverageGids
* format2 range range gid start..end+ inSubset readCoverageGids
* 280 format3 coverage gid
*
*
* gid -1coverage covCache
* lookup type
* gid -1coverage
*
* 优化310: cache lookup type
* cache miss ** readCoverageGids cache**
* `readCoverageGids(off, cache)` coverage format2 range
* coverage gid使 gid excluded
* [[gsub-classdef-format2-bsearch]]fmt3 coverage collectSubtableTargets
* target cache triggerable=true coverage readCoverageGids
* contextGids
*
* @param off coverage
* @param inSubset gid
@ -78,8 +85,34 @@ function coverageFirstExcludedGid(
cache: CoverageCache,
inSubset: (gid: number) => boolean,
): number {
const gids = readCoverageGids(r, off, cache);
for (const g of gids) if (!inSubset(g)) return g;
/** cache 命中:遍历已缓存数组(复用其他 lookup 的解析结果) */
const cached = cache.get(off);
if (cached !== undefined) {
for (const g of cached) if (!inSubset(g)) return g;
return -1;
}
/** cache miss边解析边查不分配数组、不填 cache */
const format = r.u16(off);
if (format === 1) {
const count = r.u16(off + 2);
for (let i = 0; i < count; i++) {
const g = r.u16(off + 4 + i * 2);
if (!inSubset(g)) return g;
}
return -1;
} else if (format === 2) {
const rangeCount = r.u16(off + 2);
let p = off + 4;
for (let i = 0; i < rangeCount; i++) {
const start = r.u16(p);
const end = r.u16(p + 2);
for (let g = start; g <= end; g++) {
if (!inSubset(g)) return g;
}
p += 6;
}
return -1;
}
return -1;
}