perf(reader): M31 字体路径记忆化(开书字体查找 35 目录 walk → 1 次 stat)+ 「· 读」徽标即时刷新

字体:find_font_rec/resolve_font 从 reader.c 抽到 app/font_resolve.c 并加进程内记忆化。
只缓存成功路径;命中后仍做一次 access() 校验(字库被删即重扫);没找到不缓存,
用户之后拷入的字库下次开书即可找到。顶栏「刷新」额外调 font_cache_invalidate(),
给「新拷入字库、旧字库仍在」一条显式失效路径。宿主实测:35 目录树 walk 0.026~0.54ms
(=300+ 次系统调用)vs 命中 0.001ms(1 次 access);开书路径每次省一次完整 walk。

徽标:副题格式化抽成 shelf_fmt_sub()(扫描与「刚读完置顶」共用一份实现),
promote_just_read() 改 ts 后立即重算这本的 sub/sub_folder,「· 读」不再等下次全量刷新。

回归:test_reader_core 6846、shelf_group_smoke 63(+8)、font_resolve_smoke 14 全绿;
build_reader.sh 0 warning;扫描缓存/进度语义未改。app.json 0.1.3/code4 → 0.1.4/code5。
This commit is contained in:
AskaEth committed 2026-11-10 13:39:32 +08:00
1 parent 0b30cdbafb
commit dc9bfdbdaf
12 files changed
+460 -53

No files matched your search

@@ -0,0 +1,109 @@
/* test/font_resolve_bench.c — M31 字体路径记忆化耗时对比(宿主,不依赖设备)。
*
* 同一棵树两种口径:
* walk = 每次都重扫(= 加缓存前的 resolve_font 行为),由 font_cache_invalidate() 强制;
* hit = 缓存命中 + access() 校验(= 加缓存后开书路径)。
* 用法:./font_resolve_bench_host [目录] [迭代数],不传目录则先造一棵 35 目录的树。
*/
#include "font_resolve.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
static void mkdirs(const char *path)
{
char t[512];
snprintf(t, sizeof t, "%s", path);
for (char *p = t + 1; *p; p++) {
if (*p == '/') { *p = 0; mkdir(t, 0755); *p = '/'; }
}
mkdir(t, 0755);
}
static void write_file(const char *path, const char *body)
{
FILE *f = fopen(path, "wb");
if (!f) return;
fputs(body, f);
fclose(f);
}
static void make_fonts_tree(const char *root, int ndirs, int files_per_dir)
{
char p[512];
for (int i = 0; i < ndirs; i++) {
snprintf(p, sizeof p, "%s/family_%02d/sub", root, i);
mkdirs(p);
for (int j = 0; j < files_per_dir; j++) {
snprintf(p, sizeof p, "%s/family_%02d/readme_%d.txt", root, i, j);
write_file(p, "x");
}
snprintf(p, sizeof p, "%s/family_%02d/sub/Font%02d.ttf", root, i, i);
write_file(p, "x");
}
}
static double ms_now(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
}
typedef struct stat_res { double min, avg, max; } stat_res;
static stat_res run(const char *root, int iters, int invalidate_each)
{
stat_res r = { 1e9, 0, 0 };
double sum = 0;
for (int i = 0; i < iters; i++) {
if (invalidate_each) font_cache_invalidate();
double t0 = ms_now();
(void)font_resolve(root);
double dt = ms_now() - t0;
if (dt < r.min) r.min = dt;
if (dt > r.max) r.max = dt;
sum += dt;
}
r.avg = iters ? sum / (double)iters : 0;
return r;
}
int main(int argc, char **argv)
{
const char *root = (argc > 1) ? argv[1] : NULL;
int iters = (argc > 2) ? atoi(argv[2]) : 200;
char tmpbuf[512] = { 0 };
if (!root) {
const char *base = getenv("TMPDIR");
if (!base || !base[0]) base = "/tmp";
snprintf(tmpbuf, sizeof tmpbuf, "%s/fontbench_XXXXXX", base);
if (!mkdtemp(tmpbuf)) { printf("mkdtemp 失败\n"); return 2; }
make_fonts_tree(tmpbuf, 35, 3);
root = tmpbuf;
}
/* 预热(首轮含 dentry 冷缓存,不计入) */
font_cache_invalidate(); font_resolve(root);
font_resolve(root);
const char *p = font_resolve(root);
printf("root=%s\n", root);
printf("font=%s\n", p ? p : "(none)");
stat_res w = run(root, iters, 1); /* 每次失效 = 旧行为:每次开书重 walk */
stat_res h = run(root, iters, 0); /* 缓存命中 + access 校验 = 新行为 */
printf("walk (每次重扫): min=%.3f avg=%.3f max=%.3f ms\n", w.min, w.avg, w.max);
printf("hit (缓存+access): min=%.3f avg=%.3f max=%.3f ms\n", h.min, h.avg, h.max);
if (h.avg > 0) printf("提速: %.1fx\n", w.avg / h.avg);
if (tmpbuf[0]) {
char cmd[600];
snprintf(cmd, sizeof cmd, "rm -rf '%s'", tmpbuf);
if (system(cmd) != 0) { /* 忽略 */ }
}
return 0;
}