From 93c39d5526cbaed0c6495db5432087e22a2521b2 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 3 Oct 2026 21:11:09 +0200 Subject: [PATCH] Add tommy_hashdyn_reserve to pre-allocate hash buckets for expected elements --- tommyds/tommyhashdyn.c | 72 ++++++++++++++++++++++++++++-------------- tommyds/tommyhashdyn.h | 10 ++++++ 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/tommyds/tommyhashdyn.c b/tommyds/tommyhashdyn.c index ce2587fb..54d213f2 100644 --- a/tommyds/tommyhashdyn.c +++ b/tommyds/tommyhashdyn.c @@ -28,36 +28,47 @@ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn) */ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket_bit) { - tommy_size_t bucket_bit; - tommy_size_t bucket_max; - tommy_size_t new_bucket_max; - tommy_size_t new_bucket_mask; + tommy_size_t bucket_bit = hashdyn->bucket_bit; + tommy_size_t bucket_max = hashdyn->bucket_max; + tommy_size_t new_bucket_max = (tommy_size_t)1 << new_bucket_bit; + tommy_size_t new_bucket_mask = new_bucket_max - 1; tommy_hashdyn_node** new_bucket; - bucket_bit = hashdyn->bucket_bit; - bucket_max = hashdyn->bucket_max; - - new_bucket_max = (tommy_size_t)1 << new_bucket_bit; - new_bucket_mask = new_bucket_max - 1; - - /* allocate the new vector using malloc() and not calloc() */ - /* because data is fully initialized in the update process */ - new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); - - /* reinsert all the elements */ - if (new_bucket_bit > bucket_bit) { + if (!hashdyn->count) { + /* empty table: allocate zeroed memory with calloc without looping */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*))); + } else if (new_bucket_bit == bucket_bit + 1) { tommy_size_t i; - /* grow */ + /* grow by 1 bit: zero the two buckets inline in loop to preserve cache locality */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); + for (i = 0; i < bucket_max; ++i) { tommy_hashdyn_node* j; - /* setup the new two buckets */ new_bucket[i] = 0; new_bucket[i + bucket_max] = 0; - /* reinsert the bucket */ j = hashdyn->bucket[i]; + while (j) { + tommy_hashdyn_node* j_next = j->next; + tommy_size_t pos = j->index & new_bucket_mask; + if (new_bucket[pos]) + tommy_list_insert_tail_not_empty(new_bucket[pos], j); + else + tommy_list_insert_first(&new_bucket[pos], j); + j = j_next; + } + } + } else if (new_bucket_bit > bucket_bit) { + tommy_size_t i; + + /* grow by multiple bits with elements: allocate zeroed memory and reinsert */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_calloc(new_bucket_max, sizeof(tommy_hashdyn_node*))); + + for (i = 0; i < bucket_max; ++i) { + tommy_hashdyn_node* j = hashdyn->bucket[i]; + while (j) { tommy_hashdyn_node* j_next = j->next; tommy_size_t pos = j->index & new_bucket_mask; @@ -71,12 +82,11 @@ static void tommy_hashdyn_resize(tommy_hashdyn* hashdyn, tommy_uint_t new_bucket } else { tommy_size_t i; - /* shrink */ - for (i = 0; i < new_bucket_max; ++i) { - /* setup the new bucket with the lower bucket*/ - new_bucket[i] = hashdyn->bucket[i]; + /* shrink with elements: all buckets are overwritten, no pre-zeroing needed */ + new_bucket = tommy_cast(tommy_hashdyn_node**, tommy_malloc(new_bucket_max * sizeof(tommy_hashdyn_node*))); - /* concat the upper bucket */ + for (i = 0; i < new_bucket_max; ++i) { + new_bucket[i] = hashdyn->bucket[i]; tommy_list_concat(&new_bucket[i], &hashdyn->bucket[i + new_bucket_max]); } } @@ -110,6 +120,19 @@ tommy_inline void hashdyn_shrink_step(tommy_hashdyn* hashdyn) tommy_hashdyn_resize(hashdyn, hashdyn->bucket_bit - 1); } +TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count) +{ + tommy_uint_t bucket_bit = tommy_ilog2(count | 1) + 2; + + if (bucket_bit < TOMMY_HASHDYN_BIT) + bucket_bit = TOMMY_HASHDYN_BIT; + + if (bucket_bit <= hashdyn->bucket_bit) + return; + + tommy_hashdyn_resize(hashdyn, bucket_bit); +} + TOMMY_API void tommy_hashdyn_insert(tommy_hashdyn* hashdyn, tommy_hashdyn_node* node, void* data, tommy_hash_t hash) { tommy_size_t pos = hash & hashdyn->bucket_mask; @@ -256,3 +279,4 @@ TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list) memset(hashdyn->bucket, 0, hashdyn->bucket_max * sizeof(tommy_hashdyn_node*)); hashdyn->count = 0; } + diff --git a/tommyds/tommyhashdyn.h b/tommyds/tommyhashdyn.h index 5ed7ed7a..9bce46be 100644 --- a/tommyds/tommyhashdyn.h +++ b/tommyds/tommyhashdyn.h @@ -155,6 +155,15 @@ TOMMY_API void tommy_hashdyn_init(tommy_hashdyn* hashdyn); */ TOMMY_API void tommy_hashdyn_done(tommy_hashdyn* hashdyn); +/** + * Pre-allocates buckets for the specified number of elements. + * + * It ensures that the hashtable has enough buckets allocated to contain + * the specified number of elements without triggering a dynamic resize. + * \param count Number of elements to reserve space for. + */ +TOMMY_API void tommy_hashdyn_reserve(tommy_hashdyn* hashdyn, tommy_size_t count); + /** * Inserts an element in the hashtable. */ @@ -308,3 +317,4 @@ TOMMY_API tommy_size_t tommy_hashdyn_memory_usage(tommy_hashdyn* hashdyn); TOMMY_API void tommy_hashdyn_to_list(tommy_hashdyn* hashdyn, tommy_list* list); #endif +