From 4997816271596f4ba92288ed96c871059f2bef85 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Thu, 14 May 2026 11:53:16 +0900 Subject: [PATCH 01/11] INTERNAL: Extract common hash_tree struct definitions --- engines/default/hash_tree.h | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 engines/default/hash_tree.h diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h new file mode 100644 index 00000000..048fe4f6 --- /dev/null +++ b/engines/default/hash_tree.h @@ -0,0 +1,50 @@ +/* + * arcus-memcached - Arcus memory cache server + * Copyright 2010-2014 NAVER Corp. + * Copyright 2014-2020 JaM2in Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef HASH_TREE_H +#define HASH_TREE_H + +#include +#include +#include + +#define HTREE_HASHTAB_SIZE 16 + +/* Layout contract for hash-tree elements. + * Any elem_item used with hash_tree functions must begin with these + * fields in this exact order. Fields beyond this point may differ. */ +typedef struct _htree_elem_item { + uint8_t reserved[8]; /* available for use by the caller's own header fields */ + struct _htree_elem_item *next; /* hash chain next */ + uint32_t hval; /* hash value */ +} htree_elem_item; + +typedef struct _htree_node { + uint16_t refcount; + uint8_t slabs_clsid; + uint8_t hdepth; + uint32_t tot_elem_cnt; + int16_t hcnt[HTREE_HASHTAB_SIZE]; + void *htab[HTREE_HASHTAB_SIZE]; +} htree_node; + +/* ops: collection-specific key extraction */ +typedef struct { + const void *(*get_key)(const htree_elem_item *elem, uint16_t *nkey); +} htree_ops; + +#endif From a75eba398c53030690ddb0dc28ae0660c376d752 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Thu, 14 May 2026 14:45:16 +0900 Subject: [PATCH 02/11] INTERNAL: Replace map/set insert/update/exist path with hash_tree module --- Makefile.am | 2 + engines/default/coll_map.c | 338 ++++++++---------------------------- engines/default/coll_set.c | 199 ++++----------------- engines/default/hash_tree.c | 251 ++++++++++++++++++++++++++ engines/default/hash_tree.h | 26 +++ engines/default/item_base.c | 9 + engines/default/item_base.h | 2 + 7 files changed, 395 insertions(+), 432 deletions(-) create mode 100644 engines/default/hash_tree.c diff --git a/Makefile.am b/Makefile.am index ff0695b4..2123697e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -171,6 +171,8 @@ default_engine_la_SOURCES= \ engines/default/item_clog.h \ engines/default/item_base.c \ engines/default/item_base.h \ + engines/default/hash_tree.c \ + engines/default/hash_tree.h \ engines/default/coll_list.c \ engines/default/coll_list.h \ engines/default/coll_set.c \ diff --git a/engines/default/coll_map.c b/engines/default/coll_map.c index 7a1a3f7f..c714d6fe 100644 --- a/engines/default/coll_map.c +++ b/engines/default/coll_map.c @@ -33,6 +33,7 @@ #include "default_engine.h" #include "item_clog.h" +#include "hash_tree.h" static struct default_engine *engine=NULL; static struct engine_config *config=NULL; // engine config @@ -52,15 +53,14 @@ static inline void UNLOCK_CACHE(void) pthread_mutex_unlock(&engine->cache_lock); } -/* - * MAP collection manangement - */ -/* map element previous info internally used */ -typedef struct _map_prev_info { - map_hash_node *node; - map_elem_item *prev; - uint16_t hidx; -} map_prev_info; +static const void *map_get_key(const htree_elem_item *elem, uint16_t *nkey) { + *nkey = (uint16_t)((const map_elem_item *)elem)->nfield; + return ((const map_elem_item *)elem)->data; +} + +static htree_ops map_htree_ops = { + .get_key = map_get_key, +}; #define MAP_GET_HASHIDX(hval, hdepth) \ (((hval) & (MAP_HASHIDX_MASK << ((hdepth)*4))) >> ((hdepth)*4)) @@ -140,25 +140,6 @@ static hash_item *do_map_item_alloc(const void *key, const uint32_t nkey, return it; } -static map_hash_node *do_map_node_alloc(uint8_t hash_depth, const void *cookie) -{ - size_t ntotal = sizeof(map_hash_node); - - map_hash_node *node = do_item_mem_alloc(ntotal, LRU_CLSID_FOR_SMALL, cookie); - if (node != NULL) { - node->slabs_clsid = slabs_clsid(ntotal); - assert(node->slabs_clsid > 0); - - node->refcount = 0; - node->hdepth = hash_depth; - node->cur_hash_cnt = 0; - node->cur_elem_cnt = 0; - memset(node->hcnt, 0, MAP_HASHTAB_SIZE*sizeof(uint16_t)); - memset(node->htab, 0, MAP_HASHTAB_SIZE*sizeof(void*)); - } - return node; -} - static void do_map_node_free(map_hash_node *node) { do_item_mem_free(node, sizeof(map_hash_node)); @@ -200,42 +181,6 @@ static void do_map_elem_release(map_elem_item *elem) } } -static void do_map_node_link(map_meta_info *info, - map_hash_node *par_node, const int par_hidx, - map_hash_node *node) -{ - if (par_node == NULL) { - info->root = node; - } else { - map_elem_item *elem; - int num_elems = par_node->hcnt[par_hidx]; - int num_found =0; - - while (par_node->htab[par_hidx] != NULL) { - elem = par_node->htab[par_hidx]; - par_node->htab[par_hidx] = elem->next; - - int hidx = MAP_GET_HASHIDX(elem->hval, node->hdepth); - elem->next = node->htab[hidx]; - node->htab[hidx] = elem; - node->hcnt[hidx] += 1; - num_found ++; - } - assert(num_found == num_elems); - node->cur_elem_cnt = num_found ; - - par_node->htab[par_hidx] = node; - par_node->hcnt[par_hidx] = -1; /* child hash node */ - par_node->cur_elem_cnt -= num_found ; - par_node->cur_hash_cnt += 1; - } - - if (1) { /* apply memory space */ - size_t stotal = slabs_space_size(sizeof(map_hash_node)); - do_coll_space_incr((coll_meta_info *)info, ITEM_TYPE_MAP, stotal); - } -} - static void do_map_node_unlink(map_meta_info *info, map_hash_node *par_node, const int par_hidx) { @@ -288,142 +233,62 @@ static void do_map_node_unlink(map_meta_info *info, do_map_node_free(node); } -static void do_map_elem_replace(map_meta_info *info, - map_prev_info *pinfo, map_elem_item *new_elem) +static ENGINE_ERROR_CODE do_map_elem_replace_at(map_meta_info *info, + htree_elem_pos *pos, + map_elem_item *old_elem, + map_elem_item *new_elem) { - map_elem_item *prev = pinfo->prev; - map_elem_item *old_elem; - size_t old_stotal; - size_t new_stotal; - - if (prev != NULL) { - old_elem = prev->next; - } else { - old_elem = (map_elem_item *)pinfo->node->htab[pinfo->hidx]; - } - - old_stotal = slabs_space_size(do_map_elem_ntotal(old_elem)); - new_stotal = slabs_space_size(do_map_elem_ntotal(new_elem)); +#ifdef ENABLE_STICKY_ITEM + if (IS_STICKY_COLLFLG(info) && do_map_elem_ntotal(old_elem) < do_map_elem_ntotal(new_elem) + && do_item_sticky_overflowed()) + return ENGINE_ENOMEM; +#endif + ssize_t space_delta = (ssize_t)slabs_space_size(do_map_elem_ntotal(new_elem)) + - (ssize_t)slabs_space_size(do_map_elem_ntotal(old_elem)); CLOG_MAP_ELEM_INSERT(info, old_elem, new_elem); - new_elem->next = old_elem->next; - if (prev != NULL) { - prev->next = new_elem; - } else { - pinfo->node->htab[pinfo->hidx] = new_elem; - } + htree_elem_replace_at(pos, (htree_elem_item *)old_elem, (htree_elem_item *)new_elem); new_elem->status = ELEM_STATUS_LINKED; - old_elem->status = ELEM_STATUS_UNLINKED; - if (old_elem->refcount == 0) { + + if (old_elem->refcount == 0) do_map_elem_free(old_elem); - } - if (new_stotal != old_stotal) { - assert(info->stotal > 0); - if (new_stotal > old_stotal) { - do_coll_space_incr((coll_meta_info *)info, ITEM_TYPE_MAP, (new_stotal-old_stotal)); - } else { - do_coll_space_decr((coll_meta_info *)info, ITEM_TYPE_MAP, (old_stotal-new_stotal)); - } - } + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); + + return ENGINE_SUCCESS; } static ENGINE_ERROR_CODE do_map_elem_link(map_meta_info *info, map_elem_item *elem, - const bool replace_if_exist, bool *replaced, const void *cookie) { - assert(info->root != NULL); - map_hash_node *node = info->root; - map_elem_item *prev = NULL; - map_elem_item *find; - map_prev_info pinfo; - ENGINE_ERROR_CODE res = ENGINE_SUCCESS; - - int hidx = -1; - - /* map hash value */ - elem->hval = genhash_string_hash(elem->data, elem->nfield); - - while (node != NULL) { - hidx = MAP_GET_HASHIDX(elem->hval, node->hdepth); - if (node->hcnt[hidx] >= 0) /* map element hash chain */ - break; - node = node->htab[hidx]; - } - assert(node != NULL); - for (find = node->htab[hidx]; find != NULL; find = find->next) { - if (map_hash_eq(elem->hval, elem->data, elem->nfield, - find->hval, find->data, find->nfield)) - break; - prev = find; - } - - if (find != NULL) { - if (replace_if_exist) { -#ifdef ENABLE_STICKY_ITEM - /* sticky memory limit check */ - if (IS_STICKY_COLLFLG(info)) { - if (find->nbytes < elem->nbytes) { - if (do_item_sticky_overflowed()) - return ENGINE_ENOMEM; - } - } -#endif - pinfo.node = node; - pinfo.prev = prev; - pinfo.hidx = hidx; - do_map_elem_replace(info, &pinfo, elem); - if (replaced) *replaced = true; - return ENGINE_SUCCESS; - } else { - return ENGINE_ELEM_EEXISTS; - } - } + assert(info->ovflact == OVFL_ERROR); + int real_mcnt = (int)(info->mcnt > 0 ? info->mcnt : config->max_map_size); + ssize_t space_delta; #ifdef ENABLE_STICKY_ITEM - /* sticky memory limit check */ - if (IS_STICKY_COLLFLG(info)) { - if (do_item_sticky_overflowed()) - return ENGINE_ENOMEM; - } + if (IS_STICKY_COLLFLG(info) && do_item_sticky_overflowed()) + return ENGINE_ENOMEM; #endif - - /* overflow check */ - assert(info->ovflact == OVFL_ERROR); - if (info->ccnt >= (info->mcnt > 0 ? info->mcnt : config->max_map_size)) { + if (real_mcnt > 0 && (int)info->ccnt >= real_mcnt) return ENGINE_EOVERFLOW; - } - - if (node->hcnt[hidx] >= MAP_MAX_HASHCHAIN_SIZE) { - map_hash_node *n_node = do_map_node_alloc(node->hdepth+1, cookie); - if (n_node == NULL) { - res = ENGINE_ENOMEM; - return res; - } - do_map_node_link(info, node, hidx, n_node); - - node = n_node; - hidx = MAP_GET_HASHIDX(elem->hval, node->hdepth); - } - CLOG_MAP_ELEM_INSERT(info, NULL, elem); + ENGINE_ERROR_CODE ret = htree_elem_link((htree_node **)&info->root, + (htree_elem_item *)elem, + &map_htree_ops, + &space_delta, cookie); + if (ret != ENGINE_SUCCESS) + return ret; - elem->next = node->htab[hidx]; - node->htab[hidx] = elem; - node->hcnt[hidx] += 1; - node->cur_elem_cnt += 1; elem->status = ELEM_STATUS_LINKED; + space_delta += (ssize_t)slabs_space_size(do_map_elem_ntotal(elem)); + CLOG_MAP_ELEM_INSERT(info, NULL, elem); info->ccnt++; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); - if (1) { /* apply memory space */ - size_t stotal = slabs_space_size(do_map_elem_ntotal(elem)); - do_coll_space_incr((coll_meta_info *)info, ITEM_TYPE_MAP, stotal); - } - - return res; + return ENGINE_SUCCESS; } static void do_map_elem_unlink(map_meta_info *info, @@ -555,82 +420,36 @@ static uint32_t do_map_elem_delete_with_field(map_meta_info *info, const int num return delcnt; } -static map_elem_item *do_map_elem_find(map_hash_node *node, const field_t *field, map_prev_info *pinfo) -{ - map_elem_item *elem = NULL; - map_elem_item *prev = NULL; - int hval = genhash_string_hash(field->value, field->length); - int hidx = -1; - - while (node != NULL) { - hidx = MAP_GET_HASHIDX(hval, node->hdepth); - if (node->hcnt[hidx] >= 0) /* map element hash chain */ - break; - node = node->htab[hidx]; - } - assert(node != NULL); - for (elem = node->htab[hidx]; elem != NULL; elem = elem->next) { - if (map_hash_eq(hval, field->value, field->length, elem->hval, elem->data, elem->nfield)) { - if (pinfo != NULL) { - pinfo->node = node; - pinfo->prev = prev; - pinfo->hidx = hidx; - } - break; - } - prev = elem; - } - return elem; -} - static ENGINE_ERROR_CODE do_map_elem_update(map_meta_info *info, const field_t *field, const char *value, const uint32_t nbytes, const void *cookie) { - map_prev_info pinfo; - map_elem_item *elem; - - if (info->root == NULL) { + htree_elem_pos pos; + map_elem_item *old_elem = (map_elem_item *)htree_elem_find((htree_node *)info->root, + field->value, field->length, + &map_htree_ops, &pos); + if (old_elem == NULL) return ENGINE_ELEM_ENOENT; - } - elem = do_map_elem_find(info->root, field, &pinfo); - if (elem == NULL) { - return ENGINE_ELEM_ENOENT; + /* in-place: same size, overwrite value without alloc */ + if (old_elem->refcount == 0 && old_elem->nbytes == (uint16_t)nbytes) { + memcpy(old_elem->data + field->length, value, nbytes); + CLOG_MAP_ELEM_INSERT(info, old_elem, old_elem); + return ENGINE_SUCCESS; } - if (elem->refcount == 0 && elem->nbytes == nbytes) { - /* old body size == new body size */ - /* do in-place update */ - memcpy(elem->data + elem->nfield, value, nbytes); - CLOG_MAP_ELEM_INSERT(info, elem, elem); - } else { - /* old body size != new body size */ -#ifdef ENABLE_STICKY_ITEM - /* sticky memory limit check */ - if (IS_STICKY_COLLFLG(info)) { - if (elem->nbytes < nbytes) { - if (do_item_sticky_overflowed()) - return ENGINE_ENOMEM; - } - } -#endif - - map_elem_item *new_elem = do_map_elem_alloc(elem->nfield, nbytes, cookie); - if (new_elem == NULL) { - return ENGINE_ENOMEM; - } - - /* build the new element */ - memcpy(new_elem->data, elem->data, elem->nfield); - memcpy(new_elem->data + elem->nfield, value, nbytes); - new_elem->hval = elem->hval; + /* chain-replace: different size or elem in use */ + map_elem_item *new_elem = do_map_elem_alloc(field->length, nbytes, cookie); + if (new_elem == NULL) + return ENGINE_ENOMEM; + memcpy(new_elem->data, field->value, field->length); + memcpy(new_elem->data + field->length, value, nbytes); - /* replace the element */ - do_map_elem_replace(info, &pinfo, new_elem); - } + ENGINE_ERROR_CODE ret = do_map_elem_replace_at(info, &pos, old_elem, new_elem); + if (ret != ENGINE_SUCCESS) + do_map_elem_free(new_elem); - return ENGINE_SUCCESS; + return ret; } static uint32_t do_map_elem_delete(map_meta_info *info, const uint32_t count, @@ -683,29 +502,22 @@ static ENGINE_ERROR_CODE do_map_elem_insert(hash_item *it, map_elem_item *elem, const void *cookie) { map_meta_info *info = (map_meta_info *)item_get_meta(it); - ENGINE_ERROR_CODE ret; - - /* create the root hash node if it does not exist */ - bool new_root_flag = false; - if (info->root == NULL) { /* empty map */ - map_hash_node *r_node = do_map_node_alloc(0, cookie); - if (r_node == NULL) { - return ENGINE_ENOMEM; - } - do_map_node_link(info, NULL, 0, r_node); - new_root_flag = true; - } - /* insert the element */ - ret = do_map_elem_link(info, elem, replace_if_exist, replaced, cookie); - if (ret != ENGINE_SUCCESS) { - if (new_root_flag) { - do_map_node_unlink(info, NULL, 0); - } - return ret; + htree_elem_pos pos; + map_elem_item *old_elem = (map_elem_item *)htree_elem_find((htree_node *)info->root, + elem->data, elem->nfield, + &map_htree_ops, &pos); + if (old_elem != NULL) { + if (!replace_if_exist) + return ENGINE_ELEM_EEXISTS; + ENGINE_ERROR_CODE ret = do_map_elem_replace_at(info, &pos, old_elem, elem); + if (ret != ENGINE_SUCCESS) + return ret; + if (replaced) *replaced = true; + return ENGINE_SUCCESS; } - return ENGINE_SUCCESS; + return do_map_elem_link(info, elem, cookie); } /* diff --git a/engines/default/coll_set.c b/engines/default/coll_set.c index 9d3e7799..5b1c455f 100644 --- a/engines/default/coll_set.c +++ b/engines/default/coll_set.c @@ -33,6 +33,7 @@ #include "default_engine.h" #include "item_clog.h" +#include "hash_tree.h" static struct default_engine *engine=NULL; static struct engine_config *config=NULL; // engine config @@ -52,6 +53,15 @@ static inline void UNLOCK_CACHE(void) pthread_mutex_unlock(&engine->cache_lock); } +static const void *set_get_key(const htree_elem_item *elem, uint16_t *nkey) { + *nkey = (uint16_t)((const set_elem_item *)elem)->nbytes; + return ((const set_elem_item *)elem)->value; +} + +static htree_ops set_htree_ops = { + .get_key = set_get_key, +}; + /* * Hash table management */ @@ -189,24 +199,6 @@ static hash_item *do_set_item_alloc(const void *key, const uint32_t nkey, return it; } -static set_hash_node *do_set_node_alloc(uint8_t hash_depth, const void *cookie) -{ - size_t ntotal = sizeof(set_hash_node); - - set_hash_node *node = do_item_mem_alloc(ntotal, LRU_CLSID_FOR_SMALL, cookie); - if (node != NULL) { - node->slabs_clsid = slabs_clsid(ntotal); - assert(node->slabs_clsid > 0); - - node->refcount = 0; - node->hdepth = hash_depth; - node->tot_elem_cnt = 0; - memset(node->hcnt, 0, SET_HASHTAB_SIZE*sizeof(uint16_t)); - memset(node->htab, 0, SET_HASHTAB_SIZE*sizeof(void*)); - } - return node; -} - static void do_set_node_free(set_hash_node *node) { do_item_mem_free(node, sizeof(set_hash_node)); @@ -246,35 +238,6 @@ static void do_set_elem_release(set_elem_item *elem) } } -static void do_set_node_link(set_meta_info *info, - set_hash_node *par_node, const int par_hidx, - set_hash_node *node) -{ - if (par_node == NULL) { - info->root = node; - } else { - set_elem_item *elem; - while (par_node->htab[par_hidx] != NULL) { - elem = par_node->htab[par_hidx]; - par_node->htab[par_hidx] = elem->next; - - int hidx = SET_GET_HASHIDX(elem->hval, node->hdepth); - elem->next = node->htab[hidx]; - node->htab[hidx] = elem; - node->hcnt[hidx] += 1; - node->tot_elem_cnt += 1; - } - assert(node->tot_elem_cnt == par_node->hcnt[par_hidx]); - par_node->htab[par_hidx] = node; - par_node->hcnt[par_hidx] = -1; /* child hash node */ - } - - if (1) { /* apply memory space */ - size_t stotal = slabs_space_size(sizeof(set_hash_node)); - do_coll_space_incr((coll_meta_info *)info, ITEM_TYPE_SET, stotal); - } -} - static void do_set_node_unlink(set_meta_info *info, set_hash_node *par_node, const int par_hidx) { @@ -323,69 +286,6 @@ static void do_set_node_unlink(set_meta_info *info, do_set_node_free(node); } -static ENGINE_ERROR_CODE do_set_elem_link(set_meta_info *info, set_elem_item *elem, - const void *cookie) -{ - assert(info->root != NULL); - set_hash_node *node = info->root; - set_elem_item *find; - int hidx = -1; - - /* set hash value */ - elem->hval = genhash_string_hash(elem->value, elem->nbytes); - - while (node != NULL) { - hidx = SET_GET_HASHIDX(elem->hval, node->hdepth); - if (node->hcnt[hidx] >= 0) /* set element hash chain */ - break; - node = node->htab[hidx]; - } - assert(node != NULL); - assert(hidx != -1); - - for (find = node->htab[hidx]; find != NULL; find = find->next) { - if (set_hash_eq(elem->hval, elem->value, elem->nbytes, - find->hval, find->value, find->nbytes)) - break; - } - if (find != NULL) { - return ENGINE_ELEM_EEXISTS; - } - - if (node->hcnt[hidx] >= SET_MAX_HASHCHAIN_SIZE) { - set_hash_node *n_node = do_set_node_alloc(node->hdepth+1, cookie); - if (n_node == NULL) { - return ENGINE_ENOMEM; - } - do_set_node_link(info, node, hidx, n_node); - - node = n_node; - hidx = SET_GET_HASHIDX(elem->hval, node->hdepth); - } - - elem->next = node->htab[hidx]; - node->htab[hidx] = elem; - node->hcnt[hidx] += 1; - node->tot_elem_cnt += 1; - elem->status = ELEM_STATUS_LINKED; - - set_hash_node *par_node = info->root; - while (par_node != node) { - par_node->tot_elem_cnt += 1; - hidx = SET_GET_HASHIDX(elem->hval, par_node->hdepth); - assert(par_node->hcnt[hidx] == -1); - par_node = par_node->htab[hidx]; - } - info->ccnt++; - - if (1) { /* apply memory space */ - size_t stotal = slabs_space_size(do_set_elem_ntotal(elem)); - do_coll_space_incr((coll_meta_info *)info, ITEM_TYPE_SET, stotal); - } - - return ENGINE_SUCCESS; -} - static void do_set_elem_unlink(set_meta_info *info, set_hash_node *node, const int hidx, set_elem_item *prev, set_elem_item *elem, @@ -410,31 +310,6 @@ static void do_set_elem_unlink(set_meta_info *info, } } -static set_elem_item *do_set_elem_find(set_meta_info *info, const char *val, const int vlen) -{ - set_elem_item *elem = NULL; - - if (info->root != NULL) { - set_hash_node *node = info->root; - int hval = genhash_string_hash(val, vlen); - int hidx = 0; - - while (node != NULL) { - hidx = SET_GET_HASHIDX(hval, node->hdepth); - if (node->hcnt[hidx] >= 0) /* set element hash chain */ - break; - node = node->htab[hidx]; - } - assert(node != NULL); - - for (elem = node->htab[hidx]; elem != NULL; elem = elem->next) { - if (set_hash_eq(hval, val, vlen, elem->hval, elem->value, elem->nbytes)) - break; - } - } - return elem; -} - static ENGINE_ERROR_CODE do_set_elem_traverse_delete(set_meta_info *info, set_hash_node *node, const int hval, const char *val, const int vlen) { @@ -690,44 +565,32 @@ static ENGINE_ERROR_CODE do_set_elem_insert(hash_item *it, set_elem_item *elem, const void *cookie) { set_meta_info *info = (set_meta_info *)item_get_meta(it); - uint32_t real_mcnt = (info->mcnt > 0 ? info->mcnt : config->max_set_size); - ENGINE_ERROR_CODE ret; + assert(info->ovflact == OVFL_ERROR); + + int real_mcnt = (int)(info->mcnt > 0 ? info->mcnt : config->max_set_size); + ssize_t space_delta; #ifdef ENABLE_STICKY_ITEM - /* sticky memory limit check */ - if (IS_STICKY_EXPTIME(it->exptime)) { - if (do_item_sticky_overflowed()) - return ENGINE_ENOMEM; - } + if (IS_STICKY_COLLFLG(info) && do_item_sticky_overflowed()) + return ENGINE_ENOMEM; #endif - - /* overflow check */ - assert(info->ovflact == OVFL_ERROR); - if (info->ccnt >= real_mcnt) { + if (real_mcnt > 0 && (int)info->ccnt >= real_mcnt) return ENGINE_EOVERFLOW; - } - /* create the root hash node if it does not exist */ - bool new_root_flag = false; - if (info->root == NULL) { /* empty set */ - set_hash_node *r_node = do_set_node_alloc(0, cookie); - if (r_node == NULL) { - return ENGINE_ENOMEM; - } - do_set_node_link(info, NULL, 0, r_node); - new_root_flag = true; - } - - /* insert the element */ - ret = do_set_elem_link(info, elem, cookie); - if (ret != ENGINE_SUCCESS) { - if (new_root_flag) { - do_set_node_unlink(info, NULL, 0); - } + ENGINE_ERROR_CODE ret = htree_elem_link((htree_node **)&info->root, + (htree_elem_item *)elem, + &set_htree_ops, + &space_delta, cookie); + if (ret != ENGINE_SUCCESS) return ret; - } + elem->status = ELEM_STATUS_LINKED; + space_delta += (ssize_t)slabs_space_size(sizeof(set_elem_item) + elem->nbytes); CLOG_SET_ELEM_INSERT(info, elem); + + info->ccnt++; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_SET, space_delta); + return ENGINE_SUCCESS; } @@ -877,10 +740,8 @@ ENGINE_ERROR_CODE set_elem_exist(const char *key, const uint32_t nkey, if ((info->mflags & COLL_META_FLAG_READABLE) == 0) { ret = ENGINE_UNREADABLE; break; } - if (do_set_elem_find(info, value, nbytes) != NULL) - *exist = true; - else - *exist = false; + *exist = (htree_elem_find((htree_node *)info->root, + value, nbytes, &set_htree_ops, NULL) != NULL); } while (0); do_item_release(it); } diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c new file mode 100644 index 00000000..657a9bfc --- /dev/null +++ b/engines/default/hash_tree.c @@ -0,0 +1,251 @@ +/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * arcus-memcached - Arcus memory cache server + * Copyright 2010-2014 NAVER Corp. + * Copyright 2014-2020 JaM2in Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include +#include +#include +#include + +#include "default_engine.h" +#include "hash_tree.h" + +#define HTREE_HASHIDX_MASK 0x0000000F +#define HTREE_MAX_HASHCHAIN_SIZE 64 + +#define HTREE_GET_HASHIDX(hval, hdepth) \ + (((hval) & (HTREE_HASHIDX_MASK << ((hdepth)*4))) >> ((hdepth)*4)) + +extern int genhash_string_hash(const void *p, size_t nkey); + +static inline uint32_t htree_hash(htree_ops *ops, const htree_elem_item *elem) +{ + uint16_t nkey; + const void *key = ops->get_key(elem, &nkey); + return (uint32_t)genhash_string_hash(key, nkey); +} + +static inline bool htree_key_eq(htree_ops *ops, + const htree_elem_item *a, const htree_elem_item *b) +{ + uint16_t na, nb; + const void *ka = ops->get_key(a, &na); + const void *kb = ops->get_key(b, &nb); + return na == nb && memcmp(ka, kb, na) == 0; +} + +static inline bool htree_key_eq_raw(htree_ops *ops, const htree_elem_item *elem, + const void *key, uint16_t nkey) +{ + uint16_t lnkey; + const void *lkey = ops->get_key(elem, &lnkey); + return lnkey == nkey && memcmp(lkey, key, nkey) == 0; +} + +static htree_node *do_htree_node_alloc(const uint8_t depth, const void *cookie) +{ + size_t ntotal = sizeof(htree_node); + htree_node *node = do_item_mem_alloc(ntotal, LRU_CLSID_FOR_SMALL, cookie); + if (node != NULL) { + node->slabs_clsid = slabs_clsid(ntotal); + node->refcount = 0; + node->hdepth = depth; + node->tot_elem_cnt = 0; + memset(node->hcnt, 0, HTREE_HASHTAB_SIZE * sizeof(int16_t)); + memset(node->htab, 0, HTREE_HASHTAB_SIZE * sizeof(void *)); + } + return node; +} + +static inline void space_delta_add(ssize_t *delta, ssize_t size) +{ + if (delta) *delta += size; +} + +/* Split par_node's chain at par_hidx into a new child node: + * allocate child, transfer existing chain into it, and link it as a child. */ +static bool do_htree_node_split(htree_node *par_node, const int par_hidx, + const void *cookie) +{ + /* allocate a new child node one level deeper */ + htree_node *n_node = do_htree_node_alloc(par_node->hdepth + 1, cookie); + if (n_node == NULL) + return false; + + htree_elem_item *elem; + while (par_node->htab[par_hidx] != NULL) { + /* pop from par_node's chain */ + elem = (htree_elem_item *)par_node->htab[par_hidx]; + par_node->htab[par_hidx] = elem->next; + + /* re-compute hidx at child depth */ + int hidx = HTREE_GET_HASHIDX(elem->hval, n_node->hdepth); + + /* insert into child node's slot */ + elem->next = n_node->htab[hidx]; + n_node->htab[hidx] = elem; + n_node->hcnt[hidx] += 1; + n_node->tot_elem_cnt += 1; + } + assert(n_node->tot_elem_cnt == par_node->hcnt[par_hidx]); + + /* replace the chain slot with the child node */ + par_node->htab[par_hidx] = n_node; + par_node->hcnt[par_hidx] = -1; + return true; +} + +/* Split the hash chain at *hidx_ptr into a new child node if it is full, + * updating *node_pptr and *hidx_ptr to point to the insertion slot in the child. */ +static bool do_htree_node_try_split(htree_node **node_pptr, + int *hidx_ptr, uint32_t hval, + ssize_t *delta, const void *cookie) +{ + htree_node *par_node = *node_pptr; + int hidx = *hidx_ptr; + /* chain not full: no split needed */ + if (par_node->hcnt[hidx] < HTREE_MAX_HASHCHAIN_SIZE) + return true; + + /* split: allocate child, transfer chain, and link as child of par_node */ + if (!do_htree_node_split(par_node, hidx, cookie)) + return false; + space_delta_add(delta, (ssize_t)slabs_space_size(sizeof(htree_node))); + + /* update *node_pptr and *hidx_ptr so caller inserts into the child node */ + *node_pptr = (htree_node *)par_node->htab[hidx]; + *hidx_ptr = HTREE_GET_HASHIDX(hval, (*node_pptr)->hdepth); + return true; +} + +static void do_htree_elem_link(htree_node **root_pptr, + htree_node *node, const int hidx, + htree_elem_item *elem) +{ + /* prepend elem to the hash chain */ + elem->next = node->htab[hidx]; + node->htab[hidx] = elem; + node->hcnt[hidx] += 1; + + /* increment tot_elem_cnt on every node from root down to the target node */ + htree_node *cur = *root_pptr; + while (cur != node) { + cur->tot_elem_cnt += 1; + int cidx = HTREE_GET_HASHIDX(elem->hval, cur->hdepth); + assert(cur->hcnt[cidx] == -1); + cur = (htree_node *)cur->htab[cidx]; + } + node->tot_elem_cnt += 1; +} + +htree_elem_item *htree_elem_find(htree_node *root, + const void *key, uint16_t nkey, + htree_ops *ops, + htree_elem_pos *pos) +{ + if (root == NULL) + return NULL; + + uint32_t hval = (uint32_t)genhash_string_hash(key, nkey); + htree_node *node = root; + htree_elem_item *prev = NULL; + int hidx = -1; + + while (node != NULL) { + hidx = HTREE_GET_HASHIDX(hval, node->hdepth); + if (node->hcnt[hidx] >= 0) + break; + node = (htree_node *)node->htab[hidx]; + } + assert(node != NULL && hidx != -1); + + htree_elem_item *find; + for (find = (htree_elem_item *)node->htab[hidx]; + find != NULL; + find = find->next) { + if (find->hval == hval && htree_key_eq_raw(ops, find, key, nkey)) + break; + prev = find; + } + + if (find != NULL && pos != NULL) { + pos->node = node; + pos->hidx = hidx; + pos->prev = prev; + } + return find; +} + +void htree_elem_replace_at(htree_elem_pos *pos, + htree_elem_item *old_elem, + htree_elem_item *new_elem) +{ + new_elem->hval = old_elem->hval; + new_elem->next = old_elem->next; + if (pos->prev != NULL) + pos->prev->next = new_elem; + else + pos->node->htab[pos->hidx] = new_elem; +} + +ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, + htree_elem_item *elem, + htree_ops *ops, + ssize_t *htree_space_delta, + const void *cookie) +{ + if (htree_space_delta) *htree_space_delta = 0; + elem->hval = htree_hash(ops, elem); + + /* allocate root node if the tree is empty */ + if (*root_pptr == NULL) { + htree_node *root = do_htree_node_alloc(0, cookie); + if (root == NULL) + return ENGINE_ENOMEM; + *root_pptr = root; + space_delta_add(htree_space_delta, (ssize_t)slabs_space_size(sizeof(htree_node))); + int hidx = HTREE_GET_HASHIDX(elem->hval, 0); + do_htree_elem_link(root_pptr, root, hidx, elem); + return ENGINE_SUCCESS; + } + + /* traverse to the leaf node that should contain this element */ + htree_node *node = *root_pptr; + int hidx; + while (true) { + hidx = HTREE_GET_HASHIDX(elem->hval, node->hdepth); + if (node->hcnt[hidx] >= 0) + break; + node = (htree_node *)node->htab[hidx]; + } + + /* check for duplicate key in the hash chain */ + for (htree_elem_item *find = (htree_elem_item *)node->htab[hidx]; + find != NULL; find = find->next) { + if (find->hval == elem->hval && htree_key_eq(ops, find, elem)) + return ENGINE_ELEM_EEXISTS; + } + + /* split the hash chain into a new child node if it is full */ + if (!do_htree_node_try_split(&node, &hidx, elem->hval, + htree_space_delta, cookie)) + return ENGINE_ENOMEM; + + do_htree_elem_link(root_pptr, node, hidx, elem); + return ENGINE_SUCCESS; +} diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h index 048fe4f6..a73f3ae3 100644 --- a/engines/default/hash_tree.h +++ b/engines/default/hash_tree.h @@ -21,6 +21,7 @@ #include #include #include +#include #define HTREE_HASHTAB_SIZE 16 @@ -47,4 +48,29 @@ typedef struct { const void *(*get_key)(const htree_elem_item *elem, uint16_t *nkey); } htree_ops; +/* Position context for hash-tree mutation operations (replace, delete). + * Filled by htree_elem_find when the caller needs to mutate the found element. */ +typedef struct { + htree_node *node; + int hidx; + htree_elem_item *prev; +} htree_elem_pos; + +/* Returns the found element, or NULL if not found. + * pos is filled only when non-NULL and the element is found. */ +htree_elem_item *htree_elem_find(htree_node *root, + const void *key, uint16_t nkey, + htree_ops *ops, + htree_elem_pos *pos); + +void htree_elem_replace_at(htree_elem_pos *pos, + htree_elem_item *old_elem, + htree_elem_item *new_elem); + +ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, + htree_elem_item *elem, + htree_ops *ops, + ssize_t *htree_space_delta, + const void *cookie); + #endif diff --git a/engines/default/item_base.c b/engines/default/item_base.c index 550bd02f..8220b790 100644 --- a/engines/default/item_base.c +++ b/engines/default/item_base.c @@ -362,6 +362,15 @@ void do_coll_space_decr(coll_meta_info *info, ENGINE_ITEM_TYPE item_type, prefix_bytes_decr(it->pfxptr, item_type, nspace); } +void do_coll_space_update(coll_meta_info *info, ENGINE_ITEM_TYPE type, + ssize_t space_delta) +{ + if (space_delta > 0) + do_coll_space_incr(info, type, (size_t)space_delta); + else if (space_delta < 0) + do_coll_space_decr(info, type, (size_t)-space_delta); +} + /* Max hash key length for calculating hash value */ #define MAX_HKEY_LEN 250 diff --git a/engines/default/item_base.h b/engines/default/item_base.h index 212f31e1..83e6835c 100644 --- a/engines/default/item_base.h +++ b/engines/default/item_base.h @@ -364,6 +364,8 @@ void do_coll_space_incr(coll_meta_info *info, ENGINE_ITEM_TYPE item_type, const size_t nspace); void do_coll_space_decr(coll_meta_info *info, ENGINE_ITEM_TYPE item_type, const size_t nspace); +void do_coll_space_update(coll_meta_info *info, ENGINE_ITEM_TYPE type, + ssize_t space_delta); /* item functions */ bool do_item_isvalid(hash_item *it, rel_time_t current_time); From 738b514c276bc767d6a4d8b819b12c82db6a16e6 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Thu, 14 May 2026 17:49:28 +0900 Subject: [PATCH 03/11] INTERNAL: Replace map/set delete path with hash_tree module --- engines/default/coll_map.c | 158 +++++++++++++++------- engines/default/coll_set.c | 124 +++++++----------- engines/default/hash_tree.c | 252 ++++++++++++++++++++++++++++++++++++ engines/default/hash_tree.h | 11 ++ 4 files changed, 418 insertions(+), 127 deletions(-) diff --git a/engines/default/coll_map.c b/engines/default/coll_map.c index c714d6fe..d2d05f4e 100644 --- a/engines/default/coll_map.c +++ b/engines/default/coll_map.c @@ -394,32 +394,6 @@ static int do_map_elem_traverse_dfs_bycnt(map_meta_info *info, map_hash_node *no return fcnt; } -static uint32_t do_map_elem_delete_with_field(map_meta_info *info, const int numfields, - const field_t *flist, enum elem_delete_cause cause) -{ - assert(cause == ELEM_DELETE_NORMAL); - uint32_t delcnt = 0; - - if (info->root != NULL) { - CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, numfields, cause); - if (numfields == 0) { - delcnt = do_map_elem_traverse_dfs_bycnt(info, info->root, 0, true, NULL, cause); - } else { - for (int ii = 0; ii < numfields; ii++) { - int hval = genhash_string_hash(flist[ii].value, flist[ii].length); - if (do_map_elem_traverse_dfs_byfield(info, info->root, hval, &flist[ii], true, NULL)) { - delcnt++; - } - } - } - if (info->root->cur_hash_cnt == 0 && info->root->cur_elem_cnt == 0) { - do_map_node_unlink(info, NULL, 0); - } - CLOG_ELEM_DELETE_END((coll_meta_info*)info, cause); - } - return delcnt; -} - static ENGINE_ERROR_CODE do_map_elem_update(map_meta_info *info, const field_t *field, const char *value, const uint32_t nbytes, const void *cookie) @@ -452,17 +426,97 @@ static ENGINE_ERROR_CODE do_map_elem_update(map_meta_info *info, return ret; } -static uint32_t do_map_elem_delete(map_meta_info *info, const uint32_t count, - enum elem_delete_cause cause) +static inline ssize_t do_map_elem_unlink_process(map_meta_info *info, map_elem_item *e) +{ + e->status = ELEM_STATUS_UNLINKED; + CLOG_MAP_ELEM_DELETE(info, e, ELEM_DELETE_NORMAL); + return (ssize_t)slabs_space_size(do_map_elem_ntotal(e)); +} + +static map_elem_item *do_map_elem_unlink_by_field(map_meta_info *info, + const char *field, int nfield, + ssize_t *delta) { - assert(cause == ELEM_DELETE_COLL); + htree_elem_item *unlinked = htree_elem_unlink((htree_node **)&info->root, field, nfield, + &map_htree_ops, delta); + if (unlinked == NULL) + return NULL; + + map_elem_item *elem = (map_elem_item *)unlinked; + *delta -= do_map_elem_unlink_process(info, elem); + + return elem; +} + +static ENGINE_ERROR_CODE do_map_elem_delete_by_field(map_meta_info *info, + const field_t *field) +{ + if (info->root == NULL) + return ENGINE_ELEM_ENOENT; + + ssize_t delta; + map_elem_item *elem = do_map_elem_unlink_by_field(info, field->value, field->length, &delta); + if (elem == NULL) + return ENGINE_ELEM_ENOENT; + + do_map_elem_release(elem); + info->ccnt--; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, delta); + + return ENGINE_SUCCESS; +} + +static uint32_t do_map_elem_delete_by_fields(map_meta_info *info, + const int numfields, const field_t *flist) +{ + assert(info->root && numfields > 0); uint32_t fcnt = 0; - if (info->root != NULL) { - fcnt = do_map_elem_traverse_dfs_bycnt(info, info->root, count, true, NULL, cause); - if (info->root->cur_hash_cnt == 0 && info->root->cur_elem_cnt == 0) { - do_map_node_unlink(info, NULL, 0); - } + ssize_t space_delta = 0; + + CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, numfields, ELEM_DELETE_NORMAL); + + for (int ii = 0; ii < numfields; ii++) { + ssize_t delta; + map_elem_item *elem = do_map_elem_unlink_by_field(info, flist[ii].value, + flist[ii].length, + &delta); + if (elem == NULL) continue; + do_map_elem_release(elem); + space_delta += delta; + fcnt++; + } + + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); + + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); + + return fcnt; +} + +static uint32_t do_map_elem_delete_all(map_meta_info *info) +{ + assert(info->root); + ssize_t space_delta; + uint32_t fcnt = 0; + + CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, 0, ELEM_DELETE_NORMAL); + + htree_elem_item *head = htree_elem_unlink_by_cnt((htree_node **)&info->root, 0, &space_delta); + for (htree_elem_item *cur = head; cur != NULL; ) { + htree_elem_item *next = cur->next; + map_elem_item *e = (map_elem_item *)cur; + space_delta -= do_map_elem_unlink_process(info, e); + do_map_elem_release(e); + fcnt++; + cur = next; } + + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); + + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); + return fcnt; } @@ -659,7 +713,12 @@ ENGINE_ERROR_CODE map_elem_delete(const char *key, const uint32_t nkey, ret = do_map_item_find(key, nkey, DONT_UPDATE, &it); if (ret == ENGINE_SUCCESS) { /* it != NULL */ map_meta_info *info = (map_meta_info *)item_get_meta(it); - *del_count = do_map_elem_delete_with_field(info, numfields, flist, ELEM_DELETE_NORMAL); + if (info->root != NULL) { + if (numfields == 0) + *del_count = do_map_elem_delete_all(info); + else + *del_count = do_map_elem_delete_by_fields(info, numfields, flist); + } if (*del_count > 0) { if (info->ccnt == 0 && drop_if_empty) { assert(info->root == NULL); @@ -738,7 +797,19 @@ ENGINE_ERROR_CODE map_elem_get(const char *key, const uint32_t nkey, uint32_t map_elem_delete_with_count(map_meta_info *info, const uint32_t count) { - return do_map_elem_delete(info, count, ELEM_DELETE_COLL); + uint32_t fcnt = 0; + if (info->root != NULL) { + htree_elem_item *head = htree_elem_unlink_by_cnt((htree_node **)&info->root, count, NULL); + for (htree_elem_item *cur = head; cur != NULL; ) { + htree_elem_item *next = cur->next; + map_elem_item *e = (map_elem_item *)cur; + e->status = ELEM_STATUS_UNLINKED; + do_map_elem_release(e); + cur = next; + fcnt++; + } + } + return fcnt; } /* See do_map_elem_traverse_dfs and do_map_elem_link. do_map_elem_traverse_dfs @@ -966,8 +1037,7 @@ ENGINE_ERROR_CODE map_apply_elem_delete(void *engine, hash_item *it, const char *key = item_get_key(it); map_meta_info *info; field_t flist; - uint32_t ndeleted; - ENGINE_ERROR_CODE ret = ENGINE_SUCCESS; + ENGINE_ERROR_CODE ret; flist.value = (char*)field; flist.length = nfield; @@ -985,18 +1055,12 @@ ENGINE_ERROR_CODE map_apply_elem_delete(void *engine, hash_item *it, } info = (map_meta_info *)item_get_meta(it); - if (info->ccnt == 0) { - logger->log(EXTENSION_LOG_INFO, NULL, "map_apply_elem_delete failed." - " no element.\n"); - ret = ENGINE_ELEM_ENOENT; break; - } - - ndeleted = do_map_elem_delete_with_field(info, 1, &flist, ELEM_DELETE_NORMAL); - if (ndeleted == 0) { + ret = do_map_elem_delete_by_field(info, &flist); + if (ret == ENGINE_ELEM_ENOENT) { logger->log(EXTENSION_LOG_INFO, NULL, "map_apply_elem_delete failed." " no element deleted. key=%.*s nkey=%u field=%.*s nfield=%u\n", PRINT_NKEY(it->nkey), key, it->nkey, nfield, field, nfield); - ret = ENGINE_ELEM_ENOENT; break; + break; } } while(0); diff --git a/engines/default/coll_set.c b/engines/default/coll_set.c index 5b1c455f..ecc420ba 100644 --- a/engines/default/coll_set.c +++ b/engines/default/coll_set.c @@ -115,12 +115,6 @@ static bool hash_insert(hash_table *ht, int key) #define SET_GET_HASHIDX(hval, hdepth) \ (((hval) & (SET_HASHIDX_MASK << ((hdepth)*4))) >> ((hdepth)*4)) -static inline int set_hash_eq(const int h1, const void *v1, size_t vlen1, - const int h2, const void *v2, size_t vlen2) -{ - return (h1 == h2 && vlen1 == vlen2 && memcmp(v1, v2, vlen1) == 0); -} - static inline uint32_t do_set_elem_ntotal(set_elem_item *elem) { return sizeof(set_elem_item) + elem->nbytes; @@ -310,63 +304,6 @@ static void do_set_elem_unlink(set_meta_info *info, } } -static ENGINE_ERROR_CODE do_set_elem_traverse_delete(set_meta_info *info, set_hash_node *node, - const int hval, const char *val, const int vlen) -{ - ENGINE_ERROR_CODE ret; - - int hidx = SET_GET_HASHIDX(hval, node->hdepth); - - if (node->hcnt[hidx] == -1) { - set_hash_node *child_node = node->htab[hidx]; - ret = do_set_elem_traverse_delete(info, child_node, hval, val, vlen); - if (ret == ENGINE_SUCCESS) { - if (child_node->tot_elem_cnt < (SET_MAX_HASHCHAIN_SIZE/2) - && is_leaf_node(child_node)) { - do_set_node_unlink(info, node, hidx); - } - node->tot_elem_cnt -= 1; - } - } else { - ret = ENGINE_ELEM_ENOENT; - if (node->hcnt[hidx] > 0) { - set_elem_item *prev = NULL; - set_elem_item *elem = node->htab[hidx]; - while (elem != NULL) { - if (set_hash_eq(hval, val, vlen, elem->hval, elem->value, elem->nbytes)) - break; - prev = elem; - elem = elem->next; - } - if (elem != NULL) { - do_set_elem_unlink(info, node, hidx, prev, elem, ELEM_DELETE_NORMAL); - ret = ENGINE_SUCCESS; - } - } - } - return ret; -} - -static ENGINE_ERROR_CODE do_set_elem_delete_with_value(set_meta_info *info, - const char *val, const int vlen, - enum elem_delete_cause cause) -{ - assert(cause == ELEM_DELETE_NORMAL); - ENGINE_ERROR_CODE ret; - if (info->root != NULL) { - int hval = genhash_string_hash(val, vlen); - ret = do_set_elem_traverse_delete(info, info->root, hval, val, vlen); - if (ret == ENGINE_SUCCESS) { - if (info->root->tot_elem_cnt == 0) { - do_set_node_unlink(info, NULL, 0); - } - } - } else { - ret = ENGINE_ELEM_ENOENT; - } - return ret; -} - static int do_set_elem_traverse_dfs(set_meta_info *info, set_hash_node *node, const uint32_t count, const bool delete, set_elem_item **elem_array) @@ -480,20 +417,6 @@ static set_elem_item *do_set_elem_at_offset(set_meta_info *info, set_hash_node * return NULL; } -static uint32_t do_set_elem_delete(set_meta_info *info, const uint32_t count, - enum elem_delete_cause cause) -{ - assert(cause == ELEM_DELETE_COLL); - uint32_t fcnt = 0; - if (info->root != NULL) { - fcnt = do_set_elem_traverse_dfs(info, info->root, count, true, NULL); - if (info->root->tot_elem_cnt == 0) { - do_set_node_unlink(info, NULL, 0); - } - } - return fcnt; -} - static uint32_t do_set_elem_traverse_rand(set_meta_info *info, const uint32_t count, const bool delete, set_elem_item **elem_array) @@ -594,6 +517,35 @@ static ENGINE_ERROR_CODE do_set_elem_insert(hash_item *it, set_elem_item *elem, return ENGINE_SUCCESS; } +static inline ssize_t do_set_elem_unlink_process(set_meta_info *info, set_elem_item *e) +{ + e->status = ELEM_STATUS_UNLINKED; + CLOG_SET_ELEM_DELETE(info, e, ELEM_DELETE_NORMAL); + return (ssize_t)slabs_space_size(sizeof(set_elem_item) + e->nbytes); +} + +static ENGINE_ERROR_CODE do_set_elem_delete_by_value(set_meta_info *info, + const char *val, const int vlen) +{ + if (info->root == NULL) + return ENGINE_ELEM_ENOENT; + + ssize_t space_delta; + htree_elem_item *unlinked = htree_elem_unlink((htree_node **)&info->root, val, vlen, + &set_htree_ops, &space_delta); + if (unlinked == NULL) + return ENGINE_ELEM_ENOENT; + + set_elem_item *elem = (set_elem_item *)unlinked; + space_delta -= do_set_elem_unlink_process(info, elem); + + do_set_elem_release(elem); + info->ccnt--; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_SET, space_delta); + + return ENGINE_SUCCESS; +} + /* * SET Interface Functions */ @@ -710,7 +662,7 @@ ENGINE_ERROR_CODE set_elem_delete(const char *key, const uint32_t nkey, ret = do_set_item_find(key, nkey, DONT_UPDATE, &it); if (ret == ENGINE_SUCCESS) { /* it != NULL */ set_meta_info *info = (set_meta_info *)item_get_meta(it); - ret = do_set_elem_delete_with_value(info, value, nbytes, ELEM_DELETE_NORMAL); + ret = do_set_elem_delete_by_value(info, value, nbytes); if (ret == ENGINE_SUCCESS) { if (info->ccnt == 0 && drop_if_empty) { do_item_unlink(it, ITEM_UNLINK_NORMAL); @@ -811,7 +763,19 @@ ENGINE_ERROR_CODE set_elem_get(const char *key, const uint32_t nkey, uint32_t set_elem_delete_with_count(set_meta_info *info, const uint32_t count) { - return do_set_elem_delete(info, count, ELEM_DELETE_COLL); + uint32_t fcnt = 0; + if (info->root != NULL) { + htree_elem_item *head = htree_elem_unlink_by_cnt((htree_node **)&info->root, count, NULL); + for (htree_elem_item *cur = head; cur != NULL; ) { + htree_elem_item *next = cur->next; + set_elem_item *e = (set_elem_item *)cur; + e->status = ELEM_STATUS_UNLINKED; + do_set_elem_release(e); + cur = next; + fcnt++; + } + } + return fcnt; } /* See do_set_elem_traverse_dfs and do_set_elem_link. do_set_elem_traverse_dfs @@ -1050,7 +1014,7 @@ ENGINE_ERROR_CODE set_apply_elem_delete(void *engine, hash_item *it, } info = (set_meta_info *)item_get_meta(it); - ret = do_set_elem_delete_with_value(info, value, nbytes, ELEM_DELETE_NORMAL); + ret = do_set_elem_delete_by_value(info, value, nbytes); if (ret == ENGINE_ELEM_ENOENT) { logger->log(EXTENSION_LOG_INFO, NULL, "set_apply_elem_delete failed." " no element deleted. key=%.*s nkey=%u\n", diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 657a9bfc..9656889d 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -33,6 +33,11 @@ extern int genhash_string_hash(const void *p, size_t nkey); +typedef struct { + htree_elem_item **pos; /* array mode: current write position */ + htree_elem_item *tail; /* chain mode: current tail */ +} htree_collect_ctx; + static inline uint32_t htree_hash(htree_ops *ops, const htree_elem_item *elem) { uint16_t nkey; @@ -57,6 +62,12 @@ static inline bool htree_key_eq_raw(htree_ops *ops, const htree_elem_item *elem, return lnkey == nkey && memcmp(lkey, key, nkey) == 0; } +static void collect_to_chain(htree_elem_item *elem, htree_collect_ctx *ctx) +{ + ctx->tail->next = elem; + ctx->tail = elem; +} + static htree_node *do_htree_node_alloc(const uint8_t depth, const void *cookie) { size_t ntotal = sizeof(htree_node); @@ -72,11 +83,25 @@ static htree_node *do_htree_node_alloc(const uint8_t depth, const void *cookie) return node; } +static void do_htree_node_free(htree_node *node) +{ + do_item_mem_free(node, sizeof(htree_node)); +} + static inline void space_delta_add(ssize_t *delta, ssize_t size) { if (delta) *delta += size; } +static inline bool is_leaf_node(const htree_node *node) +{ + for (int hidx = 0; hidx < HTREE_HASHTAB_SIZE; hidx++) { + if (node->hcnt[hidx] == -1) + return false; + } + return true; +} + /* Split par_node's chain at par_hidx into a new child node: * allocate child, transfer existing chain into it, and link it as a child. */ static bool do_htree_node_split(htree_node *par_node, const int par_hidx, @@ -110,6 +135,49 @@ static bool do_htree_node_split(htree_node *par_node, const int par_hidx, return true; } +/* Merge node back into par_node at par_hidx: + * transfer node's hash chain back into par_node's slot and free node. */ +static void do_htree_node_merge(htree_node **root_pptr, + htree_node *par_node, const int par_hidx) +{ + htree_node *node; + + if (par_node == NULL) { /* removing the root: must already be empty */ + node = *root_pptr; + *root_pptr = NULL; + assert(node->tot_elem_cnt == 0); + do_htree_node_free(node); + return; + } + /* merge child node's elements back into par_node's hash chain */ + assert(par_node->hcnt[par_hidx] == -1); /* par_hidx must point to a child node */ + node = (htree_node *)par_node->htab[par_hidx]; + + htree_elem_item *head = NULL; + int fcnt = 0; + for (int hidx = 0; hidx < HTREE_HASHTAB_SIZE; hidx++) { + assert(node->hcnt[hidx] >= 0); /* child must be a leaf (no grandchildren) */ + if (node->hcnt[hidx] == 0) + continue; + + /* prepend this slot's chain onto head */ + fcnt += node->hcnt[hidx]; + while (node->htab[hidx] != NULL) { + htree_elem_item *elem = (htree_elem_item *)node->htab[hidx]; + node->htab[hidx] = elem->next; + node->hcnt[hidx] -= 1; + elem->next = head; + head = elem; + } + } + assert(fcnt == (int)node->tot_elem_cnt); + + /* replace the child slot with the collected flat chain */ + par_node->htab[par_hidx] = head; + par_node->hcnt[par_hidx] = fcnt; + do_htree_node_free(node); +} + /* Split the hash chain at *hidx_ptr into a new child node if it is full, * updating *node_pptr and *hidx_ptr to point to the insertion slot in the child. */ static bool do_htree_node_try_split(htree_node **node_pptr, @@ -133,6 +201,23 @@ static bool do_htree_node_try_split(htree_node **node_pptr, return true; } +/* Merge child back into par_node if child is empty or too sparse. */ +static void do_htree_node_try_merge(htree_node **root_pptr, + htree_node *par_node, int par_hidx, + htree_node *child, ssize_t *delta) +{ + if (*root_pptr == NULL) return; + /* merge if empty, or if leaf has too few elems (< MAX/2) to keep as a separate node */ + if (child->tot_elem_cnt == 0 || + (par_node != NULL && + is_leaf_node(child) && + child->tot_elem_cnt < (HTREE_MAX_HASHCHAIN_SIZE / 2))) { + + do_htree_node_merge(root_pptr, par_node, par_hidx); + space_delta_add(delta, -(ssize_t)slabs_space_size(sizeof(htree_node))); + } +} + static void do_htree_elem_link(htree_node **root_pptr, htree_node *node, const int hidx, htree_elem_item *elem) @@ -153,6 +238,108 @@ static void do_htree_elem_link(htree_node **root_pptr, node->tot_elem_cnt += 1; } +static void do_htree_elem_unlink(htree_node **root_pptr, + htree_node *node, int hidx, + htree_elem_item *prev, htree_elem_item *elem) +{ + /* remove link from the hash chain */ + if (prev != NULL) prev->next = elem->next; + else node->htab[hidx] = elem->next; + + node->hcnt[hidx] -= 1; + elem->next = NULL; + + /* decrement tot_elem_cnt on every node from root down to the target node */ + htree_node *cur = *root_pptr; + while (cur != node) { + cur->tot_elem_cnt -= 1; + int cidx = HTREE_GET_HASHIDX(elem->hval, cur->hdepth); + assert(cur->hcnt[cidx] == -1); + cur = (htree_node *)cur->htab[cidx]; + } + node->tot_elem_cnt -= 1; +} + +/* Skip *skip elems then collect up to *take from a single hash chain. + * prev is tracked from the skip phase so unlink can splice at the right point. */ +static uint32_t do_htree_chain_range(htree_node *node, int hidx, + uint32_t *skip, uint32_t *take, + bool unlink, + void (*collect)(htree_elem_item *, htree_collect_ctx *), + htree_collect_ctx *ctx) +{ + assert(*skip < (uint32_t)node->hcnt[hidx]); + uint32_t fcnt = 0; + htree_elem_item *prev = NULL; + htree_elem_item *elem = (htree_elem_item *)node->htab[hidx]; + + while (*skip > 0) { + prev = elem; + elem = elem->next; + (*skip)--; + } + + while (elem != NULL && *take > 0) { + htree_elem_item *next = elem->next; + collect(elem, ctx); + (*take)--; + fcnt++; + if (unlink) { + if (prev != NULL) prev->next = next; + else node->htab[hidx] = next; + node->hcnt[hidx]--; + node->tot_elem_cnt--; + elem->next = NULL; + } + elem = next; + } + return fcnt; +} + +/* DFS over the hash tree: skip *skip elems then collect up to *take + * via collect_to_array (array output) or collect_to_chain (linked-list output). + * collect_to_chain relinks elems via next pointers; must pair with unlink=true. */ +static uint32_t do_htree_range(htree_node **root_pptr, + htree_node *node, + uint32_t *skip, uint32_t *take, + bool unlink, + void (*collect)(htree_elem_item *, htree_collect_ctx *), + htree_collect_ctx *ctx, + ssize_t *htree_space_delta) +{ + assert(collect != collect_to_chain || unlink); + uint32_t fcnt = 0; + + for (int hidx = 0; hidx < HTREE_HASHTAB_SIZE && *take > 0; hidx++) { + bool is_child = (node->hcnt[hidx] == -1); + if (!is_child && node->hcnt[hidx] == 0) continue; + + /* skip the entire slot if it falls within the skip range */ + uint32_t elem_cnt = is_child ? ((htree_node *)node->htab[hidx])->tot_elem_cnt + : (uint32_t)node->hcnt[hidx]; + if (*skip >= elem_cnt) { + *skip -= elem_cnt; + continue; + } + + if (is_child) { + /* child node: recurse deeper to find elems within the skip/take range */ + htree_node *child = (htree_node *)node->htab[hidx]; + uint32_t got = do_htree_range(root_pptr, child, skip, take, + unlink, collect, ctx, htree_space_delta); + if (unlink && got > 0) { + do_htree_node_try_merge(root_pptr, node, hidx, child, htree_space_delta); + node->tot_elem_cnt -= got; + } + fcnt += got; + } else { + /* hash chain: skip remaining offset then collect up to *take elems */ + fcnt += do_htree_chain_range(node, hidx, skip, take, unlink, collect, ctx); + } + } + return fcnt; +} + htree_elem_item *htree_elem_find(htree_node *root, const void *key, uint16_t nkey, htree_ops *ops, @@ -249,3 +436,68 @@ ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, do_htree_elem_link(root_pptr, node, hidx, elem); return ENGINE_SUCCESS; } + +htree_elem_item *htree_elem_unlink(htree_node **root_pptr, + const void *key, uint16_t nkey, + htree_ops *ops, + ssize_t *htree_space_delta) +{ + if (*root_pptr == NULL) return NULL; + if (htree_space_delta) *htree_space_delta = 0; + + uint32_t hval = (uint32_t)genhash_string_hash(key, nkey); + + /* traverse to the leaf node, tracking parent for potential merge */ + htree_node *par_node = NULL; + int par_hidx = 0; + htree_node *node = *root_pptr; + int hidx; + while (true) { + hidx = HTREE_GET_HASHIDX(hval, node->hdepth); + if (node->hcnt[hidx] >= 0) break; + par_node = node; + par_hidx = hidx; + node = (htree_node *)node->htab[hidx]; + } + + /* find the element in the hash chain */ + htree_elem_item *prev = NULL; + htree_elem_item *elem = (htree_elem_item *)node->htab[hidx]; + while (elem != NULL) { + if (elem->hval == hval && htree_key_eq_raw(ops, elem, key, nkey)) + break; + prev = elem; + elem = elem->next; + } + if (elem == NULL) return NULL; + + do_htree_elem_unlink(root_pptr, node, hidx, prev, elem); + do_htree_node_try_merge(root_pptr, par_node, par_hidx, node, htree_space_delta); + + /* returns the unlinked elem for caller post-processing (e.g. CLOG, free). */ + return elem; +} + +htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, + uint32_t count, + ssize_t *htree_space_delta) +{ + if (*root_pptr == NULL) return NULL; + if (htree_space_delta) *htree_space_delta = 0; + + /* count == 0 means "all"; take == 0 when tree is empty */ + uint32_t actual = (count > 0) ? count : (*root_pptr)->tot_elem_cnt; + if (actual == 0) return NULL; + + /* sentinel head: collect_to_chain links the result onto dummy.next */ + htree_elem_item dummy = {0}; + uint32_t skip = 0, take = actual; + htree_collect_ctx ctx = { .tail = &dummy }; + + /* skip 0, unlink `take` elems */ + do_htree_range(root_pptr, *root_pptr, &skip, &take, true, collect_to_chain, &ctx, + htree_space_delta); + + do_htree_node_try_merge(root_pptr, NULL, 0, *root_pptr, htree_space_delta); + return dummy.next; +} diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h index a73f3ae3..b194843b 100644 --- a/engines/default/hash_tree.h +++ b/engines/default/hash_tree.h @@ -73,4 +73,15 @@ ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, ssize_t *htree_space_delta, const void *cookie); +/* unlinks the element matching the given key and returns it. + * returns NULL if not found. caller is responsible for CLOG and free. */ +htree_elem_item *htree_elem_unlink(htree_node **root_pptr, + const void *key, uint16_t nkey, + htree_ops *ops, + ssize_t *htree_space_delta); + +htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, + uint32_t count, + ssize_t *htree_space_delta); + #endif From cd81043c68084d283d631840c910452ea4d09660 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Thu, 14 May 2026 18:17:48 +0900 Subject: [PATCH 04/11] INTERNAL: Replace map get path with hash_tree module --- engines/default/coll_map.c | 317 ++++++++---------------------------- engines/default/hash_tree.c | 32 ++++ engines/default/hash_tree.h | 6 + 3 files changed, 103 insertions(+), 252 deletions(-) diff --git a/engines/default/coll_map.c b/engines/default/coll_map.c index d2d05f4e..3f9d79b1 100644 --- a/engines/default/coll_map.c +++ b/engines/default/coll_map.c @@ -62,15 +62,6 @@ static htree_ops map_htree_ops = { .get_key = map_get_key, }; -#define MAP_GET_HASHIDX(hval, hdepth) \ - (((hval) & (MAP_HASHIDX_MASK << ((hdepth)*4))) >> ((hdepth)*4)) - -static inline int map_hash_eq(const int h1, const void *v1, size_t vlen1, - const int h2, const void *v2, size_t vlen2) -{ - return (h1 == h2 && vlen1 == vlen2 && memcmp(v1, v2, vlen1) == 0); -} - static inline uint32_t do_map_elem_ntotal(map_elem_item *elem) { return sizeof(map_elem_item) + elem->nfield + elem->nbytes; @@ -140,11 +131,6 @@ static hash_item *do_map_item_alloc(const void *key, const uint32_t nkey, return it; } -static void do_map_node_free(map_hash_node *node) -{ - do_item_mem_free(node, sizeof(map_hash_node)); -} - static map_elem_item *do_map_elem_alloc(const int nfield, const uint32_t nbytes, const void *cookie) { @@ -181,58 +167,6 @@ static void do_map_elem_release(map_elem_item *elem) } } -static void do_map_node_unlink(map_meta_info *info, - map_hash_node *par_node, const int par_hidx) -{ - map_hash_node *node; - - if (par_node == NULL) { - node = info->root; - info->root = NULL; - assert(node->cur_hash_cnt == 0); - assert(node->cur_elem_cnt == 0); - } else { - assert(par_node->hcnt[par_hidx] == -1); /* child hash node */ - map_elem_item *head = NULL; - map_elem_item *elem; - int hidx, fcnt = 0; - - node = (map_hash_node *)par_node->htab[par_hidx]; - assert(node->cur_hash_cnt == 0); - - for (hidx = 0; hidx < MAP_HASHTAB_SIZE; hidx++) { - assert(node->hcnt[hidx] >= 0); - if (node->hcnt[hidx] > 0) { - fcnt += node->hcnt[hidx]; - while (node->htab[hidx] != NULL) { - elem = node->htab[hidx]; - node->htab[hidx] = elem->next; - node->hcnt[hidx] -= 1; - - elem->next = head; - head = elem; - } - assert(node->hcnt[hidx] == 0); - } - } - assert(fcnt == node->cur_elem_cnt); - node->cur_elem_cnt = 0; - - par_node->htab[par_hidx] = head; - par_node->hcnt[par_hidx] = fcnt; - par_node->cur_elem_cnt += fcnt; - par_node->cur_hash_cnt -= 1; - } - - if (info->stotal > 0) { /* apply memory space */ - size_t stotal = slabs_space_size(sizeof(map_hash_node)); - do_coll_space_decr((coll_meta_info *)info, ITEM_TYPE_MAP, stotal); - } - - /* free the node */ - do_map_node_free(node); -} - static ENGINE_ERROR_CODE do_map_elem_replace_at(map_meta_info *info, htree_elem_pos *pos, map_elem_item *old_elem, @@ -291,109 +225,6 @@ static ENGINE_ERROR_CODE do_map_elem_link(map_meta_info *info, map_elem_item *el return ENGINE_SUCCESS; } -static void do_map_elem_unlink(map_meta_info *info, - map_hash_node *node, const int hidx, - map_elem_item *prev, map_elem_item *elem, - enum elem_delete_cause cause) -{ - if (prev != NULL) prev->next = elem->next; - else node->htab[hidx] = elem->next; - elem->status = ELEM_STATUS_UNLINKED; - node->hcnt[hidx] -= 1; - node->cur_elem_cnt -= 1; - info->ccnt--; - - CLOG_MAP_ELEM_DELETE(info, elem, cause); - - if (info->stotal > 0) { /* apply memory space */ - size_t stotal = slabs_space_size(do_map_elem_ntotal(elem)); - do_coll_space_decr((coll_meta_info *)info, ITEM_TYPE_MAP, stotal); - } - - if (elem->refcount == 0) { - do_map_elem_free(elem); - } -} - -static bool do_map_elem_traverse_dfs_byfield(map_meta_info *info, map_hash_node *node, const int hval, - const field_t *field, const bool delete, - map_elem_item **elem_array) -{ - bool ret; - int hidx = MAP_GET_HASHIDX(hval, node->hdepth); - - if (node->hcnt[hidx] == -1) { - map_hash_node *child_node = node->htab[hidx]; - ret = do_map_elem_traverse_dfs_byfield(info, child_node, hval, field, delete, elem_array); - if (ret && delete) { - if (child_node->cur_hash_cnt == 0 && - child_node->cur_elem_cnt < (MAP_MAX_HASHCHAIN_SIZE/2)) { - do_map_node_unlink(info, node, hidx); - } - } - } else { - ret = false; - if (node->hcnt[hidx] > 0) { - map_elem_item *prev = NULL; - map_elem_item *elem = node->htab[hidx]; - while (elem != NULL) { - if (map_hash_eq(hval, field->value, field->length, elem->hval, elem->data, elem->nfield)) { - if (elem_array) { - elem->refcount++; - elem_array[0] = elem; - } - - if (delete) { - do_map_elem_unlink(info, node, hidx, prev, elem, ELEM_DELETE_NORMAL); - } - ret = true; - break; - } - prev = elem; - elem = elem->next; - } - } - } - return ret; -} - -static int do_map_elem_traverse_dfs_bycnt(map_meta_info *info, map_hash_node *node, - const uint32_t count, const bool delete, - map_elem_item **elem_array, enum elem_delete_cause cause) -{ - int hidx; - int fcnt = 0; /* found count */ - - for (hidx = 0; hidx < MAP_HASHTAB_SIZE; hidx++) { - if (node->hcnt[hidx] == -1) { - map_hash_node *child_node = (map_hash_node *)node->htab[hidx]; - int rcnt = (count > 0 ? (count - fcnt) : 0); - fcnt += do_map_elem_traverse_dfs_bycnt(info, child_node, rcnt, delete, - (elem_array==NULL ? NULL : &elem_array[fcnt]), cause); - if (delete) { - if (child_node->cur_hash_cnt == 0 && - child_node->cur_elem_cnt < (MAP_MAX_HASHCHAIN_SIZE/2)) { - do_map_node_unlink(info, node, hidx); - } - } - } else if (node->hcnt[hidx] > 0) { - map_elem_item *elem = node->htab[hidx]; - while (elem != NULL) { - if (elem_array) { - elem->refcount++; - elem_array[fcnt] = elem; - } - fcnt++; - if (delete) do_map_elem_unlink(info, node, hidx, NULL, elem, cause); - if (count > 0 && fcnt >= count) break; - elem = (delete ? node->htab[hidx] : elem->next); - } - } - if (count > 0 && fcnt >= count) break; - } - return fcnt; -} - static ENGINE_ERROR_CODE do_map_elem_update(map_meta_info *info, const field_t *field, const char *value, const uint32_t nbytes, const void *cookie) @@ -520,33 +351,73 @@ static uint32_t do_map_elem_delete_all(map_meta_info *info) return fcnt; } -static uint32_t do_map_elem_get(map_meta_info *info, - const int numfields, const field_t *flist, - const bool delete, map_elem_item **elem_array) +static uint32_t do_map_elem_get_by_fields(map_meta_info *info, + const int numfields, const field_t *flist, + const bool delete, map_elem_item **elem_array) { - assert(info->root); + assert(info->root && numfields > 0 && elem_array != NULL); uint32_t fcnt = 0; + ssize_t space_delta = 0; if (delete) { CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, numfields, ELEM_DELETE_NORMAL); } - if (numfields == 0) { - fcnt = do_map_elem_traverse_dfs_bycnt(info, info->root, 0, delete, - elem_array, ELEM_DELETE_NORMAL); - } else { - for (int ii = 0; ii < numfields; ii++) { - int hval = genhash_string_hash(flist[ii].value, flist[ii].length); - if (do_map_elem_traverse_dfs_byfield(info, info->root, hval, &flist[ii], - delete, &elem_array[fcnt])) { - fcnt++; - } + for (int ii = 0; ii < numfields; ii++) { + if (delete) { + ssize_t delta; + map_elem_item *elem = do_map_elem_unlink_by_field(info, flist[ii].value, + flist[ii].length, &delta); + if (elem == NULL) continue; + elem_array[fcnt] = elem; + space_delta += delta; + } else { + map_elem_item *elem = (map_elem_item *)htree_elem_find((htree_node *)info->root, + flist[ii].value, flist[ii].length, + &map_htree_ops, NULL); + if (elem == NULL) + continue; + elem->refcount++; + elem_array[fcnt] = elem; } + fcnt++; } - if (delete && info->root->cur_hash_cnt == 0 && info->root->cur_elem_cnt == 0) { - do_map_node_unlink(info, NULL, 0); + if (delete) { + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); } + return fcnt; +} + +static uint32_t do_map_elem_get_all(map_meta_info *info, + const bool delete, map_elem_item **elem_array) +{ + assert(info->root && elem_array != NULL); + ssize_t space_delta; + uint32_t fcnt; + if (delete) { + CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, 0, ELEM_DELETE_NORMAL); + + fcnt = htree_elem_get_by_cnt((htree_node **)&info->root, 0, + (htree_elem_item **)elem_array, true, &space_delta); + for (uint32_t i = 0; i < fcnt; i++) { + map_elem_item *e = (map_elem_item *)elem_array[i]; + space_delta -= do_map_elem_unlink_process(info, e); + elem_array[i] = e; + } + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_MAP, space_delta); + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); + } else { + fcnt = htree_elem_get_by_cnt((htree_node **)&info->root, 0, + (htree_elem_item **)elem_array, false, NULL); + for (uint32_t i = 0; i < fcnt; i++) { + map_elem_item *e = (map_elem_item *)elem_array[i]; + e->refcount++; + elem_array[i] = e; + } } return fcnt; } @@ -768,8 +639,13 @@ ENGINE_ERROR_CODE map_elem_get(const char *key, const uint32_t nkey, if (eresult->elem_array == NULL) { ret = ENGINE_ENOMEM; break; } - eresult->elem_count = do_map_elem_get(info, numfields, flist, delete, - (map_elem_item **)eresult->elem_array); + if (numfields == 0) { + eresult->elem_count = do_map_elem_get_all(info, delete, + (map_elem_item **)eresult->elem_array); + } else { + eresult->elem_count = do_map_elem_get_by_fields(info, numfields, flist, delete, + (map_elem_item **)eresult->elem_array); + } if (eresult->elem_count > 0) { if (info->ccnt == 0 && drop_if_empty) { assert(delete == true); @@ -812,74 +688,11 @@ uint32_t map_elem_delete_with_count(map_meta_info *info, const uint32_t count) return fcnt; } -/* See do_map_elem_traverse_dfs and do_map_elem_link. do_map_elem_traverse_dfs - * can visit all elements, but only supports get and delete operations. - * Do something similar and visit all elements. - */ void map_elem_get_all(map_meta_info *info, elems_result_t *eresult) { assert(eresult->elem_arrsz >= info->ccnt && eresult->elem_count == 0); - map_hash_node *node; - map_elem_item *elem; - int cur_depth, i; - bool push; - - /* Temporay stack we use to do dfs. Static is ugly but is okay... - * This function runs with the cache lock acquired. - */ - static int stack_max = 0; - static struct _map_hash_posi { - map_hash_node *node; - int idx; - } *stack = NULL; - - node = info->root; - cur_depth = 0; - push = true; - while (node != NULL) { - if (push) { - push = false; - if (stack_max <= cur_depth) { - struct _map_hash_posi *tmp; - stack_max += 16; - tmp = realloc(stack, sizeof(*stack) * stack_max); - assert(tmp != NULL); - stack = tmp; - } - stack[cur_depth].node = node; - stack[cur_depth].idx = 0; - } - - /* Scan the current node */ - for (i = stack[cur_depth].idx; i < MAP_HASHTAB_SIZE; i++) { - if (node->hcnt[i] >= 0) { - /* Hash chain. Insert all elements on the chain into the - * to-be-copied list. - */ - for (elem = node->htab[i]; elem != NULL; elem = elem->next) { - elem->refcount++; - eresult->elem_array[eresult->elem_count++] = elem; - } - } - else if (node->htab[i] != NULL) { - /* Another hash node. Go down */ - stack[cur_depth].idx = i+1; - push = true; - node = node->htab[i]; - cur_depth++; - break; - } - } - - /* Scannned everything in this node. Go up. */ - if (i >= MAP_HASHTAB_SIZE) { - cur_depth--; - if (cur_depth < 0) - node = NULL; /* done */ - else - node = stack[cur_depth].node; - } - } + eresult->elem_count = do_map_elem_get_all(info, false, + (map_elem_item **)eresult->elem_array); assert(eresult->elem_count == info->ccnt); } diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 9656889d..3bfa80d8 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -62,6 +62,11 @@ static inline bool htree_key_eq_raw(htree_ops *ops, const htree_elem_item *elem, return lnkey == nkey && memcmp(lkey, key, nkey) == 0; } +static void collect_to_array(htree_elem_item *elem, htree_collect_ctx *ctx) +{ + *ctx->pos++ = elem; +} + static void collect_to_chain(htree_elem_item *elem, htree_collect_ctx *ctx) { ctx->tail->next = elem; @@ -501,3 +506,30 @@ htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, do_htree_node_try_merge(root_pptr, NULL, 0, *root_pptr, htree_space_delta); return dummy.next; } + +uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, + uint32_t count, + htree_elem_item **elem_array, + bool unlink, + ssize_t *htree_space_delta) +{ + assert(elem_array != NULL); + if (*root_pptr == NULL) return 0; + if (htree_space_delta) *htree_space_delta = 0; + + /* count == 0 means "all"; take == 0 when tree is empty */ + uint32_t actual = (count > 0) ? count : (*root_pptr)->tot_elem_cnt; + if (actual == 0) return 0; + + uint32_t skip = 0, take = actual; + htree_collect_ctx ctx = { .pos = elem_array }; + + /* skip 0, collect `take` elems */ + uint32_t fcnt = do_htree_range(root_pptr, *root_pptr, &skip, &take, unlink, + collect_to_array, &ctx, + unlink ? htree_space_delta : NULL); + + if (unlink) + do_htree_node_try_merge(root_pptr, NULL, 0, *root_pptr, htree_space_delta); + return fcnt; +} diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h index b194843b..83128ff8 100644 --- a/engines/default/hash_tree.h +++ b/engines/default/hash_tree.h @@ -84,4 +84,10 @@ htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, uint32_t count, ssize_t *htree_space_delta); +uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, + uint32_t count, + htree_elem_item **elem_array, + bool unlink, + ssize_t *htree_space_delta); + #endif From df63f64bff19ac5e9e780bd995dc992481dd321f Mon Sep 17 00:00:00 2001 From: zhy2on Date: Thu, 14 May 2026 19:42:55 +0900 Subject: [PATCH 05/11] INTERNAL: Replace set get path with hash_tree module --- engines/default/coll_set.c | 462 +++++++----------------------------- engines/default/hash_tree.c | 107 +++++++++ engines/default/hash_tree.h | 12 + engines/default/item_base.h | 32 +-- 4 files changed, 205 insertions(+), 408 deletions(-) diff --git a/engines/default/coll_set.c b/engines/default/coll_set.c index ecc420ba..e3ec9717 100644 --- a/engines/default/coll_set.c +++ b/engines/default/coll_set.c @@ -62,52 +62,6 @@ static htree_ops set_htree_ops = { .get_key = set_get_key, }; -/* - * Hash table management - */ -typedef struct hash_node { - int cnt; - int keys[3]; -} hash_node; - -typedef struct hash_table { - hash_node *buckets; - int capacity; -} hash_table; - -static bool hash_init(hash_table *ht, int count) -{ - ht->capacity = (int)(1.3 * (double)count); - ht->buckets = (hash_node*)calloc(ht->capacity, sizeof(hash_node)); - if (ht->buckets == NULL) - return false; - return true; -} - -static void hash_free(hash_table *ht) -{ - if (ht->buckets) { - free(ht->buckets); - ht->buckets = NULL; - ht->capacity = 0; - } -} - -static bool hash_insert(hash_table *ht, int key) -{ - size_t idx = key % ht->capacity; - hash_node *bucket = &ht->buckets[idx]; - if (bucket->cnt == 3) - return false; - for (int i = 0; i < bucket->cnt; i++) { - if (bucket->keys[i] == key) - return false; - } - bucket->keys[bucket->cnt] = key; - bucket->cnt++; - return true; -} - /* * SET collection manangement */ @@ -120,15 +74,6 @@ static inline uint32_t do_set_elem_ntotal(set_elem_item *elem) return sizeof(set_elem_item) + elem->nbytes; } -static inline bool is_leaf_node(set_hash_node *node) -{ - for (int hidx = 0; hidx < SET_HASHTAB_SIZE; hidx++) { - if (node->hcnt[hidx] == -1) - return false; - } - return true; -} - static ENGINE_ERROR_CODE do_set_item_find(const void *key, const uint32_t nkey, bool do_update, hash_item **item) { @@ -193,11 +138,6 @@ static hash_item *do_set_item_alloc(const void *key, const uint32_t nkey, return it; } -static void do_set_node_free(set_hash_node *node) -{ - do_item_mem_free(node, sizeof(set_hash_node)); -} - static set_elem_item *do_set_elem_alloc(const uint32_t nbytes, const void *cookie) { size_t ntotal = sizeof(set_elem_item) + nbytes; @@ -232,258 +172,6 @@ static void do_set_elem_release(set_elem_item *elem) } } -static void do_set_node_unlink(set_meta_info *info, - set_hash_node *par_node, const int par_hidx) -{ - set_hash_node *node; - - if (par_node == NULL) { - node = info->root; - info->root = NULL; - assert(node->tot_elem_cnt == 0); - } else { - assert(par_node->hcnt[par_hidx] == -1); /* child hash node */ - set_elem_item *head = NULL; - set_elem_item *elem; - int hidx, fcnt = 0; - - node = (set_hash_node *)par_node->htab[par_hidx]; - - for (hidx = 0; hidx < SET_HASHTAB_SIZE; hidx++) { - assert(node->hcnt[hidx] >= 0); - if (node->hcnt[hidx] > 0) { - fcnt += node->hcnt[hidx]; - while (node->htab[hidx] != NULL) { - elem = node->htab[hidx]; - node->htab[hidx] = elem->next; - node->hcnt[hidx] -= 1; - - elem->next = head; - head = elem; - } - assert(node->hcnt[hidx] == 0); - } - } - assert(fcnt == node->tot_elem_cnt); - node->tot_elem_cnt = 0; - - par_node->htab[par_hidx] = head; - par_node->hcnt[par_hidx] = fcnt; - } - - if (info->stotal > 0) { /* apply memory space */ - size_t stotal = slabs_space_size(sizeof(set_hash_node)); - do_coll_space_decr((coll_meta_info *)info, ITEM_TYPE_SET, stotal); - } - - /* free the node */ - do_set_node_free(node); -} - -static void do_set_elem_unlink(set_meta_info *info, - set_hash_node *node, const int hidx, - set_elem_item *prev, set_elem_item *elem, - enum elem_delete_cause cause) -{ - if (prev != NULL) prev->next = elem->next; - else node->htab[hidx] = elem->next; - elem->status = ELEM_STATUS_UNLINKED; - node->hcnt[hidx] -= 1; - node->tot_elem_cnt -= 1; - info->ccnt--; - - CLOG_SET_ELEM_DELETE(info, elem, cause); - - if (info->stotal > 0) { /* apply memory space */ - size_t stotal = slabs_space_size(do_set_elem_ntotal(elem)); - do_coll_space_decr((coll_meta_info *)info, ITEM_TYPE_SET, stotal); - } - - if (elem->refcount == 0) { - do_set_elem_free(elem); - } -} - -static int do_set_elem_traverse_dfs(set_meta_info *info, set_hash_node *node, - const uint32_t count, const bool delete, - set_elem_item **elem_array) -{ - int hidx; - int fcnt = 0; /* found count */ - - for (hidx = 0; hidx < SET_HASHTAB_SIZE; hidx++) { - if (node->hcnt[hidx] == -1) { - set_hash_node *child_node = (set_hash_node *)node->htab[hidx]; - int rcnt = (count > 0 ? (count - fcnt) : 0); - int ecnt = do_set_elem_traverse_dfs(info, child_node, rcnt, delete, - (elem_array==NULL ? NULL : &elem_array[fcnt])); - fcnt += ecnt; - if (delete) { - if (child_node->tot_elem_cnt < (SET_MAX_HASHCHAIN_SIZE/2) - && is_leaf_node(child_node)) { - do_set_node_unlink(info, node, hidx); - } - node->tot_elem_cnt -= ecnt; - } - } else if (node->hcnt[hidx] > 0) { - set_elem_item *elem = node->htab[hidx]; - while (elem != NULL) { - if (elem_array) { - elem->refcount++; - elem_array[fcnt] = elem; - } - fcnt++; - if (delete) do_set_elem_unlink(info, node, hidx, NULL, elem, - (elem_array==NULL ? ELEM_DELETE_COLL - : ELEM_DELETE_NORMAL)); - if (count > 0 && fcnt >= count) break; - elem = (delete ? node->htab[hidx] : elem->next); - } - } - if (count > 0 && fcnt >= count) break; - } - return fcnt; -} - -static int do_set_elem_traverse_sampling(set_meta_info *info, set_hash_node *node, - uint32_t remain, const uint32_t count, - set_elem_item **elem_array) -{ - int hidx; - int fcnt = 0; /* found count */ - - for (hidx = 0; hidx < SET_HASHTAB_SIZE; hidx++) { - if (node->hcnt[hidx] == -1) { - set_hash_node *child_node = (set_hash_node *)node->htab[hidx]; - fcnt += do_set_elem_traverse_sampling(info, child_node, remain, - count - fcnt, &elem_array[fcnt]); - remain -= child_node->tot_elem_cnt; - } else if (node->hcnt[hidx] > 0) { - set_elem_item *elem = node->htab[hidx]; - while (elem != NULL) { - if ((rand() % remain) < (count - fcnt)) { - elem->refcount++; - elem_array[fcnt] = elem; - fcnt++; - if (fcnt >= count) break; - } - remain -= 1; - elem = elem->next; - } - } - if (fcnt >= count) break; - } - return fcnt; -} - -static set_elem_item *do_set_elem_at_offset(set_meta_info *info, set_hash_node *node, - uint32_t offset, const bool delete) -{ - int hidx; - for (hidx = 0; hidx < SET_HASHTAB_SIZE; hidx++) { - if (node->hcnt[hidx] == -1) { - set_hash_node *child_node = (set_hash_node *)node->htab[hidx]; - if (offset >= child_node->tot_elem_cnt) { - offset -= child_node->tot_elem_cnt; - continue; - } - set_elem_item *found = do_set_elem_at_offset(info, child_node, offset, delete); - if (delete) { - if (child_node->tot_elem_cnt < (SET_MAX_HASHCHAIN_SIZE/2) - && is_leaf_node(child_node)) { - do_set_node_unlink(info, node, hidx); - } - node->tot_elem_cnt -= 1; - } - return found; - } else if (node->hcnt[hidx] > 0) { - if (offset >= node->hcnt[hidx]) { - offset -= node->hcnt[hidx]; - continue; - } - set_elem_item *prev = NULL; - set_elem_item *elem = node->htab[hidx]; - while (offset > 0) { - prev = elem; - elem = elem->next; - offset -= 1; - } - elem->refcount++; - if (delete) do_set_elem_unlink(info, node, hidx, prev, elem, - ELEM_DELETE_NORMAL); - return elem; - } - } - return NULL; -} - -static uint32_t do_set_elem_traverse_rand(set_meta_info *info, - const uint32_t count, const bool delete, - set_elem_item **elem_array) -{ - uint32_t fcnt = 0; - - if (delete) { /* Deleting partial elements */ - while (fcnt < count) { - int rand_offset = (rand() % info->ccnt); - set_elem_item *found = do_set_elem_at_offset(info, info->root, - rand_offset, delete); - assert(found != NULL); - elem_array[fcnt++] = found; - } - } else if (count <= info->ccnt / 10) { /* Use hash table */ - hash_table offset_ht; - if (!hash_init(&offset_ht, count)) - return 0; - while (fcnt < count) { - int rand_offset = (rand() % info->ccnt); - if (hash_insert(&offset_ht, rand_offset)) { - set_elem_item *found = do_set_elem_at_offset(info, info->root, - rand_offset, delete); - assert(found != NULL); - elem_array[fcnt++] = found; - } - } - hash_free(&offset_ht); - } else { /* Use sampling */ - fcnt = do_set_elem_traverse_sampling(info, info->root, info->ccnt, - count, elem_array); - for (int i = fcnt - 1; i > 0; i--) { - int rand_idx = rand() % (i + 1); - if (rand_idx != i) { - set_elem_item *temp = elem_array[i]; - elem_array[i] = elem_array[rand_idx]; - elem_array[rand_idx] = temp; - } - } - } - return fcnt; -} - -static uint32_t do_set_elem_get(set_meta_info *info, - const uint32_t count, const bool delete, - set_elem_item **elem_array) -{ - assert(info->root); - uint32_t fcnt; - - if (delete) { - CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, count, ELEM_DELETE_NORMAL); - } - if (count >= info->ccnt || count == 0) { /* Return all */ - fcnt = do_set_elem_traverse_dfs(info, info->root, count, delete, elem_array); - } else { /* Return some */ - fcnt = do_set_elem_traverse_rand(info, count, delete, elem_array); - } - if (delete && info->root->tot_elem_cnt == 0) { - do_set_node_unlink(info, NULL, 0); - } - if (delete) { - CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); - } - return fcnt; -} - static ENGINE_ERROR_CODE do_set_elem_insert(hash_item *it, set_elem_item *elem, const void *cookie) { @@ -677,6 +365,80 @@ ENGINE_ERROR_CODE set_elem_delete(const char *key, const uint32_t nkey, return ret; } +static uint32_t do_set_elem_get_rand(set_meta_info *info, + const uint32_t count, const bool delete, + set_elem_item **elem_array) +{ + assert(info->root); + + if (delete) { + uint32_t fcnt = 0; + ssize_t space_delta = 0; + uint32_t total_count = info->ccnt; + CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, count, ELEM_DELETE_NORMAL); + + while (fcnt < count) { + uint32_t rand_offset = (uint32_t)(rand() % (int)total_count); + ssize_t delta; + htree_elem_item *unlinked = htree_elem_unlink_at_offset((htree_node **)&info->root, + rand_offset, &delta); + set_elem_item *e = (set_elem_item *)unlinked; + delta -= do_set_elem_unlink_process(info, e); + elem_array[fcnt++] = e; + space_delta += delta; + total_count--; + } + + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_SET, space_delta); + + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); + return fcnt; + } + + uint32_t fcnt = htree_elem_get_rand(info->root, info->ccnt, count, + (htree_elem_item **)elem_array); + for (uint32_t i = 0; i < fcnt; i++) { + set_elem_item *e = (set_elem_item *)elem_array[i]; + e->refcount++; + elem_array[i] = e; + } + return fcnt; +} + +static uint32_t do_set_elem_get_all(set_meta_info *info, + const bool delete, set_elem_item **elem_array) +{ + assert(info->root && elem_array != NULL); + ssize_t space_delta; + uint32_t fcnt; + + if (delete) { + CLOG_ELEM_DELETE_BEGIN((coll_meta_info*)info, 0, ELEM_DELETE_NORMAL); + + fcnt = htree_elem_get_by_cnt((htree_node **)&info->root, 0, + (htree_elem_item **)elem_array, true, &space_delta); + for (uint32_t i = 0; i < fcnt; i++) { + set_elem_item *e = (set_elem_item *)elem_array[i]; + space_delta -= do_set_elem_unlink_process(info, e); + elem_array[i] = e; + } + info->ccnt -= fcnt; + do_coll_space_update((coll_meta_info *)info, ITEM_TYPE_SET, space_delta); + + CLOG_ELEM_DELETE_END((coll_meta_info*)info, ELEM_DELETE_NORMAL); + } else { + fcnt = htree_elem_get_by_cnt((htree_node **)&info->root, 0, + (htree_elem_item **)elem_array, false, NULL); + for (uint32_t i = 0; i < fcnt; i++) { + set_elem_item *e = (set_elem_item *)elem_array[i]; + e->refcount++; + elem_array[i] = e; + } + } + return fcnt; +} + ENGINE_ERROR_CODE set_elem_exist(const char *key, const uint32_t nkey, const char *value, const uint32_t nbytes, bool *exist) @@ -733,8 +495,13 @@ ENGINE_ERROR_CODE set_elem_get(const char *key, const uint32_t nkey, if (eresult->elem_array == NULL) { ret = ENGINE_ENOMEM; break; } - eresult->elem_count = do_set_elem_get(info, count, delete, - (set_elem_item**)(eresult->elem_array)); + if (count == 0 || count >= info->ccnt) { + eresult->elem_count = do_set_elem_get_all(info, delete, + (set_elem_item**)(eresult->elem_array)); + } else { + eresult->elem_count = do_set_elem_get_rand(info, count, delete, + (set_elem_item**)(eresult->elem_array)); + } if (eresult->elem_count == 0) { free(eresult->elem_array); eresult->elem_array = NULL; @@ -778,74 +545,11 @@ uint32_t set_elem_delete_with_count(set_meta_info *info, const uint32_t count) return fcnt; } -/* See do_set_elem_traverse_dfs and do_set_elem_link. do_set_elem_traverse_dfs - * can visit all elements, but only supports get and delete operations. - * Do something similar and visit all elements. - */ void set_elem_get_all(set_meta_info *info, elems_result_t *eresult) { assert(eresult->elem_arrsz >= info->ccnt && eresult->elem_count == 0); - set_hash_node *node; - set_elem_item *elem; - int cur_depth, i; - bool push; - - /* Temporay stack we use to do dfs. Static is ugly but is okay... - * This function runs with the cache lock acquired. - */ - static int stack_max = 0; - static struct _set_hash_posi { - set_hash_node *node; - int idx; - } *stack = NULL; - - node = info->root; - cur_depth = 0; - push = true; - while (node != NULL) { - if (push) { - push = false; - if (stack_max <= cur_depth) { - struct _set_hash_posi *tmp; - stack_max += 16; - tmp = realloc(stack, sizeof(*stack) * stack_max); - assert(tmp != NULL); - stack = tmp; - } - stack[cur_depth].node = node; - stack[cur_depth].idx = 0; - } - - /* Scan the current node */ - for (i = stack[cur_depth].idx; i < SET_HASHTAB_SIZE; i++) { - if (node->hcnt[i] >= 0) { - /* Hash chain. Insert all elements on the chain into the - * to-be-copied list. - */ - for (elem = node->htab[i]; elem != NULL; elem = elem->next) { - elem->refcount++; - eresult->elem_array[eresult->elem_count++] = elem; - } - } - else if (node->htab[i] != NULL) { - /* Another hash node. Go down */ - stack[cur_depth].idx = i+1; - push = true; - node = node->htab[i]; - cur_depth++; - break; - } - } - - /* Scannned everything in this node. Go up. */ - if (i >= SET_HASHTAB_SIZE) { - cur_depth--; - if (cur_depth < 0) - node = NULL; /* done */ - else - node = stack[cur_depth].node; - } - } + eresult->elem_count = do_set_elem_get_all(info, false, + (set_elem_item **)eresult->elem_array); assert(eresult->elem_count == info->ccnt); } diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 3bfa80d8..cf4ad7e5 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -345,6 +345,35 @@ static uint32_t do_htree_range(htree_node **root_pptr, return fcnt; } +/* DFS over the hash tree: collect count elems by deciding each one randomly. + * Each elem is accepted with probability (needed / remain). */ +static int do_htree_sampling(htree_node *node, + uint32_t remain, uint32_t count, + htree_elem_item **elem_array) +{ + int fcnt = 0; + for (int hidx = 0; hidx < HTREE_HASHTAB_SIZE; hidx++) { + if (node->hcnt[hidx] == -1) { + htree_node *child_node = (htree_node *)node->htab[hidx]; + fcnt += do_htree_sampling(child_node, remain, + count - fcnt, &elem_array[fcnt]); + remain -= child_node->tot_elem_cnt; /* mark child's elems as seen */ + } else if (node->hcnt[hidx] > 0) { + htree_elem_item *elem = (htree_elem_item *)node->htab[hidx]; + while (elem != NULL) { + if ((rand() % remain) < (count - (uint32_t)fcnt)) { + elem_array[fcnt++] = elem; + if ((uint32_t)fcnt >= count) break; + } + remain -= 1; + elem = elem->next; + } + } + if ((uint32_t)fcnt >= count) break; + } + return fcnt; +} + htree_elem_item *htree_elem_find(htree_node *root, const void *key, uint16_t nkey, htree_ops *ops, @@ -483,6 +512,39 @@ htree_elem_item *htree_elem_unlink(htree_node **root_pptr, return elem; } +htree_elem_item *htree_elem_get_at_offset(htree_node *node, uint32_t offset) +{ + /* acts as a 1-elem array for collect_to_array */ + htree_elem_item *elem = NULL; + uint32_t skip = offset, take = 1; + htree_collect_ctx ctx = { .pos = &elem }; + + /* skip `offset`, read 1 */ + do_htree_range(&node, node, &skip, &take, false, collect_to_array, &ctx, NULL); + return elem; +} + +htree_elem_item *htree_elem_unlink_at_offset(htree_node **root_pptr, + uint32_t offset, + ssize_t *htree_space_delta) +{ + if (*root_pptr == NULL) return NULL; + if (htree_space_delta) *htree_space_delta = 0; + + /* sentinel head: collect_to_chain links the result onto dummy.next */ + htree_elem_item dummy = {0}; + uint32_t skip = offset, take = 1; + htree_collect_ctx ctx = { .tail = &dummy }; + + /* skip `offset`, unlink 1 */ + do_htree_range(root_pptr, *root_pptr, &skip, &take, true, collect_to_chain, &ctx, + htree_space_delta); + if (dummy.next == NULL) return NULL; + + do_htree_node_try_merge(root_pptr, NULL, 0, *root_pptr, htree_space_delta); + return dummy.next; +} + htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, uint32_t count, ssize_t *htree_space_delta) @@ -533,3 +595,48 @@ uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, do_htree_node_try_merge(root_pptr, NULL, 0, *root_pptr, htree_space_delta); return fcnt; } + +int htree_elem_get_rand(htree_node *node, + uint32_t total_count, uint32_t count, + htree_elem_item **elem_array) +{ + int fcnt = 0; + + if (count <= total_count / 10) { + /* sparse: offset dedup hash table */ + int capacity = (int)(1.3 * (double)count); + typedef struct { int cnt; int keys[3]; } _bucket; + _bucket *buckets = calloc(capacity, sizeof(_bucket)); + if (buckets == NULL) return 0; + while ((uint32_t)fcnt < count) { + int rand_offset = rand() % (int)total_count; + int idx = rand_offset % capacity; + _bucket *b = &buckets[idx]; + bool dup = false; + for (int i = 0; i < b->cnt; i++) { + if (b->keys[i] == rand_offset) { dup = true; break; } + } + if (!dup && b->cnt < 3) { + b->keys[b->cnt++] = rand_offset; + htree_elem_item *found = htree_elem_get_at_offset(node, rand_offset); + assert(found != NULL); + elem_array[fcnt++] = found; + } + } + free(buckets); + } else { + /* dense: reservoir sampling + Fisher-Yates shuffle */ + fcnt = do_htree_sampling(node, total_count, count, elem_array); /* reservoir sampling */ + + /* sampling collects elems in DFS order, so shuffle to randomize output order */ + for (int i = fcnt - 1; i > 0; i--) { /* Fisher-Yates shuffle */ + int rand_idx = rand() % (i + 1); + if (rand_idx != i) { + htree_elem_item *tmp = elem_array[i]; + elem_array[i] = elem_array[rand_idx]; + elem_array[rand_idx] = tmp; + } + } + } + return fcnt; +} diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h index 83128ff8..ee023c5e 100644 --- a/engines/default/hash_tree.h +++ b/engines/default/hash_tree.h @@ -80,14 +80,26 @@ htree_elem_item *htree_elem_unlink(htree_node **root_pptr, htree_ops *ops, ssize_t *htree_space_delta); +/* unlinks the element matching the given key and returns it. + * returns NULL if not found. caller is responsible for CLOG and free. */ +htree_elem_item *htree_elem_unlink_at_offset(htree_node **root_pptr, + uint32_t offset, + ssize_t *htree_space_delta); + htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, uint32_t count, ssize_t *htree_space_delta); +htree_elem_item *htree_elem_get_at_offset(htree_node *node, uint32_t offset); + uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, uint32_t count, htree_elem_item **elem_array, bool unlink, ssize_t *htree_space_delta); +int htree_elem_get_rand(htree_node *node, + uint32_t total_count, uint32_t count, + htree_elem_item **elem_array); + #endif diff --git a/engines/default/item_base.h b/engines/default/item_base.h index 83e6835c..7027eb7c 100644 --- a/engines/default/item_base.h +++ b/engines/default/item_base.h @@ -21,6 +21,7 @@ #include #include #include +#include "hash_tree.h" /* max collection size */ #define MINIMUM_MAX_COLL_SIZE 10000 @@ -228,19 +229,6 @@ typedef struct _list_meta_info { } list_meta_info; /* set meta info */ -#define SET_HASHTAB_SIZE 16 -#define SET_HASHIDX_MASK 0x0000000F -#define SET_MAX_HASHCHAIN_SIZE 64 - -typedef struct _set_hash_node { - uint16_t refcount; - uint8_t slabs_clsid; /* which slab class we're in */ - uint8_t hdepth; - uint32_t tot_elem_cnt; - int16_t hcnt[SET_HASHTAB_SIZE]; - void *htab[SET_HASHTAB_SIZE]; -} set_hash_node; - typedef struct _set_meta_info { int32_t mcnt; /* maximum count */ int32_t ccnt; /* current count */ @@ -248,24 +236,10 @@ typedef struct _set_meta_info { uint8_t mflags; /* sticky, readable flags */ uint16_t itdist; /* distance from hash item (unit: sizeof(size_t)) */ uint32_t stotal; /* total space */ - set_hash_node *root; + htree_node *root; } set_meta_info; /* map meta info */ -#define MAP_HASHTAB_SIZE 16 -#define MAP_HASHIDX_MASK 0x0000000F -#define MAP_MAX_HASHCHAIN_SIZE 64 - -typedef struct _map_hash_node { - uint16_t refcount; - uint8_t slabs_clsid; /* which slab class we're in */ - uint8_t hdepth; - uint16_t cur_elem_cnt; - uint16_t cur_hash_cnt; - int16_t hcnt[MAP_HASHTAB_SIZE]; - void *htab[MAP_HASHTAB_SIZE]; -} map_hash_node; - typedef struct _map_meta_info { int32_t mcnt; /* maximum count */ int32_t ccnt; /* current count */ @@ -273,7 +247,7 @@ typedef struct _map_meta_info { uint8_t mflags; /* sticky, readable flags */ uint16_t itdist; /* distance from hash item (unit: sizeof(size_t)) */ uint32_t stotal; /* total space */ - map_hash_node *root; + htree_node *root; } map_meta_info; /* btree meta info */ From 2822154cdbc0ce26cdc611aa614f02a13383247d Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 10:19:41 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20do=5Fmap=5Felem=5Finsert?= =?UTF-8?q?=EC=9D=98=20replaced=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/coll_map.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/engines/default/coll_map.c b/engines/default/coll_map.c index 3f9d79b1..2d0e5efd 100644 --- a/engines/default/coll_map.c +++ b/engines/default/coll_map.c @@ -432,17 +432,18 @@ static ENGINE_ERROR_CODE do_map_elem_insert(hash_item *it, map_elem_item *elem, map_elem_item *old_elem = (map_elem_item *)htree_elem_find((htree_node *)info->root, elem->data, elem->nfield, &map_htree_ops, &pos); - if (old_elem != NULL) { - if (!replace_if_exist) - return ENGINE_ELEM_EEXISTS; - ENGINE_ERROR_CODE ret = do_map_elem_replace_at(info, &pos, old_elem, elem); - if (ret != ENGINE_SUCCESS) - return ret; - if (replaced) *replaced = true; - return ENGINE_SUCCESS; - } - return do_map_elem_link(info, elem, cookie); + if (replaced) *replaced = false; + + if (old_elem == NULL) + return do_map_elem_link(info, elem, cookie); + + if (!replace_if_exist) + return ENGINE_ELEM_EEXISTS; + + ENGINE_ERROR_CODE ret = do_map_elem_replace_at(info, &pos, old_elem, elem); + if (ret == ENGINE_SUCCESS && replaced) *replaced = true; + return ret; } /* @@ -516,7 +517,6 @@ ENGINE_ERROR_CODE map_elem_insert(const char *key, const uint32_t nkey, PERSISTENCE_ACTION_BEGIN(cookie, UPD_MAP_ELEM_INSERT); *created = false; - *replaced = false; LOCK_CACHE(); ret = do_map_item_find(key, nkey, DONT_UPDATE, &it); From d2f1cc58ab9b0c7161ea473cebd0810be553afb0 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 10:59:36 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20do=5Fhtree=5Felem=5Flink/unli?= =?UTF-8?q?nk=EC=9D=98=20root=20=EC=9D=B8=EC=9E=90=EB=A5=BC=20=EC=9D=B4?= =?UTF-8?q?=EC=A4=91=ED=8F=AC=EC=9D=B8=ED=84=B0=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=ED=8F=AC=EC=9D=B8=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/hash_tree.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index cf4ad7e5..88be6eb6 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -223,7 +223,7 @@ static void do_htree_node_try_merge(htree_node **root_pptr, } } -static void do_htree_elem_link(htree_node **root_pptr, +static void do_htree_elem_link(htree_node *root, htree_node *node, const int hidx, htree_elem_item *elem) { @@ -233,7 +233,7 @@ static void do_htree_elem_link(htree_node **root_pptr, node->hcnt[hidx] += 1; /* increment tot_elem_cnt on every node from root down to the target node */ - htree_node *cur = *root_pptr; + htree_node *cur = root; while (cur != node) { cur->tot_elem_cnt += 1; int cidx = HTREE_GET_HASHIDX(elem->hval, cur->hdepth); @@ -243,7 +243,7 @@ static void do_htree_elem_link(htree_node **root_pptr, node->tot_elem_cnt += 1; } -static void do_htree_elem_unlink(htree_node **root_pptr, +static void do_htree_elem_unlink(htree_node *root, htree_node *node, int hidx, htree_elem_item *prev, htree_elem_item *elem) { @@ -255,7 +255,7 @@ static void do_htree_elem_unlink(htree_node **root_pptr, elem->next = NULL; /* decrement tot_elem_cnt on every node from root down to the target node */ - htree_node *cur = *root_pptr; + htree_node *cur = root; while (cur != node) { cur->tot_elem_cnt -= 1; int cidx = HTREE_GET_HASHIDX(elem->hval, cur->hdepth); @@ -435,13 +435,12 @@ ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, /* allocate root node if the tree is empty */ if (*root_pptr == NULL) { - htree_node *root = do_htree_node_alloc(0, cookie); - if (root == NULL) + *root_pptr = do_htree_node_alloc(0, cookie); + if (*root_pptr == NULL) return ENGINE_ENOMEM; - *root_pptr = root; space_delta_add(htree_space_delta, (ssize_t)slabs_space_size(sizeof(htree_node))); int hidx = HTREE_GET_HASHIDX(elem->hval, 0); - do_htree_elem_link(root_pptr, root, hidx, elem); + do_htree_elem_link(*root_pptr, *root_pptr, hidx, elem); return ENGINE_SUCCESS; } @@ -467,7 +466,7 @@ ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, htree_space_delta, cookie)) return ENGINE_ENOMEM; - do_htree_elem_link(root_pptr, node, hidx, elem); + do_htree_elem_link(*root_pptr, node, hidx, elem); return ENGINE_SUCCESS; } @@ -505,7 +504,7 @@ htree_elem_item *htree_elem_unlink(htree_node **root_pptr, } if (elem == NULL) return NULL; - do_htree_elem_unlink(root_pptr, node, hidx, prev, elem); + do_htree_elem_unlink(*root_pptr, node, hidx, prev, elem); do_htree_node_try_merge(root_pptr, par_node, par_hidx, node, htree_space_delta); /* returns the unlinked elem for caller post-processing (e.g. CLOG, free). */ From 9c18773957ccba29b0d8ca822c2527d04bca21c4 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 11:07:15 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20do=5Fhtree=5Felem=5Funlink?= =?UTF-8?q?=EC=9D=98=20hcnt=20=EA=B0=90=EC=86=8C=EB=A5=BC=20=EC=B2=B4?= =?UTF-8?q?=EC=9D=B8=20=ED=95=B4=EC=A0=9C=20=EC=9D=B4=ED=9B=84=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/hash_tree.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 88be6eb6..dc23d722 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -250,9 +250,8 @@ static void do_htree_elem_unlink(htree_node *root, /* remove link from the hash chain */ if (prev != NULL) prev->next = elem->next; else node->htab[hidx] = elem->next; - - node->hcnt[hidx] -= 1; elem->next = NULL; + node->hcnt[hidx] -= 1; /* decrement tot_elem_cnt on every node from root down to the target node */ htree_node *cur = root; From 5f88dcb50c9e8c8ffa4865c47521f9c960d40e67 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 11:32:51 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20htree=5Felem=5Ffind=20?= =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=85=B8=EB=93=9C=20=ED=83=90=EC=83=89=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=ED=86=B5=EC=9D=BC=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EB=B0=8F=20=EC=A1=B0=EA=B8=B0=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/hash_tree.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index dc23d722..65ea1273 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -382,19 +382,19 @@ htree_elem_item *htree_elem_find(htree_node *root, return NULL; uint32_t hval = (uint32_t)genhash_string_hash(key, nkey); - htree_node *node = root; - htree_elem_item *prev = NULL; - int hidx = -1; - while (node != NULL) { + htree_node *node = root; + int hidx; + while (true) { hidx = HTREE_GET_HASHIDX(hval, node->hdepth); if (node->hcnt[hidx] >= 0) break; node = (htree_node *)node->htab[hidx]; } - assert(node != NULL && hidx != -1); + if (node->hcnt[hidx] == 0) return NULL; htree_elem_item *find; + htree_elem_item *prev = NULL; for (find = (htree_elem_item *)node->htab[hidx]; find != NULL; find = find->next) { From fd185c8d23cbd3f9644ab10293c57c20066fe254 Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 12:01:11 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20htree=5Felem=5Flink/unlink=20?= =?UTF-8?q?=EA=B3=84=EC=97=B4=20=ED=95=A8=EC=88=98=EC=9D=98=20htree=5Fspac?= =?UTF-8?q?e=5Fdelta=20NULL=20=ED=97=88=EC=9A=A9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/hash_tree.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 65ea1273..8e598091 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -429,7 +429,8 @@ ENGINE_ERROR_CODE htree_elem_link(htree_node **root_pptr, ssize_t *htree_space_delta, const void *cookie) { - if (htree_space_delta) *htree_space_delta = 0; + assert(htree_space_delta != NULL); + *htree_space_delta = 0; elem->hval = htree_hash(ops, elem); /* allocate root node if the tree is empty */ @@ -475,7 +476,8 @@ htree_elem_item *htree_elem_unlink(htree_node **root_pptr, ssize_t *htree_space_delta) { if (*root_pptr == NULL) return NULL; - if (htree_space_delta) *htree_space_delta = 0; + assert(htree_space_delta != NULL); + *htree_space_delta = 0; uint32_t hval = (uint32_t)genhash_string_hash(key, nkey); @@ -527,7 +529,8 @@ htree_elem_item *htree_elem_unlink_at_offset(htree_node **root_pptr, ssize_t *htree_space_delta) { if (*root_pptr == NULL) return NULL; - if (htree_space_delta) *htree_space_delta = 0; + assert(htree_space_delta != NULL); + *htree_space_delta = 0; /* sentinel head: collect_to_chain links the result onto dummy.next */ htree_elem_item dummy = {0}; @@ -574,6 +577,8 @@ uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, ssize_t *htree_space_delta) { assert(elem_array != NULL); + assert(!unlink || htree_space_delta != NULL); + if (*root_pptr == NULL) return 0; if (htree_space_delta) *htree_space_delta = 0; From 10b92d4350ac87ca330f2d6ab5728ed5cd27995d Mon Sep 17 00:00:00 2001 From: zhy2on Date: Tue, 19 May 2026 12:10:27 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20htree=5Felem=5Fget=5Fat=5Foff?= =?UTF-8?q?set=20public=20api=EB=A5=BC=20=EB=82=B4=EB=B6=80=20api=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engines/default/hash_tree.c | 26 +++++++++++++------------- engines/default/hash_tree.h | 2 -- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/engines/default/hash_tree.c b/engines/default/hash_tree.c index 8e598091..7f6bc4be 100644 --- a/engines/default/hash_tree.c +++ b/engines/default/hash_tree.c @@ -373,6 +373,18 @@ static int do_htree_sampling(htree_node *node, return fcnt; } +static htree_elem_item *do_htree_elem_get_at_offset(htree_node *node, uint32_t offset) +{ + /* acts as a 1-elem array for collect_to_array */ + htree_elem_item *elem = NULL; + uint32_t skip = offset, take = 1; + htree_collect_ctx ctx = { .pos = &elem }; + + /* skip `offset`, read 1 */ + do_htree_range(&node, node, &skip, &take, false, collect_to_array, &ctx, NULL); + return elem; +} + htree_elem_item *htree_elem_find(htree_node *root, const void *key, uint16_t nkey, htree_ops *ops, @@ -512,18 +524,6 @@ htree_elem_item *htree_elem_unlink(htree_node **root_pptr, return elem; } -htree_elem_item *htree_elem_get_at_offset(htree_node *node, uint32_t offset) -{ - /* acts as a 1-elem array for collect_to_array */ - htree_elem_item *elem = NULL; - uint32_t skip = offset, take = 1; - htree_collect_ctx ctx = { .pos = &elem }; - - /* skip `offset`, read 1 */ - do_htree_range(&node, node, &skip, &take, false, collect_to_array, &ctx, NULL); - return elem; -} - htree_elem_item *htree_elem_unlink_at_offset(htree_node **root_pptr, uint32_t offset, ssize_t *htree_space_delta) @@ -621,7 +621,7 @@ int htree_elem_get_rand(htree_node *node, } if (!dup && b->cnt < 3) { b->keys[b->cnt++] = rand_offset; - htree_elem_item *found = htree_elem_get_at_offset(node, rand_offset); + htree_elem_item *found = do_htree_elem_get_at_offset(node, rand_offset); assert(found != NULL); elem_array[fcnt++] = found; } diff --git a/engines/default/hash_tree.h b/engines/default/hash_tree.h index ee023c5e..15435980 100644 --- a/engines/default/hash_tree.h +++ b/engines/default/hash_tree.h @@ -90,8 +90,6 @@ htree_elem_item *htree_elem_unlink_by_cnt(htree_node **root_pptr, uint32_t count, ssize_t *htree_space_delta); -htree_elem_item *htree_elem_get_at_offset(htree_node *node, uint32_t offset); - uint32_t htree_elem_get_by_cnt(htree_node **root_pptr, uint32_t count, htree_elem_item **elem_array,