From 852f0d2f21164eca2fb2fa10433eb4d41acd72d4 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 14:40:05 +0800 Subject: [PATCH] =?UTF-8?q?perf(ot-bytes):=20OTWriter=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=20256=E2=86=922048=20+=20=E5=86=85=E8=81=94?= =?UTF-8?q?=E5=AE=B9=E9=87=8F=E6=A3=80=E6=9F=A5=E6=B6=88=E9=99=A4=E7=83=AD?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=87=BD=E6=95=B0=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subsetGPOS 思源 36→33μs、subsetGSUB 13→11μs;writeUint16 是序列化第一热点 (思源 207次/call),原 per-call ensure() 函数调用在 V8 class private method 不内联;内联为容量判断 + grow,并预分配 2048 避免小输出多次扩容拷贝 --- backend/font_util/ot-bytes.ts | 39 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/backend/font_util/ot-bytes.ts b/backend/font_util/ot-bytes.ts index 665615b..af3b9db 100644 --- a/backend/font_util/ot-bytes.ts +++ b/backend/font_util/ot-bytes.ts @@ -25,8 +25,15 @@ export class OTWriter { /** 优化(Uint8Array 底层缓冲):原用 number[] + push 累积字节,每个 writeUint8/16 触发 * 数字装箱与数组扩容;gsub-subset 逐字节 writeUint8 复制 ScriptList/FeatureList 字节块 * 极慢。改用 Uint8Array 容量缓冲 + size 指针:writeUint8/16 索引写入(无装箱), - * writeBytes 用 TypedArray.set 批量复制,toUint8Array 零拷贝 subarray。 */ - private buf: Uint8Array = new Uint8Array(256); + * writeBytes 用 TypedArray.set 批量复制,toUint8Array 零拷贝 subarray。 + * + * 优化327: 初始容量 256→2048,并让 writeUint16/writeInt16/reserveOffset16 内联容量检查。 + * GPOS/GSUB 输出常达数 KB(思源 GPOS 978B、令东 GSUB 数十 KB),256 起步触发多次 2× 扩容 + * (每次扩容 new Uint8Array + set 全拷贝)。更关键的是 writeUint16 是序列化第一热点 + * (思源 subsetGPOS 207 次/call 占 ~58%),原实现每次调 private ensure()——V8 对 class + * private method 内联不充分,per-call 函数调用开销在百次累计下显著。改为内联容量判断 + * (够用直接写,不够才调 grow),消除热路径上的函数调用。 */ + private buf: Uint8Array = new Uint8Array(2048); private size: number = 0; private patches: Array<{ pos: number; base: number; targetGetter: () => number }> = []; @@ -34,10 +41,8 @@ export class OTWriter { return this.size; } - /** 确保剩余容量 >= need,不足则按 2× 扩容 */ - private ensure(need: number): void { - const required = this.size + need; - if (required <= this.buf.byteLength) return; + /** 容量不足时扩容(仅在 write 路径内联判断发现不够时调用) */ + private grow(required: number): void { let cap = this.buf.byteLength; while (cap < required) cap *= 2; const grown = new Uint8Array(cap); @@ -55,13 +60,15 @@ export class OTWriter { } writeUint8(v: number): void { - this.ensure(1); - this.buf[this.size++] = v & 0xff; + const s = this.size; + if (s + 1 > this.buf.byteLength) this.grow(s + 1); + this.buf[s] = v & 0xff; + this.size = s + 1; } writeUint16(v: number): void { - this.ensure(2); const s = this.size; + if (s + 2 > this.buf.byteLength) this.grow(s + 2); this.buf[s] = (v >>> 8) & 0xff; this.buf[s + 1] = v & 0xff; this.size = s + 2; @@ -70,16 +77,18 @@ export class OTWriter { /** 批量写入字节块(TypedArray.set,远快于逐字节 writeUint8 循环) */ writeBytes(arr: Uint8Array): void { const n = arr.byteLength; - this.ensure(n); - this.buf.set(arr, this.size); - this.size += n; + const s = this.size; + const required = s + n; + if (required > this.buf.byteLength) this.grow(required); + this.buf.set(arr, s); + this.size = required; } /** 在当前末尾写入 int16(大端,支持负数;如 SingleSubst format1 的 deltaGlyphID)。 * 原实现依赖 number[] 索引赋值到 length 位置隐式扩展数组,Uint8Array 版需显式 ensure + 推进 size。 */ writeInt16(v: number): void { - this.ensure(2); const s = this.size; + if (s + 2 > this.buf.byteLength) this.grow(s + 2); const u16 = v < 0 ? 0x10000 + (v & 0xffff) : v & 0xffff; this.buf[s] = (u16 >>> 8) & 0xff; this.buf[s + 1] = u16 & 0xff; @@ -96,9 +105,9 @@ export class OTWriter { /** 预留一个 uint16 偏移量槽位,flush 时写入 (targetGetter() - base) */ reserveOffset16(base: number, targetGetter: () => number): void { - this.ensure(2); const pos = this.size; - this.size += 2; + if (pos + 2 > this.buf.byteLength) this.grow(pos + 2); + this.size = pos + 2; this.patches.push({ pos, base, targetGetter }); }