M29 只解决了「回书架不重扫」;首次进书架仍要全量扫 434 目录(真机 3.0–3.4s, 其中 opendir+readdir 占 96%)。本单把扫描结果 + 每个扫描目录的 mtime 存到 MeowData/os.meow.reader/index/shelf.idx,冷启动逐目录 stat 比对 mtime,命中即 完全跳过 readdir;任何异常一律 fail-open 全量重扫。 先量再选(任务 1):宿主 434 目录/595 本/51 rar 虚拟库(贴真机量级) - 434 次目录 stat = 0.48ms/pass(1.1µs/次,不含 readdir); - 读 72KB 索引 = 0.009ms;全量扫描 3.54ms;索引命中 1.29ms(ts_fn=NULL)。 真机按 M28 实测 1079 次 stat=160–170ms(≈150µs/次)外推:434 次 ≈ 65ms, 远低于「可能不可行」的 1s 阈值 → 选「校验全部扫描目录 mtime」(非只 level-1)。 strace 断言:indexonly 载入 0 次 getdents64、恰好 434 次 stat(真的没 readdir)。 生产路径 ≈ 65ms(stat) + <20ms(索引读) + 40–50ms(rdprog×595) + <20ms(build_rows) ≈ 0.13–0.15s(对比 3.0s)。 索引格式 v1(手工小端序列化,不依赖结构体布局): header(magic/version/header_size/root_len) + root + dir 记录×(rel,mtime_s, mtime_ns,books_here) + book 记录×(rel,size,fmt) + trailer(ndirs,nbooks,flags, depth_skipped,crc32,magic)。truncated/depth_skipped 存在 flags/字段里,载入时 如实重建提示条;**不存阅读进度 ts**,载入后每本由 rdprog_load 现读再排序。 失效语义(存每个扫描过的目录,不只 level-1): - level-2 新增 / level-3 深处新增 / 删书 → 该目录 mtime 变 → 重扫; - 一级目录改名 → 父目录 mtime 变 + 旧目录 stat ENOENT → 重扫; - 超深(>6 层)未扫描子树不在索引内,其内部变化不自动失效(本来就不显示), 点「刷新」更新 depth_skipped 计数;mtime 精度受限(exFAT 10ms/FAT32 2s)的 同刻度变更也可能漏判,点「刷新」必可纠正。 M29 语义不变:首次进书架=索引或全扫;回书架=不扫;刷新=删索引+全扫(之后原子 写回新索引)。 安全/健壮(宁可慢不可错): - 写:shelf.idx.tmp 全量写完(尽力 fsync)后 rename;中途被杀只剩 tmp,正式索引 要么旧要么完整新;rename 覆盖失败先删旧再改名;fflush/写错即放弃,绝不影响书架。 - 读:O_NOFOLLOW|O_NONBLOCK + S_ISREG(FIFO/符号链接直接拒绝不阻塞);CRC32 覆盖 全部记录;rel 做绝对路径/“..”/内嵌 NUL/长度校验;版本/根路径/计数/逐目录 mtime 任一不符即重扫;载入崩溃路径全部 goto 清理,不改书架可用性。 - writer_open 失败(卡满/写保护/无 MeowData)→ 扫描不采集目录、照常出书; commit 失败(含只读目录 rename 失败)不留正式索引。 测试:test/shelf_index_smoke 154 pass(含四种变更场景失效、截断/字节翻转/垃圾/ 缺失/FIFO/符号链接/版本不符/根路径不符、ts 现读、truncated/depth_skipped 重建、 只读目录写失败、空 Books、root 缺失、.tmp 残渣),ASan+UBSan 干净; test/shelf_index_bench 模拟 434 目录/595 本/51 rar:scan/load 均 595 本、 truncated=0/depth_skipped=0、索引 72,156B。build_reader.sh 全绿 0 warning (core 6846 / seg 19 / chapter 71 / shelf_group 63 / index 154 / font 14 / loaders PASS)。shelf_bench_ondev 增加 index_load/scan_save 计时与 MEOW_BENCH_IDX=miss(真机首开命中 vs 未命中对比,待有卡复核)。 交付:app.json 0.1.4/code5 → 0.1.5/code6;reader md5 df4cadcf 1,711,924B;mpk md5 fec7b1bb 814,535B;未签名不推卡。
155 lines
6.7 KiB
C
155 lines
6.7 KiB
C
/* test/shelf_bench_ondev.c — 真机耗时基准(不进发行包,不参与 mpk)。
|
||
*
|
||
* 目的:在**同一台设备、同一字库/主题环境**下量 App 的同步耗时,用来给书架缓存 /
|
||
* 行自绘 / 开书路径做前后对比(宿主数字不算数)。
|
||
*
|
||
* 只调 App/SDK 公共入口(与 app.c 的调用顺序一致),不复制任何内部实现:
|
||
* meow_ui_init → 建三页 → push 书架(= 首次进书架,含扫描)
|
||
* → push/pop 阅读页 ×N(= 从阅读页返回书架)
|
||
* → shelf_on_swipe ×N(= 书架滑动,含 widget_draw + flush 上屏)
|
||
* → reader_open + push 阅读页(= 点书开读首屏)、翻页
|
||
*
|
||
* 用法(设备):shelf_bench_ondev [book1 [book2 ...]]
|
||
* 输出:`BENCH <key> <min>-<max> (avg ...)` 行,ms 为毫秒(设备时钟 10ms 分辨率,
|
||
* 单次量用多轮取样给区间;批量操作用「总时长/次数」给出平均值)。
|
||
*/
|
||
#include "reader_internal.h"
|
||
#include "app/shelf_model.h"
|
||
#include "app/shelf_index.h"
|
||
#include "meow_ui_internal.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
|
||
static uint64_t now_ms(void) { return meow_ui_time_ms(); }
|
||
|
||
/* M32:索引命中/未命中的耗时(不含 UI)。先在本次运行开头测:
|
||
* - index_load:读到上次运行写下的索引(首次运行 miss);
|
||
* - scan_save :全量扫描 + 落盘(生产「无索引首次进入」)。
|
||
* 之后 shelf_on_enter 会走生产路径:有索引=命中(默认),MEOW_BENCH_IDX=miss 时先删索引。 */
|
||
static void bench_index(void)
|
||
{
|
||
static shelf_book books[SHELF_MAX_BOOKS];
|
||
shelf_scan_report rep;
|
||
uint64_t t0 = now_ms();
|
||
int hit = shelf_index_load(READER_SD_ROOT "/Books", books, SHELF_MAX_BOOKS, NULL, &rep);
|
||
uint64_t t1 = now_ms();
|
||
printf("BENCH index_load_ms %llu (hit=%d n=%d trunc=%d skip=%d)\n",
|
||
(unsigned long long)(t1 - t0), hit, rep.nbooks, rep.truncated, rep.depth_skipped);
|
||
|
||
t0 = now_ms();
|
||
shelf_idx_writer *w = shelf_index_writer_open(READER_SD_ROOT "/Books");
|
||
int n = shelf_scan_books_ex(READER_SD_ROOT "/Books", books, SHELF_MAX_BOOKS, NULL, &rep,
|
||
w ? shelf_index_writer_dir : NULL, w);
|
||
int ok = w ? shelf_index_writer_commit(w, books, n, &rep) : 0;
|
||
t1 = now_ms();
|
||
char ip[500];
|
||
struct stat st;
|
||
long long isz = (shelf_index_path(READER_SD_ROOT "/Books", ip, sizeof ip) == 0 &&
|
||
stat(ip, &st) == 0) ? (long long)st.st_size : -1;
|
||
printf("BENCH scan_save_ms %llu (n=%d commit=%d index_bytes=%lld)\n",
|
||
(unsigned long long)(t1 - t0), n, ok, isz);
|
||
fflush(stdout);
|
||
}
|
||
|
||
static void bench_scan(void)
|
||
{
|
||
static shelf_book books[SHELF_MAX_BOOKS];
|
||
shelf_scan_report rep;
|
||
uint64_t t0 = now_ms();
|
||
int n = shelf_scan_books(READER_SD_ROOT "/Books", books, SHELF_MAX_BOOKS, NULL, &rep);
|
||
uint64_t t1 = now_ms();
|
||
printf("BENCH scan_ms %llu (n=%d truncated=%d depth_skipped=%d root_missing=%d)\n",
|
||
(unsigned long long)(t1 - t0), n, rep.truncated, rep.depth_skipped, rep.root_missing);
|
||
fflush(stdout);
|
||
}
|
||
|
||
int main(int argc, char **argv)
|
||
{
|
||
meow_ui_opts o = { 480, 720 };
|
||
if (meow_ui_init(&o) != 0) { printf("BENCH init_fail\n"); return 1; }
|
||
meow_theme_add_listener(NULL);
|
||
|
||
bench_index(); /* M32:索引命中/未命中 + 扫描落盘(不含 UI) */
|
||
bench_scan(); /* 纯扫描基线(不建 UI) */
|
||
|
||
/* 首次进书架:默认走索引命中;MEOW_BENCH_IDX=miss 先删索引 → 走全量扫描(对比用)。 */
|
||
const char *eidx = getenv("MEOW_BENCH_IDX");
|
||
if (eidx && strcmp(eidx, "miss") == 0) {
|
||
shelf_index_remove(READER_SD_ROOT "/Books");
|
||
printf("BENCH index_removed (miss path)\n");
|
||
fflush(stdout);
|
||
}
|
||
|
||
shelf_view_create();
|
||
reader_view_create();
|
||
settings_view_create();
|
||
|
||
/* ① 首次进书架(新进程:含全量扫描 + 建控件 + 首帧上屏) */
|
||
uint64_t t0 = now_ms();
|
||
meow_view_page_push(ACT_SHELF);
|
||
uint64_t t1 = now_ms();
|
||
printf("BENCH shelf_first_enter_ms %llu\n", (unsigned long long)(t1 - t0));
|
||
|
||
/* ② 从阅读页返回书架:push 阅读页(不等书)→ pop 回书架;连做 5 次给区间 */
|
||
unsigned long long rmin = 0, rmax = 0, rsum = 0;
|
||
for (int i = 0; i < 5; i++) {
|
||
meow_view_page_push(ACT_READER);
|
||
uint64_t a = now_ms();
|
||
meow_view_page_pop();
|
||
uint64_t b = now_ms();
|
||
uint64_t d = b - a;
|
||
if (i == 0 || d < rmin) rmin = d;
|
||
if (i == 0 || d > rmax) rmax = d;
|
||
rsum += d;
|
||
}
|
||
printf("BENCH shelf_return_ms %llu-%llu (avg %llu)\n",
|
||
rmin, rmax, rsum / 5);
|
||
|
||
/* ③ 书架滑动:每步 62px(与 survey 同口径:scroll 62 + widget_draw + flush) */
|
||
for (int batch = 0; batch < 3; batch++) {
|
||
uint64_t a = now_ms();
|
||
for (int i = 0; i < 30; i++) shelf_on_swipe(MEOW_SWIPE_UP, 0, 62);
|
||
uint64_t b = now_ms();
|
||
printf("BENCH shelf_scroll_step_ms %.1f (batch %d: 30 steps %llu ms)\n",
|
||
(double)(b - a) / 30.0, batch, (unsigned long long)(b - a));
|
||
for (int i = 0; i < 30; i++) shelf_on_swipe(MEOW_SWIPE_DOWN, 0, 62); /* 回到顶部 */
|
||
}
|
||
fflush(stdout);
|
||
|
||
/* ④ 开书首屏 / 翻页(argv 给书路径;不给则跳过) */
|
||
for (int i = 1; i < argc; i++) {
|
||
uint64_t a = now_ms();
|
||
int rc = reader_open(argv[i]);
|
||
uint64_t b = now_ms();
|
||
if (rc != 0) { printf("BENCH open_fail %s\n", argv[i]); continue; }
|
||
meow_view_page_push(ACT_READER);
|
||
uint64_t c = now_ms();
|
||
printf("BENCH open_ms %llu (reader_open %llu + page_push %llu) %s\n",
|
||
(unsigned long long)(c - a), (unsigned long long)(b - a),
|
||
(unsigned long long)(c - b), argv[i]);
|
||
/* 翻页:reader_page_key 置 pending → reader_on_tick 消费(同步段=turn+refill+redraw)
|
||
* 轮数由 MEOW_BENCH_TURNS 控制(0=不翻页 → 用于「开书不翻页进度文件不变」回归)。 */
|
||
const char *et = getenv("MEOW_BENCH_TURNS");
|
||
int nturns = et && et[0] ? atoi(et) : 4;
|
||
unsigned long long tmin = 0, tmax = 0, tsum = 0;
|
||
for (int p = 0; p < nturns; p++) {
|
||
reader_page_key(1);
|
||
uint64_t x = now_ms();
|
||
reader_on_tick(now_ms());
|
||
uint64_t y = now_ms();
|
||
uint64_t d = y - x;
|
||
if (p == 0 || d < tmin) tmin = d;
|
||
if (p == 0 || d > tmax) tmax = d;
|
||
tsum += d;
|
||
}
|
||
printf("BENCH turn_ms %llu-%llu (avg %llu) %s\n", tmin, tmax, nturns ? tsum / nturns : 0, argv[i]);
|
||
meow_view_page_pop();
|
||
fflush(stdout);
|
||
}
|
||
|
||
meow_ui_shutdown();
|
||
return 0;
|
||
}
|