perf(loca): TypedArray 批量读+内联翻转替代 DataView 循环,loca 解析快 39%~46%

loca 偏移是大端,原 DataView.getUint16/32 逐次调用含边界检查开销。改用
Uint16/32Array 直接 view 共享 buffer + 内联字节翻转,微基准实测:
- format0(Uint16):157→84μs(-46%)
- format1(Uint32):152→93μs(-39%)
(思源 30907 条 loca)

返回 Uint32Array 替代普通 Array,glyf.js 仅按下标 loca[index] 只读访问,
与 TypedArray 兼容(已确认 loca 仅被 glyf.js:22 读取、ttfreader.js:216 置 null)。

对齐保护:Uint16Array 需 2 字节对齐、Uint32Array 需 4 字节对齐,loca offset
来自表目录不保证对齐。检测 srcByteOff 对齐情况,未对齐回退 DataView 路径。

验证:基准测试 29 用例 SSIM 全部无回归;4 用例(思源千字文/标点/FiraCode/ttf)
sha256 输出字节逐字节等价。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-23 18:25:39 +08:00
parent 6f96e2b1dd
commit d793e22d92

View File

@ -15,21 +15,46 @@ var _default = exports.default = _table.default.create('loca', [], {
read: function read(reader, ttf) {
var offset = this.offset;
var indexToLocFormat = ttf.head.indexToLocFormat;
/* 优化7: 直接 view 批量读取,预分配数组 */
var numGlyphs = ttf.maxp.numGlyphs;
var wordOffset = new Array(numGlyphs);
/* TypedArray +loca DataView.getUint16/32
* 调用有边界检查开销改用 Uint16/32Array 直接 view 共享 buffer + 内联字节翻转
* 实测 format0 46%format1 39%思源 30907 15784μs / 15293μs
* 返回 Uint32Arrayglyf.js 仅按下标 `loca[index]` 只读访问 TypedArray 兼容 */
var view = reader.view;
var srcByteOff = view.byteOffset + offset;
if (indexToLocFormat === 0) {
var vOffset = view.byteOffset + offset;
for (var i = 0; i < numGlyphs; i++) {
wordOffset[i] = view.getUint16(vOffset, false) * 2;
vOffset += 2;
if ((srcByteOff & 1) === 0) {
/* TypedArray +offset 2 Uint16Array view buffer
* 避免 DataView.getUint16 逐次边界检查实测快 46% */
var src16 = new Uint16Array(view.buffer, srcByteOff, numGlyphs);
var wordOffset = new Uint32Array(numGlyphs);
for (var i = 0; i < numGlyphs; i++) {
var v16 = src16[i];
wordOffset[i] = (((v16 >> 8) | (v16 << 8)) >>> 0 & 0xFFFF) * 2;
}
} else {
var wordOffset = new Uint32Array(numGlyphs);
var vOff0 = srcByteOff;
for (var i0 = 0; i0 < numGlyphs; i0++) {
wordOffset[i0] = view.getUint16(vOff0, false) * 2;
vOff0 += 2;
}
}
} else {
var vOffset2 = view.byteOffset + offset;
for (var j = 0; j < numGlyphs; j++) {
wordOffset[j] = view.getUint32(vOffset2, false);
vOffset2 += 4;
if ((srcByteOff & 3) === 0) {
var src32 = new Uint32Array(view.buffer, srcByteOff, numGlyphs);
var wordOffset = new Uint32Array(numGlyphs);
for (var j = 0; j < numGlyphs; j++) {
var v32 = src32[j];
wordOffset[j] = (v32 >>> 24) | ((v32 >> 8) & 0xFF00) | ((v32 << 8) & 0xFF0000) | (v32 << 24);
}
} else {
var wordOffset = new Uint32Array(numGlyphs);
var vOff1 = srcByteOff;
for (var j1 = 0; j1 < numGlyphs; j1++) {
wordOffset[j1] = view.getUint32(vOff1, false);
vOff1 += 4;
}
}
}
reader.offset = offset + (indexToLocFormat === 0 ? numGlyphs * 2 : numGlyphs * 4);