优化基准测试

This commit is contained in:
崮生(子虚) 2026-04-08 22:49:43 +08:00
parent c74278cf2f
commit a8fdb24de4
4 changed files with 59 additions and 8 deletions

6
.gitignore vendored
View File

@ -30,4 +30,8 @@ app
*.tar
dist_backend
.pilot
.pilot
verify_font_baseline
benchmark_results
.claude

3
task.md Normal file
View File

@ -0,0 +1,3 @@
/loop 持续优化字体子集化性能,可以大胆放开手脚的去做,但是优化完一定要通过基准测试。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
啊,你每次优化能不能把基准测试保存在本地目录下,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果,这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。

View File

@ -18,6 +18,7 @@ const isBaseline = process.argv[2] === "baseline";
const FONT_PATH = "font/令东齐伋复刻体.ttf";
const FONT_NAME = "令东齐伋复刻体";
const SIMILARITY_THRESHOLD = 0.98;
const BASELINE_DIR = "benchmark_results";
const raw = await readFile(FONT_PATH);
const fontBuffer = new Uint8Array(raw).buffer;
@ -135,15 +136,16 @@ function extractFontData(font: any) {
/** 注册子集字体并返回 fontFamily 名 */
async function registerSubsetFont(ttfBuffer: ArrayBuffer, counter: number): Promise<string> {
const fontPath = `verify_font_baseline/_verify_${counter}.ttf`;
const fontPath = `${BASELINE_DIR}/_verify_${counter}.ttf`;
await writeFile(fontPath, Buffer.from(ttfBuffer));
const familyName = `SubsetFont_${counter}`;
FontLibrary.use(familyName, [fontPath]);
return familyName;
}
await mkdir(BASELINE_DIR, { recursive: true });
if (isBaseline) {
await mkdir("verify_font_baseline", { recursive: true });
const baseline: Record<string, any> = {};
for (const { text, label } of testCases) {
@ -151,7 +153,7 @@ if (isBaseline) {
const font = Font.create(fontBuffer, { type: "ttf", subset });
const data = extractFontData(font);
await writeFile(`verify_font_baseline/${label}.ttf`, Buffer.from(data.buffer));
await writeFile(`${BASELINE_DIR}/${label}.ttf`, Buffer.from(data.buffer));
/** 用完整字体和子集字体分别渲染,保存像素数据 */
const fullPixels = renderText(FONT_NAME, text, 48);
@ -172,10 +174,10 @@ if (isBaseline) {
console.log(` ${label}: glyf=${data.glyfCount}, output=${data.outputSize} bytes, ssim=${ssim.toFixed(4)}`);
}
await writeFile("verify_font_baseline.json", JSON.stringify(baseline, null, 2));
await writeFile(`${BASELINE_DIR}/verify_baseline.json`, JSON.stringify(baseline, null, 2));
console.log("\n基准已生成含完整字体+子集字体渲染像素数据及相似度)");
} else {
const baselineRaw = await readFile("verify_font_baseline.json", "utf-8");
const baselineRaw = await readFile(`${BASELINE_DIR}/verify_baseline.json`, "utf-8");
const baseline = JSON.parse(baselineRaw);
let allPassed = true;

View File

@ -5,6 +5,7 @@
* :
* 1. Font.create optimize sort write
* 2. vs SSIM
* 3. benchmark_results/
*/
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { performance } from "node:perf_hooks";
@ -13,6 +14,7 @@ import { Canvas, FontLibrary } from "skia-canvas";
const FONT_PATH = "font/令东齐伋复刻体.ttf";
const FONT_NAME = "令东齐伋复刻体";
const BENCHMARK_DIR = "benchmark_results";
const raw = await readFile(FONT_PATH);
const fontBuffer = new Uint8Array(raw).buffer;
@ -48,6 +50,22 @@ function renderText(fontFamily: string, text: string, fontSize: number): Uint8Ar
return new Uint8Array(imgData.data.buffer);
}
/** 渲染文字并保存为 PNG */
async function renderTextToPng(fontFamily: string, text: string, fontSize: number, filePath: string) {
const charWidth = Math.ceil(fontSize * 1.5);
const width = text.length * charWidth + 20;
const height = Math.ceil(fontSize * 1.5);
const canvas = new Canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillStyle = "black";
ctx.fillText(text, 10, Math.ceil(fontSize * 1.2));
const buffer = await canvas.toBuffer("png");
return writeFile(filePath, buffer);
}
/** 计算两张图片的结构相似度(简化版 SSIM返回 0~1 */
function calculateSSIM(a: Uint8Array, b: Uint8Array): number {
if (a.length !== b.length) return 0;
@ -84,16 +102,27 @@ function calculateSSIM(a: Uint8Array, b: Uint8Array): number {
/** 注册子集字体用于渲染 */
async function registerSubsetFont(ttfBuffer: ArrayBuffer, counter: number): Promise<string> {
await mkdir("verify_font_baseline", { recursive: true });
const fontPath = `verify_font_baseline/_bench_${counter}.ttf`;
await mkdir(BENCHMARK_DIR, { recursive: true });
const fontPath = `${BENCHMARK_DIR}/_bench_${counter}.ttf`;
await writeFile(fontPath, Buffer.from(ttfBuffer));
const familyName = `BenchSubset_${counter}`;
FontLibrary.use(familyName, [fontPath]);
return familyName;
}
await mkdir(BENCHMARK_DIR, { recursive: true });
console.log("\n=== 字体裁剪基准测试 ===\n");
const results: Array<{
label: string;
avg: number;
min: number;
max: number;
outputSize: number;
ssim: number;
}> = [];
for (const { label, text } of testCases) {
const subset = [...text].map((c) => c.codePointAt(0)!);
const times: number[] = [];
@ -122,12 +151,25 @@ for (const { label, text } of testCases) {
if (lastTtfBuffer) {
subsetFontCounter++;
const familyName = await registerSubsetFont(lastTtfBuffer, subsetFontCounter);
/** 保存渲染对比图片 */
const safeLabel = label.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, "_");
await renderTextToPng(FONT_NAME, text, 48, `${BENCHMARK_DIR}/${safeLabel}_full.png`);
await renderTextToPng(familyName, text, 48, `${BENCHMARK_DIR}/${safeLabel}_subset.png`);
const fullPixels = renderText(FONT_NAME, text, 48);
const subsetPixels = renderText(familyName, text, 48);
ssim = calculateSSIM(fullPixels, subsetPixels);
}
results.push({ label, avg, min, max, outputSize: lastOutputSize, ssim });
console.log(` ${label}: avg=${avg.toFixed(1)}ms min=${min.toFixed(1)}ms max=${max.toFixed(1)}ms 输出=${lastOutputSize.toLocaleString()} bytes ssim=${ssim.toFixed(4)}`);
}
/** 保存结果到 JSON */
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const resultFile = `${BENCHMARK_DIR}/benchmark_${timestamp}.json`;
await writeFile(resultFile, JSON.stringify({ timestamp: new Date().toISOString(), rounds: ROUNDS, results }, null, 2));
console.log(`\n结果已保存到 ${resultFile}`);
console.log(`渲染对比图片已保存到 ${BENCHMARK_DIR}/ 目录`);
console.log("");