/* test_browse_perf.c — 单层扫描 / 行合成性能与有界性验收(纯 C,无 SDK)。 * * 夹具:500 个顶层目录 × 8 层嵌套(4000 个子目录)+ 根目录 2000 个普通文件 * + 一个 big/ 目录(2000 张图片,验证 2000 项大目录)。 * 断言(对应 M18 任务③): * P1 首帧扫描只做单层:opendir 次数 == 1,lstat 次数 == 本层条目数,耗时有界; * P2 2000 项大目录:一次扫描出全部 2000 行,opendir=1、lstat=2000,耗时有界; * P3 行合成(UI src 的数据面)纯内存、零 IO、耗时有界(2000 行 × 1000 遍); * P4 进入深层/普通子目录同样只打开它自己(进入目录成本 = 该目录一层)。 * * 编译(在 tuku/ 下): * gcc -Wall -Wextra -O1 -I. -I../sdk -DMR_LIBRARY_TEST_HOOKS \ * test/test_browse_perf.c logic/library.c -pthread -o build/host/test_browse_perf */ #include "logic/library.h" #include #include #include #include #include #include #include #define PERF_DIRS 500 #define PERF_DEPTH 8 #define PERF_FILES 2000 #define PERF_BIG 2000 static int g_pass, g_fail; #define CHECK(cond, ...) do { \ if (cond) { g_pass++; } \ else { g_fail++; printf("FAIL %s:%d ", __FILE__, __LINE__); printf(__VA_ARGS__); printf("\n"); } \ } while (0) static int mkdir_p(const char *path) { char tmp[MR_PATH_MAX]; snprintf(tmp, sizeof tmp, "%s", path); for (size_t i = 1; i <= strlen(tmp); i++) { if (tmp[i] == '/' || tmp[i] == '\0') { char ch = tmp[i]; tmp[i] = '\0'; if (mkdir(tmp, 0755) != 0) { struct stat st; if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)) return -1; } tmp[i] = ch; } } return 0; } static int touch_file(const char *path) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs("x", f); return fclose(f); } static int rm_rf(const char *path) { struct stat st; if (lstat(path, &st) != 0) return -1; if (!S_ISDIR(st.st_mode)) return unlink(path); DIR *d = opendir(path); if (!d) return -1; struct dirent *de; while ((de = readdir(d)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; char full[MR_PATH_MAX]; snprintf(full, sizeof full, "%s/%s", path, de->d_name); rm_rf(full); } closedir(d); return rmdir(path); } static long long now_us(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (long long)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL; } int main(void) { char tmpl[] = "./mr_perf_XXXXXX"; char *root = mkdtemp(tmpl); CHECK(root != NULL, "mkdtemp"); if (!root) return 1; char p[MR_PATH_MAX]; /* 根目录:2000 个普通文件(不成行)+ 500 个顶层目录(每个顶层目录内是 8 层嵌套 + 1 张图)。 */ for (int f = 0; f < PERF_FILES; f++) { snprintf(p, sizeof p, "%s/f%04d.txt", root, f); if (touch_file(p) != 0) { CHECK(0, "file %s", p); break; } } for (int d = 0; d < PERF_DIRS; d++) { char deep[MR_PATH_MAX]; snprintf(deep, sizeof deep, "%s/d%04d", root, d); if (mkdir_p(deep) != 0) { CHECK(0, "mkdir %s", deep); break; } snprintf(p, sizeof p, "%s/001.png", deep); if (touch_file(p) != 0) { CHECK(0, "touched top image"); break; } for (int l = 1; l <= PERF_DEPTH; l++) { char sub[MR_PATH_MAX]; snprintf(sub, sizeof sub, "%s/l%d", deep, l); if (mkdir(sub, 0755) != 0) { CHECK(0, "mkdir %s", sub); break; } snprintf(deep, sizeof deep, "%s", sub); } snprintf(p, sizeof p, "%s/001.png", deep); if (touch_file(p) != 0) { CHECK(0, "touched deep image"); break; } } /* 2000 项大目录(图片)。 */ char big[MR_PATH_MAX]; snprintf(big, sizeof big, "%s/big", root); CHECK(mkdir_p(big) == 0, "mkdir big"); for (int i = 0; i < PERF_BIG; i++) { snprintf(p, sizeof p, "%s/img%04d.png", big, i); if (touch_file(p) != 0) { CHECK(0, "big image %s", p); break; } } /* ---- P1:首帧扫描只做单层 ---- */ mr_library lib; memset(&lib, 0, sizeof lib); mr_library_test_io_reset(); long long t0 = now_us(); int n = mr_library_scan(&lib, root, MR_LIB_MAX_DEPTH); long long scan_us = now_us() - t0; mr_library_io_stats st; mr_library_test_io_get(&st); CHECK(n == PERF_DIRS + 1, "scan lists %d dirs (got %d)", PERF_DIRS + 1, n); CHECK(lib.truncated == 0, "not truncated"); CHECK(st.opendirs == 1, "single-layer scan: opendir=%ld (want 1)", st.opendirs); CHECK(st.lstats == PERF_FILES + PERF_DIRS + 1, "lstat only current level: %ld (want %d)", st.lstats, PERF_FILES + PERF_DIRS + 1); CHECK(scan_us < 1500000LL, "scan bounded: %lldus", scan_us); if (lib.count == PERF_DIRS + 1) { CHECK(strcmp(lib.items[0].name, "big") == 0, "first=%s", lib.items[0].name); CHECK(strcmp(lib.items[lib.count - 1].name, "d0499") == 0, "last=%s", lib.items[lib.count - 1].name); } printf(" root scan: %lldus, %d rows, opendir=%ld lstat=%ld\n", scan_us, n, st.opendirs, st.lstats); mr_library_free(&lib); /* ---- P2:2000 项大目录(进入目录 = 单层扫描) ---- */ mr_library biglib; memset(&biglib, 0, sizeof biglib); mr_library_test_io_reset(); long long b0 = now_us(); int bn = mr_library_scan(&biglib, big, MR_LIB_MAX_DEPTH); long long big_us = now_us() - b0; mr_library_test_io_get(&st); CHECK(bn == PERF_BIG, "big dir rows=%d (want %d)", bn, PERF_BIG); CHECK(biglib.truncated == 0, "big dir not truncated at cap"); CHECK(st.opendirs == 1, "big dir scan: opendir=%ld", st.opendirs); CHECK(st.lstats == PERF_BIG, "big dir scan: lstat=%ld (want %d)", st.lstats, PERF_BIG); CHECK(big_us < 1500000LL, "big dir scan bounded: %lldus", big_us); if (biglib.count == PERF_BIG) { CHECK(strcmp(biglib.items[0].name, "img0000.png") == 0, "first=%s", biglib.items[0].name); CHECK(strcmp(biglib.items[PERF_BIG - 1].name, "img1999.png") == 0, "last=%s", biglib.items[PERF_BIG - 1].name); CHECK(biglib.items[PERF_BIG - 1].kind == MR_LIB_KIND_IMAGE, "last is image"); } printf(" big dir scan: %lldus, %d rows, opendir=%ld lstat=%ld\n", big_us, bn, st.opendirs, st.lstats); /* ---- P3:行合成(UI src 数据面)纯内存、2000 行 × 1000 遍 ---- */ mr_library_test_io_reset(); long long r0 = now_us(); int rows = 0; for (int r = 0; r < 1000; r++) for (int i = 0; i < biglib.count; i++) { mr_library_row row; if (mr_library_row_at(&biglib, i, &row)) rows++; } long long row_us = now_us() - r0; mr_library_test_io_get(&st); CHECK(rows == 1000 * biglib.count, "rows synthesized (%d)", rows); CHECK(st.opendirs == 0 && st.readdirs == 0 && st.lstats == 0, "row synthesis does no IO (open=%ld rd=%ld stat=%ld)", st.opendirs, st.readdirs, st.lstats); CHECK(row_us < 1000000LL, "row synthesis bounded: %lldus", row_us); printf(" rows: %lldus for %d rows, zero IO\n", row_us, rows); mr_library_free(&biglib); /* ---- P4:进入子目录只打开它自己(不递归子树) ---- */ char deep[MR_PATH_MAX]; snprintf(deep, sizeof deep, "%s/d0000", root); for (int l = 1; l <= PERF_DEPTH; l++) { char sub[MR_PATH_MAX]; snprintf(sub, sizeof sub, "%s/l%d", deep, l); snprintf(deep, sizeof deep, "%s", sub); } mr_library_test_io_reset(); mr_library deep_lib; memset(&deep_lib, 0, sizeof deep_lib); CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 1, "deep leaf has one image"); mr_library_test_io_get(&st); CHECK(st.opendirs == 1, "deep scan opens once (got %ld)", st.opendirs); CHECK(st.lstats == 1, "deep scan lstats only its image (got %ld)", st.lstats); mr_library_free(&deep_lib); /* 顶层目录:一个子目录 + 一张图 = 2 行,只打开它自己。 */ snprintf(deep, sizeof deep, "%s/d0000", root); memset(&deep_lib, 0, sizeof deep_lib); mr_library_test_io_reset(); CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 2, "top dir rows"); mr_library_test_io_get(&st); CHECK(st.opendirs == 1 && st.lstats == 2, "top dir scan: open=%ld stat=%ld", st.opendirs, st.lstats); mr_library_free(&deep_lib); rm_rf(root); printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail); return g_fail ? 1 : 0; }