From 2b4d3d2be9b318f883316ee3a2dc99ee8cebe7a2 Mon Sep 17 00:00:00 2001 From: AmuroEita <1071307515@qq.com> Date: Wed, 9 Sep 2026 12:37:33 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20art=5Fscan=20=E5=85=88=E4=B8=8B?= =?UTF-8?q?=E9=99=8D=E5=88=B0=E5=89=8D=E7=BC=80=E8=A6=86=E7=9B=96=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=86=8D=E6=89=AB=E5=AD=90=E6=A0=91=EF=BC=88#267?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit art_scan 从 art_root 整树 DFS 改为沿 pfx 下降到覆盖节点再扫子树。 List/DelTree/CpTree 均经 List,一处修改全线受益。 before/after(同一机器、同一 bench): List(/t/) ×400,噪声 /n/** 1000→8000 before 0.0066s → 0.0518s (7.87×,跟全树线性) after 0.0001s → 0.0001s (~1.0×) DelTree 小帧 /d/f,旁挂 8000 个 /lib key,200 次 before ~900 µs/次 after ~70 µs/次 (13.5×) callgrind 同 DelTree bench art_scan self 26.6% → 0.003% Deltree inclusive 41.8% → 0.40% kvlang shm 墙钟(master + v0.2.12 前端,只换 backend) fib(16) 2.11s → 0.42s (5.1×) fib(18) 6.36s → 1.00s (6.3×) nqueens(6) 0.38s → 0.09s (4.2×) binary_search(200) 0.51s → 0.17s (3.0×) --- CMakeLists.txt | 5 + src/kvspace.c | 52 ++++++++- tests/test_art_scan.c | 240 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 tests/test_art_scan.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ee599..d093160 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,4 +35,9 @@ if(KVSPACE_BUILD_TESTS) target_include_directories(test_shm_split PRIVATE src) target_link_libraries(test_shm_split PRIVATE kvspace-c) add_test(NAME shm_split COMMAND test_shm_split) + + add_executable(test_art_scan tests/test_art_scan.c) + target_include_directories(test_art_scan PRIVATE src) + target_link_libraries(test_art_scan PRIVATE kvspace-c) + add_test(NAME art_scan COMMAND test_art_scan) endif() diff --git a/src/kvspace.c b/src/kvspace.c index f43c773..a8dc807 100644 --- a/src/kvspace.c +++ b/src/kvspace.c @@ -681,6 +681,42 @@ static char *edir(const char *p) { r[l + 1] = '\0'; return r; } +/* Covering node for pfx: *bpos is reconstructed length at arrival. */ +static int32_t art_cover(kvspace_t *kv, int32_t nid, const uint8_t *pfx, + int plen, char *buf, int bcap, int *bpos) { + int d = 0; + if (nid < 0 || !pfx || plen < 0 || !buf || !bpos) + return -1; + while (nid >= 0) { + art_hdr_t *h = art_hdr(kv, nid); + if (!h) + return -1; + int pd = d, cover = 0; + if (h->prefix_len) { + int s = pfx_shared(h->prefix, h->prefix_len, pfx + d, plen - d); + if (s < h->prefix_len) { + if (d + s < plen) + return -1; + cover = 1; + } else + d += h->prefix_len; + } + if (cover || d == plen) { + if (pd >= bcap) + return -1; + if (pd) + memcpy(buf, pfx, (size_t)pd); + *bpos = pd; + return nid; + } + nid = art_child(kv, h, pfx[d]); + if (nid < 0) + return -1; + d++; + } + return -1; +} + /* ---- prefix scan: collect all keys under prefix into out[0..*n-1] ---- */ static void art_scan(kvspace_t *kv, int32_t nid, char *buf, int bpos, int bcap, const char *pfx, int plen, char ***out, int32_t *n) { @@ -749,6 +785,16 @@ static void art_scan(kvspace_t *kv, int32_t nid, char *buf, int bpos, int bcap, } } +static void art_scan_pfx(kvspace_t *kv, const char *pfx, int plen, char *buf, + int bcap, char ***out, int32_t *n) { + int bpos = 0; + int32_t nid = art_cover(kv, kv->hdr->art_root, (const uint8_t *)pfx, plen, + buf, bcap, &bpos); + if (nid < 0) + return; + art_scan(kv, nid, buf, bpos, bcap, pfx, plen, out, n); +} + /* ============ lifecycle ============ */ /* 8 * 64^k; s wraps to 0. */ static bool sbo_data_size_ok(size_t n) { @@ -1932,8 +1978,7 @@ int kvspaceShmList(kvspace_t *kv, const char *prefix, bool ex, int resolve, memset(buf, 0, sizeof(buf)); if (kv->hdr->art_root < 0) return 0; - art_scan(kv, kv->hdr->art_root, buf, 0, (int)sizeof(buf), pfx, plen, &out, - &n); + art_scan_pfx(kv, pfx, plen, buf, (int)sizeof(buf), &out, &n); // filter: only direct children (one level below prefix) char **filt = malloc(sizeof(char *) * n); int32_t fn = 0; @@ -1973,8 +2018,7 @@ int kvspaceShmList(kvspace_t *kv, const char *prefix, bool ex, int resolve, char ebuf[2048]; memset(ebuf, 0, sizeof ebuf); int el = (int)strlen(extpath); - art_scan(kv, kv->hdr->art_root, ebuf, 0, (int)sizeof ebuf, extpath, el, - &eo, &en); + art_scan_pfx(kv, extpath, el, ebuf, (int)sizeof ebuf, &eo, &en); filt = realloc(filt, sizeof(char *) * (size_t)(fn + en)); for (int i = 0; i < en; i++) { const char *k = eo[i]; diff --git a/tests/test_art_scan.c b/tests/test_art_scan.c new file mode 100644 index 0000000..ed61c66 --- /dev/null +++ b/tests/test_art_scan.c @@ -0,0 +1,240 @@ +/* test_art_scan — prefix List/DelTree/CpTree scan the covering subtree (#267) */ + +#define _GNU_SOURCE +#include "kvspace_shm.h" +#include +#include +#include +#include +#include + +#define SBO_DATA_SIZE (8UL * 64 * 64 * 64 * 64) + +static int failures = 0; +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + failures++; \ + fprintf(stderr, " FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } \ + } while (0) +#define REQUIRE(cond, ...) \ + do { \ + CHECK(cond, __VA_ARGS__); \ + if (!(cond)) \ + return; \ + } while (0) + +static double now_s(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return (double)t.tv_sec + (double)t.tv_nsec * 1e-9; +} + +static int set_int32(kvspace_t *kv, const char *key, int32_t v) { + uint8_t raw[4] = {(uint8_t)v, (uint8_t)(v >> 8), (uint8_t)(v >> 16), + (uint8_t)(v >> 24)}; + uint8_t *tlv; + int32_t n = kvspaceXvalueEncode(KVSPACE_KIND_INT32, raw, 4, NULL, 0, &tlv); + int rc = kvspaceShmSet(kv, key, tlv, n); + free(tlv); + return rc; +} + +static int32_t get_int32(kvspace_t *kv, const char *key) { + int32_t len = 0; + uint8_t *d = kvspaceShmGet(kv, key, 1, &len); + if (!d || len <= 0) + return -1; + xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); + return h.raw_len == 4 ? kvspaceXvalueRawInt32(h.raw) : -1; +} + +static void list_free(char **ns, int32_t n) { + if (!ns) + return; + for (int32_t i = 0; i < n; i++) + free(ns[i]); + free(ns); +} + +static int list_has(char **ns, int32_t n, const char *s) { + for (int32_t i = 0; i < n; i++) + if (strcmp(ns[i], s) == 0) + return 1; + return 0; +} + +static int do_list(kvspace_t *kv, const char *pfx, char ***on, int32_t *n) { + *on = NULL; + *n = 0; + return kvspaceShmList(kv, pfx, false, 0, on, n); +} + +static void t_correct(const char *db) { + printf("[correct] isolation, nested, compressed prefix, miss, deltree, cptree\n"); + kvspace_t *kv = kvspaceShmOpen(db, SBO_DATA_SIZE); + REQUIRE(kv != NULL, "open failed"); + + CHECK(set_int32(kv, "/lib/a", 1) == 0, "set /lib/a"); + CHECK(set_int32(kv, "/lib/b", 2) == 0, "set /lib/b"); + CHECK(set_int32(kv, "/lib/mod/x", 3) == 0, "set /lib/mod/x"); + CHECK(set_int32(kv, "/d/f/p", 4) == 0, "set /d/f/p"); + CHECK(set_int32(kv, "/d/f/q", 5) == 0, "set /d/f/q"); + CHECK(set_int32(kv, "/d/g", 6) == 0, "set /d/g"); + CHECK(set_int32(kv, "/foo/bar1", 7) == 0, "set /foo/bar1"); + CHECK(set_int32(kv, "/foo/bar2", 8) == 0, "set /foo/bar2"); + CHECK(set_int32(kv, "/foo/zzz", 9) == 0, "set /foo/zzz"); + + char **ns; + int32_t n; + + REQUIRE(do_list(kv, "/d/", &ns, &n) == 0, "list /d/"); + CHECK(n == 2, "list /d/ count %d", n); + CHECK(list_has(ns, n, "f") && list_has(ns, n, "g"), "list /d/ names"); + list_free(ns, n); + + REQUIRE(do_list(kv, "/d/f/", &ns, &n) == 0, "list /d/f/"); + CHECK(n == 2, "list /d/f/ count %d", n); + CHECK(list_has(ns, n, "p") && list_has(ns, n, "q"), "list /d/f/ names"); + list_free(ns, n); + + REQUIRE(do_list(kv, "/lib/", &ns, &n) == 0, "list /lib/"); + CHECK(n == 3, "list /lib/ count %d", n); + CHECK(list_has(ns, n, "a") && list_has(ns, n, "b") && list_has(ns, n, "mod"), + "list /lib/ names"); + list_free(ns, n); + + REQUIRE(do_list(kv, "/foo/", &ns, &n) == 0, "list /foo/"); + CHECK(n == 3, "list /foo/ count %d", n); + CHECK(list_has(ns, n, "bar1") && list_has(ns, n, "bar2") && + list_has(ns, n, "zzz"), + "list /foo/ names"); + list_free(ns, n); + + REQUIRE(do_list(kv, "/nope/", &ns, &n) == 0, "list /nope/"); + CHECK(n == 0, "list /nope/ count %d", n); + list_free(ns, n); + + REQUIRE(do_list(kv, "/food/", &ns, &n) == 0, "list /food/"); + CHECK(n == 0, "list /food/ count %d", n); + list_free(ns, n); + + REQUIRE(do_list(kv, "/", &ns, &n) == 0, "list /"); + CHECK(list_has(ns, n, "lib") && list_has(ns, n, "d") && list_has(ns, n, "foo"), + "list / names"); + list_free(ns, n); + + CHECK(kvspaceShmExtindex(kv, "/e/", "/lib/") == 0, "extindex"); + REQUIRE(kvspaceShmList(kv, "/e/", true, 0, &ns, &n) == 0, "list /e/ ex"); + CHECK(list_has(ns, n, "a") && list_has(ns, n, "b") && list_has(ns, n, "mod"), + "extindex children"); + list_free(ns, n); + + CHECK(kvspaceShmCptree(kv, "/d/f", "/cp") == 0, "cptree"); + REQUIRE(do_list(kv, "/cp/", &ns, &n) == 0, "list /cp/"); + CHECK(n == 2 && list_has(ns, n, "p") && list_has(ns, n, "q"), + "cptree children"); + list_free(ns, n); + CHECK(get_int32(kv, "/cp/p") == 4 && get_int32(kv, "/cp/q") == 5, + "cptree values"); + + CHECK(kvspaceShmDeltree(kv, "/d") == 0, "deltree /d"); + REQUIRE(do_list(kv, "/d/", &ns, &n) == 0, "list /d/ after deltree"); + CHECK(n == 0, "deltree left children %d", n); + list_free(ns, n); + CHECK(get_int32(kv, "/lib/a") == 1 && get_int32(kv, "/foo/bar1") == 7, + "deltree escaped sibling trees"); + CHECK(get_int32(kv, "/d/f/p") == -1, "deltree did not remove target"); + + kvspaceShmClose(kv); +} + +static int fill_noise(kvspace_t *kv, int from, int to) { + char key[32]; + int fail = 0; + for (int i = from; i < to; i++) { + snprintf(key, sizeof key, "/n/%02d/%03d", i / 100, i % 100); + fail += set_int32(kv, key, i) != 0; + } + return fail; +} + +static double time_list(kvspace_t *kv, const char *pfx, int reps) { + for (int i = 0; i < 20; i++) { + char **ns; + int32_t n; + do_list(kv, pfx, &ns, &n); + list_free(ns, n); + } + double t0 = now_s(); + for (int i = 0; i < reps; i++) { + char **ns; + int32_t n; + do_list(kv, pfx, &ns, &n); + list_free(ns, n); + } + return now_s() - t0; +} + +static void t_scale(const char *db) { + const int lo = 1000, hi = 8000, reps = 400; + printf("[scale] List(/t/) vs sibling tree size %d -> %d, %d reps\n", lo, hi, + reps); + kvspace_t *kv = kvspaceShmOpen(db, SBO_DATA_SIZE); + REQUIRE(kv != NULL, "open failed"); + + CHECK(set_int32(kv, "/t/a", 1) == 0, "set /t/a"); + CHECK(set_int32(kv, "/t/b", 2) == 0, "set /t/b"); + CHECK(set_int32(kv, "/t/c/x", 3) == 0, "set /t/c/x"); + CHECK(fill_noise(kv, 0, lo) == 0, "fill lo noise"); + + char **ns; + int32_t n; + REQUIRE(do_list(kv, "/t/", &ns, &n) == 0, "list /t/"); + CHECK(n == 3 && list_has(ns, n, "a") && list_has(ns, n, "b") && + list_has(ns, n, "c"), + "list /t/ names"); + list_free(ns, n); + + double t_lo = time_list(kv, "/t/", reps); + CHECK(fill_noise(kv, lo, hi) == 0, "fill hi noise"); + REQUIRE(do_list(kv, "/t/", &ns, &n) == 0, "list /t/ after noise"); + CHECK(n == 3, "list /t/ count after noise %d", n); + list_free(ns, n); + double t_hi = time_list(kv, "/t/", reps); + double ratio = t_lo > 0 ? t_hi / t_lo : 0; + + printf(" list /t/ %d noise: %.4fs\n", lo, t_lo); + printf(" list /t/ %d noise: %.4fs ratio=%.2f (must not track noise)\n", hi, + t_hi, ratio); + CHECK(ratio < 2.5, "List(/t/) scaled with sibling size: %.2fx", ratio); + + kvspaceShmClose(kv); +} + +int main(int argc, char **argv) { + char tmpl[] = "/tmp/kvspace-artscan-XXXXXX"; + const char *dir = argc > 1 ? argv[1] : mkdtemp(tmpl); + if (!dir) { + perror("mkdtemp"); + return 2; + } + char db[512], db2[512]; + snprintf(db, sizeof db, "%s/db", dir); + snprintf(db2, sizeof db2, "%s/db2", dir); + + t_correct(db); + t_scale(db2); + + if (argc <= 1) { + char cmd[700]; + snprintf(cmd, sizeof cmd, "rm -rf '%s'", dir); + if (system(cmd) != 0) + fprintf(stderr, "cleanup failed: %s\n", dir); + } + printf(failures ? "FAILED (%d)\n" : "OK\n", failures); + return failures ? 1 : 0; +}