perf(reader): M32 r2 书架索引改 level-1 校验 — 真机冷缓存首开 3.47s→0.13–0.16s(r1 全目录校验被真机证伪)
r1 用宿主 1.1µs/stat + M28 的**热** stat 150µs 外推「434 次≈65ms」,真机 drop_caches 后 实测是 3.15s(7.26ms/次),与 readdir 的 3.0s 同量级:全目录校验的索引命中冷启动仍要 3.06s,只比全扫快 0.2s。「3.0s→0.15s」在冷启动不成立。按任务 1 自己定的判据 (stat 接近 1s 就退回 level-1)改为只校验 Books 根 + 每个一级目录。 真机冷缓存实测(exFAT /usr/data/mnt/sd_0,434 目录/646 文件/595 本;sync + drop_caches; 探针走 /proc/uptime,UI 走 meow_ui_time_ms): - 索引命中首开:**127 / 138 / 157ms**(复测 147ms),热 dentry 107–120ms; - 全量扫描首开:**3428 / 3477ms**(复测 3711ms),热 dentry 不变(readdir 不吃 page cache); - 探针冷分项:索引载入 20–30ms;载入+rdprog×595 70–120ms;4 个目录 stat 0–30ms; - 从阅读页返回书架 29–31ms(M29 语义未变)。 → 冷启动 22–26 倍提升,方案值得做;索引实测 **63,579B**(v2;r1 全目录 97,490B)。 实现(格式 v2,magic MEOWIDX2 / version 2): - writer 回调过滤 rel 含 '/' 的目录:只落 root + 一级目录的 mtime;书/大小/格式记录仍全量; level-2+ 不落盘、载入不 stat。载入仍然 0 次 opendir/readdir(strace:0 getdents64, 真实库 4 次 stat;宿主模拟库 11 次)。 - 校验记录里出现 level-2+ rel 视为坏档;sum(books_here) 改为「≤ nbooks」合理性上界。 - fail-open/原子写/CRC/安全加固/ts 不缓存全部保留(r1 已过评审的部分未动)。 失效矩阵(报文逐条写清): 自动(根 mtime):Books 根下直接增/删/改名书;一级目录新增/删除/改名。 自动(一级目录 mtime):一级目录里直接增/删/改名书、新建子目录。 必须刷新:level-2 及更深处的任何变化(只改深层目录 mtime)。刷新=删索引+全扫。 真机 exFAT mtime 实测 **1 秒**(tm_nsec=0;同秒连续增删 mtime 不变)。我复测 10 轮 「目录刚改过就扫描、同秒再改」→ 9/10 仍命中(陈旧),与评审 29/30 一致;sleep 跨秒后 全部正常失效。头注释已把「10ms」改为「1 秒」,并写明「不在同一秒刻度的变更靠索引失效, 同秒二次变更需刷新」。 bench 修复(P3-3):MEOW_BENCH_IDX=only —— **不预先 load/scan**,直接进书架量生产首开; miss —— 先删索引再进书架。两者配合 drop_caches 即冷启动口径。旧行为(先 bench_index/ bench_scan 焐热)保留为默认模式。新增 test/shelf_index_probe_mipsel(write/scan/load/ loadts/statpaths/mtime/size)用于真机分项与 mtime 粒度验证,不进发行包。 测试:shelf_index_smoke 170 pass(新增:level-3/level-2 内变更「索引仍命中且先看到旧 列表 → 刷新后可见」、新建 level-2 目录自动失效、一级目录直接删书自动失效、v2 ndirs= 根+一级数);ASan+UBSan 干净;build_reader.sh 0 warning,core 6846 / seg 19 / chapter 71 / shelf_group 63 / index 170 / font 14 / loaders PASS。真机 6 场景逐项验证:一级直接增/ 新建子目录/一级改名/根下删书/一级直接删书 = 自动失效;level-3 深处新增 = 仍命中(需刷新)。 交付:app.json 0.1.5/code6 → **0.1.6/code7**;reader md5 5c848c99… 1,711,988B; mpk md5 10d676f5… 814,574B;未签名不 push。设备验证后已清理 /tmp 与 m32probe, 真实 MeowData 的 shelf.idx 已删(app 自己的章节 .idx 未动),Books 434/646 不变, 已装 0.1.4 md5 4036b889… 未变。
This commit is contained in:
1 parent
e0dfab95c3
commit
58f2d9bff4
10 files changed
+309
-57
No files matched your search
@@ -3,8 +3,9 @@
|
||||
* 覆盖:
|
||||
* I1 全量扫描 → 写索引 → 载入:书数/书名/分组/path/fmt/size/ts 全部与扫描一致
|
||||
* I2 载入不缓存阅读进度:同一索引换 ts_fn 再载入,ts 立刻反映新值(含真实 rdprog 文件)
|
||||
* I3 四种书库变更都必须失效(fail-open 重扫):
|
||||
* level-2 新增 / level-3 深处新增 / level-1 目录改名 / 删书
|
||||
* I3 v2 校验范围 = Books 根 + 一级目录:
|
||||
* 自动失效:一级目录直接增/删书、新建 level-2 目录、一级目录改名、根下增删书;
|
||||
* 必须刷新:level-3 深处新增、已有 level-2 目录内新增(索引仍命中,先看到旧列表)
|
||||
* I4 坏档 fail-open:截断 / 中间字节翻转 / 垃圾内容 / 文件缺失 / 索引路径是目录 /
|
||||
* 版本不符 / Books 根路径不符 —— 一律返回 0,不崩
|
||||
* I5 truncated / depth_skipped 如实重建(索引里存了这两个值)
|
||||
@@ -86,6 +87,21 @@ static int idx_path(const char *root, char *out, int outsz)
|
||||
return shelf_index_path(root, out, outsz);
|
||||
}
|
||||
|
||||
/* 读索引 trailer 的 ndirs(v2 应 = 1 + 一级目录数)。 */
|
||||
static uint32_t idx_trailer_ndirs(const char *root)
|
||||
{
|
||||
char ip[500];
|
||||
if (idx_path(root, ip, sizeof ip) != 0) return 0;
|
||||
FILE *f = fopen(ip, "rb");
|
||||
if (!f) return 0;
|
||||
unsigned char tr[24];
|
||||
if (fseek(f, -(long)sizeof tr, SEEK_END) != 0 ||
|
||||
fread(tr, 1, sizeof tr, f) != sizeof tr) { fclose(f); return 0; }
|
||||
fclose(f);
|
||||
return (uint32_t)tr[0] | ((uint32_t)tr[1] << 8) | ((uint32_t)tr[2] << 16) |
|
||||
((uint32_t)tr[3] << 24);
|
||||
}
|
||||
|
||||
static int scan_and_save(const char *root, shelf_book *books, int cap, shelf_scan_report *rep)
|
||||
{
|
||||
shelf_idx_writer *w = shelf_index_writer_open(root);
|
||||
@@ -134,6 +150,9 @@ static void test_roundtrip(const char *root)
|
||||
struct stat st;
|
||||
CHECK(stat(ip, &st) == 0 && st.st_size > 0, "索引文件已落盘");
|
||||
printf("index: %s (%lld bytes)\n", ip, (long long)st.st_size);
|
||||
/* v2:只落根 + 一级目录(本树一级 = 轻小说/玄幻/B/D/G/400+合集/空目录 → 7,加根 = 8)。
|
||||
* 这个数直接决定载入时的 stat 次数(真机冷缓存 4 次≈20ms,434 次=3.15s)。 */
|
||||
CHECK(idx_trailer_ndirs(root) == 8, "v2 目录记录 = Books 根 + 7 个一级目录");
|
||||
|
||||
int n2 = shelf_index_load(root, loadb, SHELF_MAX_BOOKS, ts_of, &r2);
|
||||
CHECK(n2 == 1, "索引命中");
|
||||
@@ -191,8 +210,21 @@ static void test_ts_not_cached(const char *root)
|
||||
}
|
||||
}
|
||||
|
||||
/* I3:四种变更都必须让索引失效。每次变更后:先断言 load=0(不静默用陈旧索引),
|
||||
* 再全量重扫 + 写新索引,断言变化可见。 */
|
||||
/* I3(v2 = 只校验 Books 根 + 一级目录):
|
||||
* 自动失效(load 返回 0 → 全扫):
|
||||
* ① 一级目录里直接新增书(一级目录 mtime 变)
|
||||
* ② 一级目录里新建子目录(一级目录 mtime 变)
|
||||
* ③ 一级目录改名(Books 根 mtime 变 + 旧目录 stat 失败)
|
||||
* ④ Books 根下删书(根 mtime 变)
|
||||
* ⑤ 一级目录里直接删书(一级目录 mtime 变)
|
||||
* 必须点「刷新」(load 仍命中,先看到旧列表):
|
||||
* ⑥ level-3 深处新增(只改更深目录的 mtime)
|
||||
* ⑦ 已有 level-2 目录里新增(只改 level-2 的 mtime)
|
||||
*
|
||||
* 注意:宿主 ext4/f2fs 的 mtime 有纳秒精度,usleep(20000) 足以拉开刻度;**真机 exFAT 的
|
||||
* 目录 mtime 只有 1 秒精度**(实测 tm_nsec=0),同秒内二次改动不会变——本测试无法模拟
|
||||
* 那个真机窗口(见 shelf_index.h 的 mtime 精度说明)。
|
||||
*/
|
||||
static void test_invalidation(const char *root)
|
||||
{
|
||||
static shelf_book b[SHELF_MAX_BOOKS];
|
||||
@@ -201,22 +233,22 @@ static void test_invalidation(const char *root)
|
||||
|
||||
CHECK(scan_and_save(root, b, SHELF_MAX_BOOKS, &rep) > 0, "基线扫描写索引");
|
||||
|
||||
/* ① level-2 直接新增 */
|
||||
/* ① 一级目录里直接新增(level-2 相对 Books 是直接子项) */
|
||||
usleep(20000);
|
||||
snprintf(p, sizeof p, "%s/轻小说/新书.txt", root); write_file(p, "new");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 0,
|
||||
"level-2 新增 → 索引失效");
|
||||
"一级目录直接新增 → 索引失效");
|
||||
int n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "新书") != NULL, "重扫后新书可见");
|
||||
|
||||
/* ② level-3 深处新增(Books/400+.../01、玄幻/01、斗破/) */
|
||||
/* ② 一级目录里新建 level-2 子目录(一级目录 mtime 变)→ 自动失效 */
|
||||
usleep(20000);
|
||||
snprintf(p, sizeof p, "%s/400+本高质量完本合集/01、玄幻/01、斗破/新书2.txt", root);
|
||||
write_file(p, "new2");
|
||||
snprintf(p, sizeof p, "%s/轻小说/新子目录", root); mkdirs(p);
|
||||
snprintf(p, sizeof p, "%s/轻小说/新子目录/深书.txt", root); write_file(p, "deep");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 0,
|
||||
"level-3 深处新增 → 索引失效");
|
||||
"新建 level-2 目录 → 索引失效");
|
||||
n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "新书2") != NULL, "重扫后深处新书可见");
|
||||
CHECK(find_title(b, n, "深书") != NULL, "重扫后新子目录里的书可见");
|
||||
|
||||
/* ③ 一级目录改名 */
|
||||
usleep(20000);
|
||||
@@ -230,14 +262,43 @@ static void test_invalidation(const char *root)
|
||||
CHECK(m && strcmp(m->top, "玄幻改") == 0 && strcmp(m->group, "玄幻改") == 0,
|
||||
"重扫后分组键跟随新目录名");
|
||||
|
||||
/* ④ 删书 */
|
||||
/* ④ Books 根下删书(根 mtime 变) */
|
||||
usleep(20000);
|
||||
snprintf(p, sizeof p, "%s/根下.txt", root);
|
||||
CHECK(unlink(p) == 0, "删除一本书");
|
||||
CHECK(unlink(p) == 0, "删除 Books 根下的一本书");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 0,
|
||||
"删书 → 索引失效");
|
||||
"根下删书 → 索引失效");
|
||||
n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "根下") == NULL, "重扫后删掉的书消失");
|
||||
|
||||
/* ⑤ 一级目录里直接删书(一级目录 mtime 变) */
|
||||
usleep(20000);
|
||||
snprintf(p, sizeof p, "%s/轻小说/2.epub", root);
|
||||
CHECK(unlink(p) == 0, "删除一级目录里直接放的书");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 0,
|
||||
"一级目录直接删书 → 索引失效");
|
||||
n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "2") == NULL, "重扫后该书消失");
|
||||
|
||||
/* ⑥ level-3 深处新增:**不自动失效**(level-1 与根 mtime 都没变)→ 需刷新 */
|
||||
snprintf(p, sizeof p, "%s/400+本高质量完本合集/01、玄幻/01、斗破/新书2.txt", root);
|
||||
write_file(p, "new2");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 1,
|
||||
"level-3 深处新增 → 索引仍命中(预期:需刷新)");
|
||||
CHECK(find_title(b, rep.nbooks, "新书2") == NULL, "命中时看不到深处新书(陈旧直到刷新)");
|
||||
shelf_index_remove(root); /* 顶栏「刷新」= 删索引 + 全扫 */
|
||||
n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "新书2") != NULL, "刷新后深处新书可见");
|
||||
|
||||
/* ⑦ 已有 level-2 目录里新增:**不自动失效** → 需刷新 */
|
||||
snprintf(p, sizeof p, "%s/B/sub/新书3.txt", root);
|
||||
write_file(p, "new3");
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 1,
|
||||
"level-2 目录内新增 → 索引仍命中(预期:需刷新)");
|
||||
CHECK(find_title(b, rep.nbooks, "新书3") == NULL, "命中时看不到 level-2 新书(陈旧直到刷新)");
|
||||
shelf_index_remove(root);
|
||||
n = scan_and_save(root, b, SHELF_MAX_BOOKS, &rep);
|
||||
CHECK(find_title(b, n, "新书3") != NULL, "刷新后 level-2 新书可见");
|
||||
}
|
||||
|
||||
static void test_bad_index(const char *root)
|
||||
@@ -299,12 +360,12 @@ static void test_bad_index(const char *root)
|
||||
CHECK(shelf_index_load(root, b, SHELF_MAX_BOOKS, ts_of, &rep) == 1, ".tmp 残渣不影响载入");
|
||||
unlink(tmpip);
|
||||
|
||||
/* 版本不符:先把版本字段改成 2 */
|
||||
/* 版本不符:先把版本字段改成 3 */
|
||||
CHECK(scan_and_save(root, b, SHELF_MAX_BOOKS, &rep) > 0, "重写索引");
|
||||
f = fopen(ip, "r+b");
|
||||
CHECK(f != NULL, "打开索引改版本");
|
||||
if (f) {
|
||||
unsigned char v[4] = { 2, 0, 0, 0 };
|
||||
unsigned char v[4] = { 3, 0, 0, 0 };
|
||||
CHECK(fseek(f, 8, SEEK_SET) == 0, "定位版本字段");
|
||||
fwrite(v, 1, sizeof v, f);
|
||||
fclose(f);
|
||||
|
||||
Reference in new issue
Block a user