diff --git a/CMakeLists.txt b/CMakeLists.txt index 566bba2..2679e4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,4 +50,9 @@ if(KVSPACE_BUILD_TESTS) 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) + + add_executable(test_handle_cache tests/test_handle_cache.c) + target_include_directories(test_handle_cache PRIVATE src) + target_link_libraries(test_handle_cache PRIVATE kvspace-c) + add_test(NAME handle_cache COMMAND test_handle_cache) endif() diff --git a/src/durable_abi.c b/src/durable_abi.c index 84f2611..de51f20 100644 --- a/src/durable_abi.c +++ b/src/durable_abi.c @@ -73,6 +73,36 @@ int kvspaceGet(void *h, const char *key, int resolve, uint8_t **out, return 0; } +int kvspaceResolveRef(void *h, const char *key, kvspaceRef_t *ref) { + return kvspaceShmResolveRef((kvspace_t *)h, key, ref); +} + +int kvspaceGetByRef(void *h, kvspaceRef_t *ref, const char *key_fallback, + uint8_t **out, uint32_t *out_len) { + int32_t len = 0; + uint8_t *d = kvspaceShmGetByRef((kvspace_t *)h, ref, key_fallback, &len); + if (!d || len <= 0) { + *out = NULL; + *out_len = 0; + return 0; + } + *out = d; + *out_len = (uint32_t)len; + return 0; +} + +int kvspaceSetPartByRef(void *h, kvspaceRef_t *ref, const char *key_fallback, + uint32_t offset, const uint8_t *buf, uint32_t buf_len, + char *err, uint32_t err_cap) { + if (kvspaceShmSetPartByRef((kvspace_t *)h, ref, key_fallback, offset, buf, + buf_len) != 0) { + if (err && err_cap) + snprintf(err, err_cap, "kvspace: set-part-by-ref failed"); + return 1; + } + return 0; +} + /* 指令边界回收读借用池:SHM 常驻映射,借用恒有效,no-op。 */ void kvspaceReadReset(void *h) { (void)h; } diff --git a/src/kvspace.c b/src/kvspace.c index 942e107..6b9446e 100644 --- a/src/kvspace.c +++ b/src/kvspace.c @@ -35,7 +35,8 @@ enum { ART_N4 = 0, ART_N16 = 1, ART_N48 = 2, - ART_N256 = 3 }; + ART_N256 = 3, + ART_MOVED = 255 }; typedef struct { uint8_t type, prefix[ART_PREFIX_MAX], prefix_len; @@ -385,34 +386,135 @@ static int32_t art_child(kvspace_t *kv, void *n, uint8_t b) { return -1; } -/* ---- art_search ---- */ -static art_hdr_t *art_search(kvspace_t *kv, int32_t nid, const uint8_t *key, - int klen) { - if (nid < 0 || !key) - return NULL; - int d = 0; +static int32_t art_follow(kvspace_t *kv, int32_t id); + +static int32_t art_walk(kvspace_t *kv, int32_t nid, const uint8_t *key, int klen, + int d, int32_t *par, int *pard) { while (nid >= 0) { art_hdr_t *h = art_hdr(kv, nid); - if (!h) - return NULL; + if (!h || h->type == ART_MOVED) + return -1; if (h->prefix_len) { int s = pfx_shared(h->prefix, h->prefix_len, key + d, klen - d); if (s != h->prefix_len) { if (d + s < klen) - return NULL; + return -1; if (s < h->prefix_len) - return NULL; + return -1; } d += h->prefix_len; if (d > klen) - return NULL; + return -1; } if (d == klen) - return h->has_value ? h : NULL; + return h->has_value ? nid : -1; + if (par) + *par = nid; + if (pard) + *pard = d; nid = art_child(kv, h, key[d]); d++; } - return NULL; + return -1; +} + +/* ---- art_search ---- */ +static int32_t art_find2(kvspace_t *kv, int32_t nid, const uint8_t *key, int klen, + int32_t *par, int *pard) { + if (nid < 0 || !key) + return -1; + int32_t parent = -1; + int parent_d = 0; + int32_t id = art_walk(kv, nid, key, klen, 0, &parent, &parent_d); + if (par) + *par = parent; + if (pard) + *pard = parent_d; + return id; +} +static int32_t art_find(kvspace_t *kv, int32_t nid, const uint8_t *key, + int klen) { + return art_find2(kv, nid, key, klen, NULL, NULL); +} + +/* Last path '/' or kvlang member '·' (U+00B7, utf-8 C2 B7). */ +static int last_key_sep(const uint8_t *key, int klen, int *seplen) { + int slash = -1, mid = -1; + for (int i = 0; i < klen; i++) { + if (key[i] == '/') + slash = i; + if (i + 1 < klen && key[i] == 0xC2 && key[i + 1] == 0xB7) + mid = i; + } + if (mid > slash) { + *seplen = 2; + return mid; + } + if (slash > 0) { + *seplen = 1; + return slash; + } + *seplen = 0; + return -1; +} + +/* One walk: leaf plus the ancestor covering last '/' or '·' (after that + * node's prefix). First match: node at the separator, not a deeper unique + * prefix that swallowed it. */ +static int32_t art_find_dir(kvspace_t *kv, int32_t nid, const uint8_t *key, + int klen, int last_sep, int seplen, int32_t *dirn, + int *dird) { + int d = 0; + int32_t dir = -1; + int dd = 0; + if (nid < 0 || !key) + return -1; + while (nid >= 0) { + art_hdr_t *h = art_hdr(kv, nid); + if (!h || h->type == ART_MOVED) + return -1; + int entry_d = d; + if (h->prefix_len) { + int s = pfx_shared(h->prefix, h->prefix_len, key + d, klen - d); + if (s != h->prefix_len) + return -1; + d += h->prefix_len; + if (d > klen) + return -1; + } + if (dir < 0 && last_sep > 0 && seplen > 0 && + ((entry_d <= last_sep && last_sep < d) || + d == last_sep + seplen)) { + dir = nid; + dd = d; + } + if (d == klen) { + if (dirn) + *dirn = dir; + if (dird) + *dird = dd; + return h->has_value ? nid : -1; + } + nid = art_child(kv, h, key[d]); + d++; + } + return -1; +} +static art_hdr_t *art_search(kvspace_t *kv, int32_t nid, const uint8_t *key, + int klen) { + int32_t id = art_find(kv, nid, key, klen); + return id < 0 ? NULL : art_hdr(kv, id); +} +static int32_t art_follow(kvspace_t *kv, int32_t id) { + for (int i = 0; i < 8 && id >= 0; i++) { + art_hdr_t *h = art_hdr(kv, id); + if (!h) + return -1; + if (h->type != ART_MOVED) + return id; + id = (int32_t)h->box_offset; + } + return -1; } /* ---- node create ---- */ @@ -526,6 +628,9 @@ static int32_t art_grow(kvspace_t *kv, void *on) { x->children[j] = tc; } } + oh->type = ART_MOVED; + oh->has_value = 0; + oh->box_offset = (uint64_t)(uint32_t)nid; return nid; } @@ -1295,6 +1400,115 @@ uint8_t *kvspaceShmGet(kvspace_t *kv, const char *key, int resolve, return raw; } +/* gen>0: block_id is parent at depth gen; key must share that prefix. */ +static int32_t ref_leaf(kvspace_t *kv, kvspaceRef_t *ref, const char *key) { + int32_t id = art_follow(kv, (int32_t)ref->block_id); + if (id < 0) + return -1; + ref->block_id = (uint32_t)id; + if (ref->gen == 0) + return id; + if (!key) + return -1; + int klen = (int)strlen(key); + int d = (int)ref->gen; + if (d < 0 || d >= klen) + return -1; + art_hdr_t *h = art_hdr(kv, id); + if (!h || h->type == ART_MOVED) + return -1; + int32_t cid = art_child(kv, h, (uint8_t)key[d]); + if (cid < 0) + return -1; + return art_walk(kv, cid, (const uint8_t *)key, klen, d + 1, NULL, NULL); +} + +int kvspaceShmResolveRef(kvspace_t *kv, const char *key, kvspaceRef_t *ref) { + if (!kv || !key || !ref) + return -1; + memset(ref, 0, sizeof(*ref)); + if (kv_sync(kv) != 0) + return -1; + int klen = (int)strlen(key); + int seplen = 0; + int sep = last_key_sep((const uint8_t *)key, klen, &seplen); + int dir_d = 0; + int32_t dn = -1; + int32_t id = art_find_dir(kv, kv->hdr->art_root, (const uint8_t *)key, klen, + sep, seplen, &dn, &dir_d); + if (id < 0) + return -1; + ref->block_id = (uint32_t)id; + ref->gen = 0; + if (dn >= 0 && dir_d > 0) { + ref->parent_id = (uint32_t)dn; + ref->depth = (uint32_t)dir_d; + } else { + ref->parent_id = 0; + ref->depth = 0; + } + return 0; +} + +uint8_t *kvspaceShmGetByRef(kvspace_t *kv, kvspaceRef_t *ref, + const char *key_fallback, int32_t *ol) { + if (!kv || !ref || !ol) + return NULL; + *ol = 0; + if (kv_sync(kv) != 0) + return NULL; + int32_t id = ref_leaf(kv, ref, key_fallback); + art_hdr_t *h = id >= 0 ? art_hdr(kv, id) : NULL; + if (h && h->has_value) { + uint8_t *raw; + int32_t rl; + if (read_tlv(kv, h->box_offset, &raw, &rl) == 0) { + *ol = rl; + return raw; + } + } + /* gen>0: parent walk miss — do not full-Get; caller falls back. */ + if (ref->gen != 0) + return NULL; + if (!key_fallback) + return NULL; + uint8_t *raw = kvspaceShmGet(kv, key_fallback, 0, ol); + if (raw) + kvspaceShmResolveRef(kv, key_fallback, ref); + return raw; +} + +int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, + const char *key_fallback, uint32_t offset, + const uint8_t *buf, uint32_t buf_len) { + if (!kv || !ref || !buf) + return -1; + if (kv_sync(kv) != 0) + return -1; + int32_t id = ref_leaf(kv, ref, key_fallback); + art_hdr_t *h = id >= 0 ? art_hdr(kv, id) : NULL; + if (!h || !h->has_value) { + if (ref->gen != 0) + return -1; + if (!key_fallback) + return -1; + int32_t rl = 0; + uint8_t *d = kvspaceShmGet(kv, key_fallback, 0, &rl); + if (!d || offset + buf_len > (uint32_t)rl) + return -1; + memcpy(d + offset, buf, buf_len); + kvspaceShmResolveRef(kv, key_fallback, ref); + return 0; + } + uint8_t *raw; + int32_t rl; + if (read_tlv(kv, h->box_offset, &raw, &rl) < 0 || + offset + buf_len > (uint32_t)rl) + return -1; + memcpy(raw + offset, buf, buf_len); + return 0; +} + /* ── 值/索引分离(方案2,对齐 kvspace-durable backend.rs) ────────── */ /* 坐标段工具见 xvalue.c:kvspaceCoordIsCoord / kvspaceParseCoord / * kvspaceCoordCmp。 */ diff --git a/src/kvspace_shm.h b/src/kvspace_shm.h index 66e0fd1..987262b 100644 --- a/src/kvspace_shm.h +++ b/src/kvspace_shm.h @@ -15,6 +15,14 @@ typedef struct kvspace kvspace_t; +/* 对齐 kvspace/include/kvspace/kvspace.h:叶子 + 目录祖先。 */ +typedef struct { + uint32_t block_id; + uint32_t gen; + uint32_t parent_id; + uint32_t depth; +} kvspaceRef_t; + /* ================================================================ * 生命周期 * ================================================================ */ @@ -32,6 +40,13 @@ void kvspaceShmClose(kvspace_t *kv); uint8_t *kvspaceShmGet(kvspace_t *kv, const char *key, int resolve, int32_t *out_len); +int kvspaceShmResolveRef(kvspace_t *kv, const char *key, kvspaceRef_t *ref); +uint8_t *kvspaceShmGetByRef(kvspace_t *kv, kvspaceRef_t *ref, + const char *key_fallback, int32_t *out_len); +int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, + const char *key_fallback, uint32_t offset, + const uint8_t *buf, uint32_t buf_len); + // Set: 写入 value(TLV 编码的字节)。总是穿透 link 写入 target。 int kvspaceShmSet(kvspace_t *kv, const char *key, const uint8_t *val, int32_t val_len); diff --git a/tests/test_handle_cache.c b/tests/test_handle_cache.c new file mode 100644 index 0000000..310d614 --- /dev/null +++ b/tests/test_handle_cache.c @@ -0,0 +1,313 @@ +#define _GNU_SOURCE +#include "kvspace_shm.h" +#include "xvalue.h" +#include +#include +#include +#include +#include + +#define DATA_SIZE (8UL * 64 * 64) +#define CHECK(c) \ + do { \ + if (!(c)) { \ + failures++; \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); \ + } \ + } while (0) + +static int failures; + +static int set_i64(kvspace_t *kv, const char *key, int64_t v) { + uint8_t *tlv; + int32_t n = kvspaceXvalueNewInt64(&v, 1, &tlv); + if (n < 0) + return -1; + int rc = kvspaceShmSet(kv, key, tlv, n); + free(tlv); + return rc; +} + +static int64_t get_i64(const uint8_t *d, int32_t len) { + if (!d || len <= 0) + return -999; + xvalue_head_t h = kvspaceXvalueDecodeHead(d, len); + return h.raw_len == 8 ? kvspaceXvalueRawInt64(h.raw) : -999; +} + +static uint64_t nsec(void) { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return (uint64_t)t.tv_sec * 1000000000ull + (uint64_t)t.tv_nsec; +} + +int main(void) { + char dir[] = "/tmp/kvs-ref-XXXXXX"; + if (!mkdtemp(dir)) + return 1; + char path[256]; + snprintf(path, sizeof path, "%s/s", dir); + kvspace_t *kv = kvspaceShmOpen(path, DATA_SIZE); + if (!kv) { + fprintf(stderr, "open failed\n"); + return 1; + } + + CHECK(set_i64(kv, "/a", 1) == 0); + kvspaceRef_t ref; + CHECK(kvspaceShmResolveRef(kv, "/a", &ref) == 0); + int32_t len = 0; + uint8_t *d = kvspaceShmGetByRef(kv, &ref, "/a", &len); + CHECK(get_i64(d, len) == 1); + uint32_t id0 = ref.block_id; + + CHECK(set_i64(kv, "/a", 42) == 0); + d = kvspaceShmGetByRef(kv, &ref, "/a", &len); + CHECK(get_i64(d, len) == 42); + CHECK(ref.block_id == id0); + + CHECK(set_i64(kv, "/n", 7) == 0); + kvspaceRef_t nr; + CHECK(kvspaceShmResolveRef(kv, "/n", &nr) == 0); + for (int i = 0; i < 5; i++) { + char k[8]; + snprintf(k, sizeof k, "/n%d", i); + CHECK(set_i64(kv, k, i) == 0); + } + d = kvspaceShmGetByRef(kv, &nr, "/n", &len); + CHECK(get_i64(d, len) == 7); + + kvspaceShmDel(kv, "/a"); + len = 0; + d = kvspaceShmGetByRef(kv, &ref, NULL, &len); + CHECK(d == NULL); + CHECK(set_i64(kv, "/a", 9) == 0); + d = kvspaceShmGetByRef(kv, &ref, "/a", &len); + CHECK(get_i64(d, len) == 9); + + CHECK(set_i64(kv, "/p", 100) == 0); + kvspaceRef_t pr; + CHECK(kvspaceShmResolveRef(kv, "/p", &pr) == 0); + d = kvspaceShmGet(kv, "/p", 0, &len); + CHECK(d && len > 8); + xvalue_head_t hh = kvspaceXvalueDecodeHead(d, len); + int64_t nv = 200; + uint8_t raw[8]; + for (int i = 0; i < 8; i++) + raw[i] = (uint8_t)((uint64_t)nv >> (8 * i)); + CHECK(kvspaceShmSetPartByRef(kv, &pr, "/p", + (uint32_t)kvspaceXvalueHeadLen(&hh), raw, + 8) == 0); + d = kvspaceShmGetByRef(kv, &pr, "/p", &len); + CHECK(get_i64(d, len) == 200); + + for (int i = 0; i < 64; i++) { + char k[16]; + snprintf(k, sizeof k, "/sib/%02d", i); + CHECK(set_i64(kv, k, i + 100) == 0); + } + for (int i = 0; i < 64; i++) { + char k[16]; + snprintf(k, sizeof k, "/sib/%02d", i); + int32_t l = 0; + d = kvspaceShmGet(kv, k, 0, &l); + CHECK(get_i64(d, l) == i + 100); + } + CHECK(set_i64(kv, "/other", 1) == 0); + { + int32_t l = 0; + d = kvspaceShmGet(kv, "/sib/00", 0, &l); + CHECK(get_i64(d, l) == 100); + d = kvspaceShmGet(kv, "/other", 0, &l); + CHECK(get_i64(d, l) == 1); + kvspaceRef_t sr; + CHECK(set_i64(kv, "/frm/a", 1) == 0); + CHECK(set_i64(kv, "/frm/i", 2) == 0); + CHECK(set_i64(kv, "/frm/n", 3) == 0); + CHECK(kvspaceShmResolveRef(kv, "/frm/a", &sr) == 0); + CHECK(sr.depth > 0 && sr.parent_id != 0); + d = kvspaceShmGetByRef(kv, &sr, "/frm/a", &l); + CHECK(get_i64(d, l) == 1); + { + kvspaceRef_t pr = { sr.parent_id, sr.depth, 0, 0 }; + d = kvspaceShmGetByRef(kv, &pr, "/frm/i", &l); + CHECK(get_i64(d, l) == 2); + CHECK(get_i64(d, l) != 1); + d = kvspaceShmGetByRef(kv, &pr, "/frm/n", &l); + CHECK(get_i64(d, l) == 3); + CHECK(get_i64(d, l) != 1); + } + CHECK(set_i64(kv, "/map/[1]", 11) == 0); + CHECK(set_i64(kv, "/map/[2]", 22) == 0); + CHECK(set_i64(kv, "/map/[3]", 33) == 0); + { + kvspaceRef_t mr; + CHECK(kvspaceShmResolveRef(kv, "/map/[1]", &mr) == 0); + CHECK(mr.depth > 0 && mr.parent_id != 0); + kvspaceRef_t mp = { mr.parent_id, mr.depth, 0, 0 }; + d = kvspaceShmGetByRef(kv, &mp, "/map/[2]", &l); + CHECK(get_i64(d, l) == 22); + CHECK(get_i64(d, l) != 11); + d = kvspaceShmGetByRef(kv, &mp, "/map/[3]", &l); + CHECK(get_i64(d, l) == 33); + CHECK(get_i64(d, l) != 11); + } + { + kvspaceRef_t sr0; + CHECK(kvspaceShmResolveRef(kv, "/sib/00", &sr0) == 0); + CHECK(sr0.depth > 0); + kvspaceRef_t sp = { sr0.parent_id, sr0.depth, 0, 0 }; + for (int i = 0; i < 64; i++) { + char k[16]; + snprintf(k, sizeof k, "/sib/%02d", i); + int32_t l2 = 0; + d = kvspaceShmGetByRef(kv, &sp, k, &l2); + CHECK(get_i64(d, l2) == i + 100); + } + } + { + const int N = 200; + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]/h/[%d]", + (int)((unsigned)i * 2654435761u % 100003)); + CHECK(set_i64(kv, k, i) == 0); + } + char k0[128]; + snprintf(k0, sizeof k0, "/vthread/vt0/[0]/h/[%d]", 0); + kvspaceRef_t hr; + CHECK(kvspaceShmResolveRef(kv, k0, &hr) == 0); + CHECK(hr.depth > 0 && hr.parent_id != 0); + kvspaceRef_t hp = { hr.parent_id, hr.depth, 0, 0 }; + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]/h/[%d]", + (int)((unsigned)i * 2654435761u % 100003)); + int32_t l2 = 0; + d = kvspaceShmGetByRef(kv, &hp, k, &l2); + CHECK(get_i64(d, l2) == i); + CHECK(get_i64(d, l2) != i + 1); + } + } + /* kvlang map slots use member '·', not '/'. */ + { + const int N = 200; +#define MSEP "\xC2\xB7" + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "h" MSEP "[%d]", + (int)((unsigned)i * 2654435761u % 100003)); + CHECK(set_i64(kv, k, i) == 0); + } + char k0[128]; + snprintf(k0, sizeof k0, "/vthread/vt0/[0]" MSEP "h" MSEP "[0]"); + kvspaceRef_t hr; + CHECK(kvspaceShmResolveRef(kv, k0, &hr) == 0); + CHECK(hr.depth > 0 && hr.parent_id != 0); + kvspaceRef_t hp = { hr.parent_id, hr.depth, 0, 0 }; + int hits = 0; + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "h" MSEP "[%d]", + (int)((unsigned)i * 2654435761u % 100003)); + int32_t l2 = 0; + d = kvspaceShmGetByRef(kv, &hp, k, &l2); + if (get_i64(d, l2) == i) + hits++; + CHECK(get_i64(d, l2) == i); + CHECK(get_i64(d, l2) != i + 1); + } + CHECK(hits == N); + for (int i = 0; i < 32; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "arr" MSEP "[%d]", i); + CHECK(set_i64(kv, k, i + 50) == 0); + } + snprintf(k0, sizeof k0, "/vthread/vt0/[0]" MSEP "arr" MSEP "[0]"); + CHECK(kvspaceShmResolveRef(kv, k0, &hr) == 0); + hp.block_id = hr.parent_id; + hp.gen = hr.depth; + for (int i = 0; i < 32; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "arr" MSEP "[%d]", i); + int32_t l2 = 0; + d = kvspaceShmGetByRef(kv, &hp, k, &l2); + CHECK(get_i64(d, l2) == i + 50); + } + /* kv.get/kv.set map slots are unbracketed: base·42 */ + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "m" MSEP "%d", + (int)((unsigned)i * 2654435761u % 100003)); + CHECK(set_i64(kv, k, i + 1000) == 0); + } + snprintf(k0, sizeof k0, "/vthread/vt0/[0]" MSEP "m" MSEP "0"); + CHECK(kvspaceShmResolveRef(kv, k0, &hr) == 0); + CHECK(hr.depth > 0 && hr.parent_id != 0); + hp.block_id = hr.parent_id; + hp.gen = hr.depth; + hits = 0; + for (int i = 0; i < N; i++) { + char k[128]; + snprintf(k, sizeof k, "/vthread/vt0/[0]" MSEP "m" MSEP "%d", + (int)((unsigned)i * 2654435761u % 100003)); + int32_t l2 = 0; + d = kvspaceShmGetByRef(kv, &hp, k, &l2); + if (get_i64(d, l2) == i + 1000) + hits++; + CHECK(get_i64(d, l2) == i + 1000); + } + CHECK(hits == N); +#undef MSEP + } + } + + const int N = 200000; + CHECK(set_i64(kv, "/hot", 1) == 0); + kvspaceRef_t hr; + CHECK(kvspaceShmResolveRef(kv, "/hot", &hr) == 0); + uint64_t t0 = nsec(); + for (int i = 0; i < N; i++) { + int32_t l = 0; + kvspaceShmGet(kv, "/hot", 0, &l); + } + uint64_t t1 = nsec(); + for (int i = 0; i < N; i++) { + int32_t l = 0; + kvspaceShmGetByRef(kv, &hr, "/hot", &l); + } + uint64_t t2 = nsec(); + double ns_get = (double)(t1 - t0) / N; + double ns_ref = (double)(t2 - t1) / N; + printf("Get %.1f ns/op GetByRef %.1f ns/op ratio %.2f\n", ns_get, ns_ref, + ns_ref > 0 ? ns_get / ns_ref : 0); + + const char *fk[] = {"/frm/a", "/frm/i", "/frm/n"}; + const int NF = 3; + const int NR = 50000; + uint64_t t3 = nsec(); + for (int r = 0; r < NR; r++) { + int32_t l = 0; + kvspaceShmGet(kv, fk[r % NF], 0, &l); + } + uint64_t t4 = nsec(); + kvspaceRef_t sr, pref; + CHECK(kvspaceShmResolveRef(kv, fk[0], &sr) == 0); + CHECK(sr.depth > 0); + pref.block_id = sr.parent_id; + pref.gen = sr.depth; + pref.parent_id = 0; + pref.depth = 0; + uint64_t t5 = nsec(); + for (int r = 0; r < NR; r++) { + int32_t l = 0; + kvspaceShmGetByRef(kv, &pref, fk[r % NF], &l); + } + uint64_t t6 = nsec(); + printf("Get sibling %.1f ns/op GetByRef sibling %.1f ns/op ratio %.2f\n", + (double)(t4 - t3) / NR, (double)(t6 - t5) / NR, + (t6 - t5) > 0 ? (double)(t4 - t3) / (double)(t6 - t5) : 0); + + kvspaceShmClose(kv); + return failures ? 1 : 0; +}