From 791ea0c89eed45fd61af41375c85ae85f9a50a13 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 19:54:07 +0800 Subject: [PATCH] =?UTF-8?q?perf(ot-writer):=20=E4=B8=BB=20Writer=20?= =?UTF-8?q?=E6=8C=89=E5=8E=9F=E8=A1=A8=E5=A4=A7=E5=B0=8F=E9=A2=84=E5=88=86?= =?UTF-8?q?=E9=85=8D=E5=AE=B9=E9=87=8F=E6=B6=88=E9=99=A4=20grow=20?= =?UTF-8?q?=E5=85=A8=E6=8B=B7=E8=B4=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OTWriter 加可选 initialCapacity 构造参数(默认 2048)。subsetGPOS/subsetGSUB 主 Writer 按原表 byteLength 预分配,避免大表多次 2× 扩容的全拷贝。 初夏纯标点 GPOS 单次 fontSubset 原 5 次 grow(cap 2048→4096→8192→16384), 改后 5 字体(初夏/FiraCode/令东千字文/思源/霞鹜)grow 全部 0。 12 字体 woff2 sha 逐字节一致,SSIM 全不变。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/gpos-subset.ts | 5 ++++- backend/font_util/gsub-subset.ts | 3 ++- backend/font_util/ot-bytes.ts | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/backend/font_util/gpos-subset.ts b/backend/font_util/gpos-subset.ts index 3038f1d..f7d52f9 100644 --- a/backend/font_util/gpos-subset.ts +++ b/backend/font_util/gpos-subset.ts @@ -222,7 +222,10 @@ export function subsetGPOS( /** ScriptList/FeatureList 解析失败(异常表)则整体降级返回 null(调用方保留原始 GPOS 字节) */ if (!scriptListBytes || !featureListBytes) return null; - const w = new Writer(); + /** 主 Writer 按原 GPOS 表大小预分配容量:子集输出通常 ≤ 原表(剔除字形), + * 默认 2048 对大 GPOS(初夏 lookup[10] 283 subtable 累积 ~16KB)触发 4+ 次 grow 全拷贝。 + * 取原表大小作为初始容量(足够且不至于对小表浪费太多)。 */ + const w = new Writer(gposBytes.byteLength); /** Header(偏移量最后回填) */ w.writeUint16(1); diff --git a/backend/font_util/gsub-subset.ts b/backend/font_util/gsub-subset.ts index 42a81f6..ad8787f 100644 --- a/backend/font_util/gsub-subset.ts +++ b/backend/font_util/gsub-subset.ts @@ -1398,7 +1398,8 @@ export function subsetGSUB( /** ScriptList/FeatureList 解析失败(异常表)则整体保留原始 GSUB 字节(安全降级) */ if (!scriptListBytes || !featureListBytes) return gsubBytes; - const w = new Writer(); + /** 主 Writer 按原 GSUB 表大小预分配容量,避免大 GSUB(令东千字文 GSUB 数十 KB)多次 grow 全拷贝 */ + const w = new Writer(gsubBytes.byteLength); /** Header */ w.writeUint16(1); diff --git a/backend/font_util/ot-bytes.ts b/backend/font_util/ot-bytes.ts index af3b9db..9a33695 100644 --- a/backend/font_util/ot-bytes.ts +++ b/backend/font_util/ot-bytes.ts @@ -33,10 +33,16 @@ export class OTWriter { * (思源 subsetGPOS 207 次/call 占 ~58%),原实现每次调 private ensure()——V8 对 class * private method 内联不充分,per-call 函数调用开销在百次累计下显著。改为内联容量判断 * (够用直接写,不够才调 grow),消除热路径上的函数调用。 */ - private buf: Uint8Array = new Uint8Array(2048); + /** 初始容量:默认 2048;subsetGPOS/subsetGSUB 主 Writer 按原表大小预分配避免多次 grow 全拷贝 */ + private buf: Uint8Array; private size: number = 0; private patches: Array<{ pos: number; base: number; targetGetter: () => number }> = []; + /** 预估输出容量的 Writer(省去从默认容量多次 2× 扩容的全拷贝)。initialCapacity 为 0/省略时用默认 2048。 */ + constructor(initialCapacity: number = 2048) { + this.buf = new Uint8Array(initialCapacity > 0 ? initialCapacity : 2048); + } + get length(): number { return this.size; }