hash: use single linked list for entries

Curl's double linked list is proven code, but it comes with some
additional memory overhead. Since hash's internal list of elements needs
only forward traversals, it seems worthwhile to use a single linked list
internally.

This saves 3 pointers per entry plus 3 pointers per slot.

Closes #16351
This commit is contained in:
Stefan Eissing 2025-02-16 15:19:20 +01:00 committed by Daniel Stenberg
parent 0064708f3f
commit 069f9d066e
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
2 changed files with 148 additions and 134 deletions

View File

@ -39,26 +39,44 @@
#define ITERINIT 0x5FEDCBA9 #define ITERINIT 0x5FEDCBA9
#endif #endif
static void
hash_element_dtor(void *user, void *element) #if 0 /* useful function for debugging hashes and their contents */
void Curl_hash_print(struct Curl_hash *h,
void (*func)(void *))
{ {
struct Curl_hash *h = (struct Curl_hash *) user; struct Curl_hash_iterator iter;
struct Curl_hash_element *e = (struct Curl_hash_element *) element; struct Curl_hash_element *he;
DEBUGASSERT(h); size_t last_index = UINT_MAX;
DEBUGASSERT(e);
if(e->ptr) { if(!h)
if(e->dtor) return;
e->dtor(e->key, e->key_len, e->ptr);
fprintf(stderr, "=Hash dump=\n");
Curl_hash_start_iterate(h, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
if(iter.slot_index != last_index) {
fprintf(stderr, "index %d:", (int)iter.slot_index);
if(last_index != UINT_MAX) {
fprintf(stderr, "\n");
}
last_index = iter.slot_index;
}
if(func)
func(he->ptr);
else else
h->dtor(e->ptr); fprintf(stderr, " [key=%.*s, he=%p, ptr=%p]",
e->ptr = NULL; (int)he->key_len, (char *)he->key,
(void *)he, (void *)he->ptr);
he = Curl_hash_next_element(&iter);
} }
fprintf(stderr, "\n");
e->key_len = 0;
free(e);
} }
#endif
/* Initializes a hash structure. /* Initializes a hash structure.
* Return 1 on error, 0 is fine. * Return 1 on error, 0 is fine.
@ -91,13 +109,15 @@ Curl_hash_init(struct Curl_hash *h,
} }
static struct Curl_hash_element * static struct Curl_hash_element *
mk_hash_element(const void *key, size_t key_len, const void *p, hash_elem_create(const void *key, size_t key_len, const void *p,
Curl_hash_elem_dtor dtor) Curl_hash_elem_dtor dtor)
{ {
struct Curl_hash_element *he;
/* allocate the struct plus memory after it to store the key */ /* allocate the struct plus memory after it to store the key */
struct Curl_hash_element *he = malloc(sizeof(struct Curl_hash_element) + he = malloc(sizeof(struct Curl_hash_element) + key_len);
key_len);
if(he) { if(he) {
he->next = NULL;
/* copy the key */ /* copy the key */
memcpy(he->key, key, key_len); memcpy(he->key, key, key_len);
he->key_len = key_len; he->key_len = key_len;
@ -107,46 +127,78 @@ mk_hash_element(const void *key, size_t key_len, const void *p,
return he; return he;
} }
#define FETCH_LIST(x,y,z) &x->table[x->hash_func(y, z, x->slots)] static void hash_elem_clear_ptr(struct Curl_hash *h,
struct Curl_hash_element *he)
{
DEBUGASSERT(h);
DEBUGASSERT(he);
if(he->ptr) {
if(he->dtor)
he->dtor(he->key, he->key_len, he->ptr);
else
h->dtor(he->ptr);
he->ptr = NULL;
}
}
static void hash_elem_destroy(struct Curl_hash *h,
struct Curl_hash_element *he)
{
hash_elem_clear_ptr(h, he);
free(he);
}
static void hash_elem_unlink(struct Curl_hash *h,
struct Curl_hash_element **he_anchor,
struct Curl_hash_element *he)
{
*he_anchor = he->next;
--h->size;
}
static void hash_elem_link(struct Curl_hash *h,
struct Curl_hash_element **he_anchor,
struct Curl_hash_element *he)
{
he->next = *he_anchor;
*he_anchor = he;
++h->size;
}
#define CURL_HASH_SLOT(x,y,z) x->table[x->hash_func(y, z, x->slots)]
#define CURL_HASH_SLOT_ADDR(x,y,z) &CURL_HASH_SLOT(x,y,z)
void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p,
Curl_hash_elem_dtor dtor) Curl_hash_elem_dtor dtor)
{ {
struct Curl_hash_element *he; struct Curl_hash_element *he, **slot;
struct Curl_llist_node *le;
struct Curl_llist *l;
DEBUGASSERT(h); DEBUGASSERT(h);
DEBUGASSERT(h->slots); DEBUGASSERT(h->slots);
DEBUGASSERT(h->init == HASHINIT); DEBUGASSERT(h->init == HASHINIT);
if(!h->table) { if(!h->table) {
size_t i; h->table = calloc(h->slots, sizeof(struct Curl_hash_element *));
h->table = malloc(h->slots * sizeof(struct Curl_llist));
if(!h->table) if(!h->table)
return NULL; /* OOM */ return NULL; /* OOM */
for(i = 0; i < h->slots; ++i)
Curl_llist_init(&h->table[i], hash_element_dtor);
} }
l = FETCH_LIST(h, key, key_len); slot = CURL_HASH_SLOT_ADDR(h, key, key_len);
for(he = *slot; he; he = he->next) {
for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) {
he = (struct Curl_hash_element *) Curl_node_elem(le);
if(h->comp_func(he->key, he->key_len, key, key_len)) { if(h->comp_func(he->key, he->key_len, key, key_len)) {
Curl_node_uremove(le, (void *)h); /* existing key entry, overwrite by clearing old pointer */
--h->size; hash_elem_clear_ptr(h, he);
break; he->ptr = (void *)p;
he->dtor = dtor;
return p;
} }
} }
he = mk_hash_element(key, key_len, p, dtor); he = hash_elem_create(key, key_len, p, dtor);
if(he) { if(!he)
Curl_llist_append(l, he, &he->list); return NULL; /* OOM */
++h->size;
return p; /* return the new entry */
}
return NULL; /* failure */ hash_elem_link(h, slot, he);
return p; /* return the new entry */
} }
/* Insert the data in the hash. If there already was a match in the hash, that /* Insert the data in the hash. If there already was a match in the hash, that
@ -174,16 +226,17 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len)
DEBUGASSERT(h->slots); DEBUGASSERT(h->slots);
DEBUGASSERT(h->init == HASHINIT); DEBUGASSERT(h->init == HASHINIT);
if(h->table) { if(h->table) {
struct Curl_llist_node *le; struct Curl_hash_element *he, **he_anchor;
struct Curl_llist *l = FETCH_LIST(h, key, key_len);
for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { he_anchor = CURL_HASH_SLOT_ADDR(h, key, key_len);
struct Curl_hash_element *he = Curl_node_elem(le); while(*he_anchor) {
he = *he_anchor;
if(h->comp_func(he->key, he->key_len, key, key_len)) { if(h->comp_func(he->key, he->key_len, key, key_len)) {
Curl_node_uremove(le, (void *) h); hash_elem_unlink(h, he_anchor, he);
--h->size; hash_elem_destroy(h, he);
return 0; return 0;
} }
he_anchor = &he->next;
} }
} }
return 1; return 1;
@ -199,18 +252,16 @@ Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len)
DEBUGASSERT(h); DEBUGASSERT(h);
DEBUGASSERT(h->init == HASHINIT); DEBUGASSERT(h->init == HASHINIT);
if(h->table) { if(h->table) {
struct Curl_llist_node *le; struct Curl_hash_element *he;
struct Curl_llist *l;
DEBUGASSERT(h->slots); DEBUGASSERT(h->slots);
l = FETCH_LIST(h, key, key_len); he = CURL_HASH_SLOT(h, key, key_len);
for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { while(he) {
struct Curl_hash_element *he = Curl_node_elem(le);
if(h->comp_func(he->key, he->key_len, key, key_len)) { if(h->comp_func(he->key, he->key_len, key, key_len)) {
return he->ptr; return he->ptr;
} }
he = he->next;
} }
} }
return NULL; return NULL;
} }
@ -226,13 +277,10 @@ Curl_hash_destroy(struct Curl_hash *h)
{ {
DEBUGASSERT(h->init == HASHINIT); DEBUGASSERT(h->init == HASHINIT);
if(h->table) { if(h->table) {
size_t i; Curl_hash_clean(h);
for(i = 0; i < h->slots; ++i) {
Curl_llist_destroy(&h->table[i], (void *) h);
}
Curl_safefree(h->table); Curl_safefree(h->table);
} }
h->size = 0; DEBUGASSERT(h->size == 0);
h->slots = 0; h->slots = 0;
} }
@ -240,10 +288,21 @@ Curl_hash_destroy(struct Curl_hash *h)
* *
* @unittest: 1602 * @unittest: 1602
*/ */
void void Curl_hash_clean(struct Curl_hash *h)
Curl_hash_clean(struct Curl_hash *h)
{ {
Curl_hash_clean_with_criterium(h, NULL, NULL); if(h && h->table) {
struct Curl_hash_element *he, **he_anchor;
size_t i;
DEBUGASSERT(h->init == HASHINIT);
for(i = 0; i < h->slots; ++i) {
he_anchor = &h->table[i];
while(*he_anchor) {
he = *he_anchor;
hash_elem_unlink(h, he_anchor, he);
hash_elem_destroy(h, he);
}
}
}
} }
size_t Curl_hash_count(struct Curl_hash *h) size_t Curl_hash_count(struct Curl_hash *h)
@ -264,18 +323,16 @@ Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user,
DEBUGASSERT(h->init == HASHINIT); DEBUGASSERT(h->init == HASHINIT);
for(i = 0; i < h->slots; ++i) { for(i = 0; i < h->slots; ++i) {
struct Curl_llist *list = &h->table[i]; struct Curl_hash_element *he, **he_anchor = &h->table[i];
struct Curl_llist_node *le = while(*he_anchor) {
Curl_llist_head(list); /* get first list entry */
while(le) {
struct Curl_hash_element *he = Curl_node_elem(le);
struct Curl_llist_node *lnext = Curl_node_next(le);
/* ask the callback function if we shall remove this entry or not */ /* ask the callback function if we shall remove this entry or not */
if(!comp || comp(user, he->ptr)) { if(!comp || comp(user, (*he_anchor)->ptr)) {
Curl_node_uremove(le, (void *) h); he = *he_anchor;
--h->size; /* one less entry in the hash now */ hash_elem_unlink(h, he_anchor, he);
hash_elem_destroy(h, he);
} }
le = lnext; else
he_anchor = &(*he_anchor)->next;
} }
} }
} }
@ -310,7 +367,7 @@ void Curl_hash_start_iterate(struct Curl_hash *hash,
DEBUGASSERT(hash->init == HASHINIT); DEBUGASSERT(hash->init == HASHINIT);
iter->hash = hash; iter->hash = hash;
iter->slot_index = 0; iter->slot_index = 0;
iter->current_element = NULL; iter->current = NULL;
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
iter->init = ITERINIT; iter->init = ITERINIT;
#endif #endif
@ -326,64 +383,24 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter)
return NULL; /* empty hash, nothing to return */ return NULL; /* empty hash, nothing to return */
/* Get the next element in the current list, if any */ /* Get the next element in the current list, if any */
if(iter->current_element) if(iter->current)
iter->current_element = Curl_node_next(iter->current_element); iter->current = iter->current->next;
/* If we have reached the end of the list, find the next one */ /* If we have reached the end of the list, find the next one */
if(!iter->current_element) { if(!iter->current) {
size_t i; size_t i;
for(i = iter->slot_index; i < h->slots; i++) { for(i = iter->slot_index; i < h->slots; i++) {
if(Curl_llist_head(&h->table[i])) { if(h->table[i]) {
iter->current_element = Curl_llist_head(&h->table[i]); iter->current = h->table[i];
iter->slot_index = i + 1; iter->slot_index = i + 1;
break; break;
} }
} }
} }
if(iter->current_element) { return iter->current;
struct Curl_hash_element *he = Curl_node_elem(iter->current_element);
return he;
}
return NULL;
} }
#if 0 /* useful function for debugging hashes and their contents */
void Curl_hash_print(struct Curl_hash *h,
void (*func)(void *))
{
struct Curl_hash_iterator iter;
struct Curl_hash_element *he;
size_t last_index = ~0;
if(!h)
return;
fprintf(stderr, "=Hash dump=\n");
Curl_hash_start_iterate(h, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
if(iter.slot_index != last_index) {
fprintf(stderr, "index %d:", iter.slot_index);
if(last_index != ~0) {
fprintf(stderr, "\n");
}
last_index = iter.slot_index;
}
if(func)
func(he->ptr);
else
fprintf(stderr, " [%p]", (void *)he->ptr);
he = Curl_hash_next_element(&iter);
}
fprintf(stderr, "\n");
}
#endif
void Curl_hash_offt_init(struct Curl_hash *h, void Curl_hash_offt_init(struct Curl_hash *h,
size_t slots, size_t slots,
Curl_hash_dtor dtor) Curl_hash_dtor dtor)

View File

@ -45,14 +45,24 @@ typedef size_t (*comp_function) (void *key1,
typedef void (*Curl_hash_dtor)(void *); typedef void (*Curl_hash_dtor)(void *);
typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p);
struct Curl_hash_element {
struct Curl_hash_element *next;
void *ptr;
Curl_hash_elem_dtor dtor;
size_t key_len;
char key[1]; /* allocated memory following the struct */
};
struct Curl_hash { struct Curl_hash {
struct Curl_llist *table; struct Curl_hash_element **table;
/* Hash function to be used for this hash table */ /* Hash function to be used for this hash table */
hash_function hash_func; hash_function hash_func;
/* Comparator function to compare keys */ /* Comparator function to compare keys */
comp_function comp_func; comp_function comp_func;
/* General element construct, unless element itself carries one */
Curl_hash_dtor dtor; Curl_hash_dtor dtor;
size_t slots; size_t slots;
size_t size; size_t size;
@ -61,23 +71,10 @@ struct Curl_hash {
#endif #endif
}; };
typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p);
struct Curl_hash_element {
struct Curl_llist_node list;
void *ptr;
Curl_hash_elem_dtor dtor;
size_t key_len;
#ifdef DEBUGBUILD
int init;
#endif
char key[1]; /* allocated memory following the struct */
};
struct Curl_hash_iterator { struct Curl_hash_iterator {
struct Curl_hash *hash; struct Curl_hash *hash;
size_t slot_index; size_t slot_index;
struct Curl_llist_node *current_element; struct Curl_hash_element *current;
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
int init; int init;
#endif #endif