perf(gsub): currentSortedSubsetGids 延迟构造,无 format2 ClassDef 时省排序

sort 仅 ClassDef format2 / coverage range 路径需要,原入口点无条件 Array.from(origToNew.keys()).sort()。
改首次访问时惰性构造(getter),无 format2 ClassDef 的字体(令东无 GSUB / 霞鹜/初夏 GSUB 多为 format3)永不算。
小字集收益不可测(sort <70μs),全字符集场景省 ~3.2ms 固定开销。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-25 08:33:24 +08:00
parent 7b3b0ef592
commit 01f920f3f0

View File

@ -88,8 +88,24 @@ type GidLookup = Int32Array;
*
* subsetGSUB 线 sortedSubsetGids 穿
* serializeSubtable serializeChainedContextSubst writeChainFormat2 readClassDefMap
*
* 336 `Array.from(origToNew.keys()).sort()`27677 gid
* sort ~3.2ms readClassDefMap format2 ClassDef format2 GSUB
* / GSUB format3 3.2ms format2 访
*/
let currentSortedSubsetGids: number[] = [];
let currentSortedSubsetGids: number[] | null = null;
/** 惰性构造的源 MapsubsetGSUB 入口设),首次 format2 访问时 Array.from(.keys()).sort() */
let currentSortedSubsetSource: Map<number, number> | null = null;
/** 惰性获取升序子集 gid 数组(首次调用构造,后续复用缓存) */
function getSortedSubsetGids(): number[] {
if (currentSortedSubsetGids === null) {
currentSortedSubsetGids = currentSortedSubsetSource
? Array.from(currentSortedSubsetSource.keys()).sort((a, b) => a - b)
: [];
}
return currentSortedSubsetGids;
}
/** Coverage gid
* cache coverage off
@ -265,7 +281,7 @@ function readCoverageRemapped(
* gid [start..e] gidLookupFiraCode coverage format2 2317 gid
* gid [start,end] gid push newGid
* range + range gid newGids gid */
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const subsetLen = subsetGids.length;
let p = off + 4;
for (let i = 0; i < rangeCount; i++) {
@ -404,7 +420,7 @@ function serializeSingleSubst(
* coverage targetO(subsetSize × log covCount) O(covCount)
* [[gsub-reachable-type1-fmt2-reverse-iter]] coverage
* coverage > */
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const covGidCount = coverageCount(r, covOff);
/** 阈值coverage gid 数 / 子集 gid 数 > 4 且 coverage 较大时启用反转 */
const useReverse = covGidCount > subsetGids.length * 4 && covGidCount > 16;
@ -534,7 +550,7 @@ function serializeMultipleSubst(
/** 反转遍历快路径(同 serializeLigatureSubst / serializeSingleSubstcoverage
* gid coverage idx SequenceTable */
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const covGidCount = coverageCount(r, covOff);
const useReverse = covGidCount > subsetGids.length * 4 && covGidCount > 16;
@ -610,7 +626,7 @@ function serializeAlternateSubst(
/** 反转遍历快路径(同 serializeMultipleSubst / serializeLigatureSubstcoverage
* gid coverage idx AlternateSet */
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const covGidCount = coverageCount(r, covOff);
const useReverse = covGidCount > subsetGids.length * 4 && covGidCount > 16;
@ -690,7 +706,7 @@ function serializeLigatureSubst(
* gidcurrentSortedSubsetGids coverageIndexOf coverage idx
* idx LigatureSet off + 6 + idx * 2 serializeSingleSubst
* [[gsub-serialize-single-bigcov-reverse]] coverage */
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const covGidCount = coverageCount(r, covOff);
const useReverse = covGidCount > subsetGids.length * 4 && covGidCount > 16;
@ -813,7 +829,7 @@ function readClassDefMap(r: Reader, off: number, gidLookup: GidLookup): Map<numb
}
} else if (format === 2) {
const rangeCount = r.u16(off + 2);
const subsetGids = currentSortedSubsetGids;
const subsetGids = getSortedSubsetGids();
const subsetLen = subsetGids.length;
if (subsetLen === 0) return result;
/** ClassDef format2 class range 数组起始(每个 range 6 字节startGid/endGid/startClassIndex */
@ -1361,7 +1377,7 @@ function coverageAllOutOfSubset(
* 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 sortedGids = getSortedSubsetGids();
const subsetN = sortedGids.length;
let p = covOff + 4;
let origNonEmpty = false;
@ -1461,8 +1477,11 @@ export function subsetGSUB(
const gidLookup: GidLookup = new Int32Array(maxOrigGid + 1).fill(-1);
for (const [g, n] of origToNew) gidLookup[g] = n;
/** 升序子集原始 gid 数组,供 readClassDefMap format2 range 二分优化(替代展开 [start..end] 全部 gid */
currentSortedSubsetGids = Array.from(origToNew.keys()).sort((a, b) => a - b);
/** 升序子集原始 gid 数组(惰性):供 readClassDefMap format2 / coverage range
* 336 sort 27677 gid ~3.2ms source +
* getSortedSubsetGids() format2/range */
currentSortedSubsetSource = origToNew;
currentSortedSubsetGids = null;
/** ---- GSUB Header ---- */
/** header offset0/2/4/6/8dv getUint16 +