Add tommy_hashdyn_reserve to pre-allocate hash buckets for expected elements

This commit is contained in:
Andrea Mazzoleni 2026-10-03 21:11:09 +02:00
parent 0d2f466b11
commit 93c39d5526
2 changed files with 58 additions and 24 deletions

View file

@ -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;
}

View file

@ -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