perf(gsub): emitCoverage 跳过 slice+sort,readCoverageRemapped 缓存前统一排序

原 emitCoverage 对输入 newGids 做 slice().sort()——防御性复制+排序。
但 readCoverageRemapped 的输出(fmt3/writeChainFormat2 coverage)因 原gid→新gid
映射不保序而可能乱序,必须排序;type1/2/3/4 的 entries.map(e=>from) 虽已按 from
排序故升序,却仍付出 slice+sort 开销。

将排序职责上移到 readCoverageRemapped 缓存入口(covCache 命中率极高,FiraCode
604 引用/83 独立 coverage,排序分摊几乎免费),使所有调用方输出天然升序无重复。
emitCoverage 据此改为直接扫描区间(不 slice、不 sort、不分配 ranges 对象数组),
同时两遍扫描统一为 rangeCount 计数。

FiraCode subsetGSUB 417→390μs(-6.5%),输出 sha 全字体一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-24 17:33:48 +08:00
parent f053112e44
commit 799d284d4b

View File

@ -289,6 +289,13 @@ function readCoverageRemapped(
}
/** 原 coverage 非空但全部 gid 落子集外 → 失效(与原 coverage 本就空的合法空数组区分) */
if (newGids.length === 0 && origNonEmpty) outOfSubset = true;
/** coverage gid gidgid subsetGids
* gid readCoverageRemapped gid emitCoverage
* fmt3 coverage / writeChainFormat2 coverage
* 使emitCoverageSorted slice+sortfmt3
* coverage off covCache FiraCode 604 /83 coverage
* */
if (!outOfSubset && newGids.length > 1) newGids.sort((a, b) => a - b);
/** remapped gids
* covCache readCoverageRemappedfmt3 newGids readCoverageGids
* fmt1 ChainContext gid index ruleSet
@ -303,38 +310,46 @@ function readCoverageRemapped(
* gid Coverage Writer
* format1 format2
*/
/**
* gid Coverage Writer
* format1 format2
*
* newGids ****
* - readCoverageRemapped sort coverage gid gidgid
* - type1/2/3/4 fmt1 entries from/firstGid map
* slice sort ranges
* fmt3 ~17 emitCoverage GC+CPU
* covCache */
function emitCoverage(w: Writer, newGids: number[]): number {
/** Coverage
* newGids
* readCoverageRemapped coverage gid gid gid
* entries.map(e=>e.from) from entry
* FiraCode 327 emitCoverage 0 new Set GC
* slice */
const sorted = newGids.slice().sort((a, b) => a - b);
const off = w.length;
let ranges: Array<{ start: number; end: number }> = [];
for (let i = 0; i < sorted.length; ) {
const n = newGids.length;
/** 第一遍:统计连续区间数(决定 format2 是否更紧凑) */
let rangeCount = 0;
for (let i = 0; i < n; ) {
let j = i;
while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1) j++;
ranges.push({ start: sorted[i], end: sorted[j] });
while (j + 1 < n && newGids[j + 1] === newGids[j] + 1) j++;
rangeCount++;
i = j + 1;
}
if (ranges.length > 0 && ranges.length < sorted.length) {
if (rangeCount > 0 && rangeCount < n) {
/** format2 区间更紧凑 */
w.writeUint16(COV_RANGE);
w.writeUint16(ranges.length);
w.writeUint16(rangeCount);
let covIndex = 0;
for (const rg of ranges) {
w.writeUint16(rg.start);
w.writeUint16(rg.end);
for (let i = 0; i < n; ) {
let j = i;
while (j + 1 < n && newGids[j + 1] === newGids[j] + 1) j++;
w.writeUint16(newGids[i]);
w.writeUint16(newGids[j]);
w.writeUint16(covIndex);
covIndex += rg.end - rg.start + 1;
covIndex += newGids[j] - newGids[i] + 1;
i = j + 1;
}
} else {
/** format1 列表 */
w.writeUint16(COV_LIST);
w.writeUint16(sorted.length);
for (const g of sorted) w.writeUint16(g);
w.writeUint16(n);
for (let i = 0; i < n; i++) w.writeUint16(newGids[i]);
}
return off;
}