--- embedaddon/strongswan/src/libstrongswan/collections/hashtable.c 2020/06/03 09:46:43 1.1.1.1 +++ embedaddon/strongswan/src/libstrongswan/collections/hashtable.c 2021/03/17 00:20:08 1.1.1.2 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2014 Tobias Brunner + * Copyright (C) 2008-2020 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -13,20 +13,43 @@ * for more details. */ - #include "hashtable.h" +#include "hashtable_profiler.h" #include +#include -/** The maximum capacity of the hash table (MUST be a power of 2) */ -#define MAX_CAPACITY (1 << 30) +/** The minimum size of the hash table (MUST be a power of 2) */ +#define MIN_SIZE 8 +/** The maximum size of the hash table (MUST be a power of 2) */ +#define MAX_SIZE (1 << 30) +/** Determine the capacity/maximum load of the table (higher values cause + * more collisions, lower values increase the memory overhead) */ +#define CAPACITY(size) (size / 3 * 2) +/** Factor for the new table size based on the number of items when resizing, + * with the above load factor this results in doubling the size when growing */ +#define RESIZE_FACTOR 3 + +/** + * A note about these parameters: + * + * The maximum number of items that can be stored in this implementation + * is MAX_COUNT = CAPACITY(MAX_SIZE). + * Since we use u_int throughout, MAX_COUNT * RESIZE_FACTOR must not overflow + * this type. + */ +#if (UINT_MAX / RESIZE_FACTOR < CAPACITY(MAX_SIZE)) + #error Hahstable parameters invalid! +#endif + typedef struct pair_t pair_t; /** * This pair holds a pointer to the key and value it represents. */ struct pair_t { + /** * Key of a hash table item. */ @@ -41,29 +64,8 @@ struct pair_t { * Cached hash (used in case of a resize). */ u_int hash; - - /** - * Next pair in an overflow list. - */ - pair_t *next; }; -/** - * Creates an empty pair object. - */ -static inline pair_t *pair_create(const void *key, void *value, u_int hash) -{ - pair_t *this; - - INIT(this, - .key = key, - .value = value, - .hash = hash, - ); - - return this; -} - typedef struct private_hashtable_t private_hashtable_t; /** @@ -71,6 +73,7 @@ typedef struct private_hashtable_t private_hashtable_t * */ struct private_hashtable_t { + /** * Public part of hash table. */ @@ -82,26 +85,39 @@ struct private_hashtable_t { u_int count; /** - * The current capacity of the hash table (always a power of 2). + * The current size of the hash table (always a power of 2). */ - u_int capacity; + u_int size; /** - * The current mask to calculate the row index (capacity - 1). + * The current mask to calculate the row index (size - 1). */ u_int mask; /** - * The load factor. + * All items in the order they were inserted (removed items are marked by + * setting the key to NULL until resized). */ - float load_factor; + pair_t *items; /** - * The actual table. + * Number of available slots in the array above and the table in general, + * is set to CAPACITY(size) when the hash table is initialized. */ - pair_t **table; + u_int capacity; /** + * Number of used slots in the array above. + */ + u_int items_count; + + /** + * Hash table with indices into the array above. The type depends on the + * current capacity. + */ + void *table; + + /** * The hashing function. */ hashtable_hash_t hash; @@ -110,48 +126,38 @@ struct private_hashtable_t { * The equality function. */ hashtable_equals_t equals; + + /** + * Profiling data + */ + hashtable_profile_t profile; }; typedef struct private_enumerator_t private_enumerator_t; /** - * hash table enumerator implementation + * Hash table enumerator implementation */ struct private_enumerator_t { /** - * implements enumerator interface + * Implements enumerator interface */ enumerator_t enumerator; /** - * associated hash table + * Associated hash table */ private_hashtable_t *table; /** - * current row index + * Current index */ - u_int row; - - /** - * number of remaining items in hashtable - */ - u_int count; - - /** - * current pair - */ - pair_t *current; - - /** - * previous pair (used by remove_at) - */ - pair_t *prev; + u_int index; }; /* - * See header. + * Described in header */ u_int hashtable_hash_ptr(const void *key) { @@ -159,7 +165,7 @@ u_int hashtable_hash_ptr(const void *key) } /* - * See header. + * Described in header */ u_int hashtable_hash_str(const void *key) { @@ -167,7 +173,7 @@ u_int hashtable_hash_str(const void *key) } /* - * See header. + * Described in header */ bool hashtable_equals_ptr(const void *key, const void *other_key) { @@ -175,7 +181,7 @@ bool hashtable_equals_ptr(const void *key, const void } /* - * See header. + * Described in header */ bool hashtable_equals_str(const void *key, const void *other_key) { @@ -183,12 +189,50 @@ bool hashtable_equals_str(const void *key, const void } /** + * Returns the index stored in the given bucket. If the bucket is empty, + * 0 is returned. + */ +static inline u_int get_index(private_hashtable_t *this, u_int row) +{ + if (this->capacity <= 0xff) + { + return ((uint8_t*)this->table)[row]; + } + else if (this->capacity <= 0xffff) + { + return ((uint16_t*)this->table)[row]; + } + return ((u_int*)this->table)[row]; +} + +/** + * Set the index stored in the given bucket. Set to 0 to clear a bucket. + */ +static inline void set_index(private_hashtable_t *this, u_int row, u_int index) +{ + if (this->capacity <= 0xff) + { + ((uint8_t*)this->table)[row] = index; + } + else if (this->capacity <= 0xffff) + { + ((uint16_t*)this->table)[row] = index; + } + else + { + ((u_int*)this->table)[row] = index; + } +} + +/** * This function returns the next-highest power of two for the given number. * The algorithm works by setting all bits on the right-hand side of the most * significant 1 to 1 and then increments the whole number so it rolls over * to the nearest power of two. Note: returns 0 for n == 0 + * + * Also used by hashlist_t. */ -static u_int get_nearest_powerof2(u_int n) +u_int hashtable_get_nearest_powerof2(u_int n) { u_int i; @@ -201,174 +245,226 @@ static u_int get_nearest_powerof2(u_int n) } /** - * Init hash table parameters + * Init hash table to the given size */ -static void init_hashtable(private_hashtable_t *this, u_int capacity) +static void init_hashtable(private_hashtable_t *this, u_int size) { - capacity = max(1, min(capacity, MAX_CAPACITY)); - this->capacity = get_nearest_powerof2(capacity); - this->mask = this->capacity - 1; - this->load_factor = 0.75; + u_int index_size = sizeof(u_int); - this->table = calloc(this->capacity, sizeof(pair_t*)); + this->size = max(MIN_SIZE, min(size, MAX_SIZE)); + this->size = hashtable_get_nearest_powerof2(this->size); + this->mask = this->size - 1; + profile_size(&this->profile, this->size); + + this->capacity = CAPACITY(this->size); + this->items = calloc(this->capacity, sizeof(pair_t)); + this->items_count = 0; + + if (this->capacity <= 0xff) + { + index_size = sizeof(uint8_t); + } + else if (this->capacity <= 0xffff) + { + index_size = sizeof(uint16_t); + } + this->table = calloc(this->size, index_size); } /** - * Double the size of the hash table and rehash all the elements. + * Calculate the next bucket using quadratic probing (the sequence is h(k) + 1, + * h(k) + 3, h(k) + 6, h(k) + 10, ...). */ -static void rehash(private_hashtable_t *this) +static inline u_int get_next(private_hashtable_t *this, u_int row, u_int *p) { - pair_t **old_table; - u_int row, old_capacity; + *p += 1; + return (row + *p) & this->mask; +} - if (this->capacity >= MAX_CAPACITY) +/** + * Find the pair with the given key, optionally returns the hash and first empty + * or previously used row if the key is not found. + */ +static inline pair_t *find_key(private_hashtable_t *this, const void *key, + u_int *out_hash, u_int *out_row) +{ + pair_t *pair; + u_int hash, row, p = 0, removed, index; + bool found_removed = FALSE; + + if (!this->count && !out_hash && !out_row) { - return; + return NULL; } - old_capacity = this->capacity; - old_table = this->table; + lookup_start(); - init_hashtable(this, old_capacity << 1); - - for (row = 0; row < old_capacity; row++) + hash = this->hash(key); + row = hash & this->mask; + index = get_index(this, row); + while (index) { - pair_t *pair, *next; - u_int new_row; + lookup_probing(); + pair = &this->items[index-1]; - pair = old_table[row]; - while (pair) - { /* insert pair at the front of new bucket*/ - next = pair->next; - new_row = pair->hash & this->mask; - pair->next = this->table[new_row]; - this->table[new_row] = pair; - pair = next; + if (!pair->key) + { + if (!found_removed && out_row) + { + removed = row; + found_removed = TRUE; + } } + else if (pair->hash == hash && this->equals(key, pair->key)) + { + lookup_success(&this->profile); + return pair; + } + row = get_next(this, row, &p); + index = get_index(this, row); } - free(old_table); + if (out_hash) + { + *out_hash = hash; + } + if (out_row) + { + *out_row = found_removed ? removed : row; + } + lookup_failure(&this->profile); + return NULL; } -METHOD(hashtable_t, put, void*, - private_hashtable_t *this, const void *key, void *value) +/** + * Helper to insert a new item into the table and items array, + * returns its new index into the latter. + */ +static inline u_int insert_item(private_hashtable_t *this, u_int row) { - void *old_value = NULL; - pair_t *pair; - u_int hash, row; + u_int index = this->items_count++; - hash = this->hash(key); - row = hash & this->mask; - pair = this->table[row]; - while (pair) - { /* search existing bucket for key */ - if (this->equals(key, pair->key)) + /* we use 0 to mark unused buckets, so increase the index */ + set_index(this, row, index + 1); + return index; +} + +/** + * Resize the hash table to the given size and rehash all the elements, + * size may be smaller or even the same (e.g. if it's necessary to clear + * previously used buckets). + */ +static bool rehash(private_hashtable_t *this, u_int size) +{ + pair_t *old_items, *pair; + u_int old_count, i, p, row, index; + + if (size > MAX_SIZE) + { + return FALSE; + } + + old_items = this->items; + old_count = this->items_count; + free(this->table); + init_hashtable(this, size); + + /* no need to do anything if the table is empty and we are just cleaning + * up previously used items */ + if (this->count) + { + for (i = 0; i < old_count; i++) { - old_value = pair->value; - pair->value = value; - pair->key = key; - break; + pair = &old_items[i]; + + if (pair->key) + { + row = pair->hash & this->mask; + index = get_index(this, row); + for (p = 0; index;) + { + row = get_next(this, row, &p); + index = get_index(this, row); + } + index = insert_item(this, row); + this->items[index] = *pair; + } } - pair = pair->next; } - if (!pair) - { /* insert at the front of bucket */ - pair = pair_create(key, value, hash); - pair->next = this->table[row]; - this->table[row] = pair; - this->count++; - } - if (this->count >= this->capacity * this->load_factor) - { - rehash(this); - } - return old_value; + free(old_items); + return TRUE; } -static void *get_internal(private_hashtable_t *this, const void *key, - hashtable_equals_t equals) +METHOD(hashtable_t, put, void*, + private_hashtable_t *this, const void *key, void *value) { - void *value = NULL; + void *old_value = NULL; pair_t *pair; + u_int index, hash = 0, row = 0; - if (!this->count) - { /* no need to calculate the hash */ + if (this->items_count >= this->capacity && + !rehash(this, this->count * RESIZE_FACTOR)) + { + DBG1(DBG_LIB, "!!! FAILED TO RESIZE HASHTABLE TO %u !!!", + this->count * RESIZE_FACTOR); return NULL; } - - pair = this->table[this->hash(key) & this->mask]; - while (pair) + pair = find_key(this, key, &hash, &row); + if (pair) { - if (equals(key, pair->key)) - { - value = pair->value; - break; - } - pair = pair->next; + old_value = pair->value; + pair->value = value; + pair->key = key; + return old_value; } - return value; + index = insert_item(this, row); + this->items[index] = (pair_t){ + .hash = hash, + .key = key, + .value = value, + }; + this->count++; + profile_count(&this->profile, this->count); + return NULL; } METHOD(hashtable_t, get, void*, private_hashtable_t *this, const void *key) { - return get_internal(this, key, this->equals); + pair_t *pair = find_key(this, key, NULL, NULL); + return pair ? pair->value : NULL; } -METHOD(hashtable_t, get_match, void*, - private_hashtable_t *this, const void *key, hashtable_equals_t match) +/** + * Remove the given item from the table, returns the currently stored value. + */ +static void *remove_internal(private_hashtable_t *this, pair_t *pair) { - return get_internal(this, key, match); + void *value = NULL; + + if (pair) + { /* this does not decrease the item count as we keep the previously + * used items until the table is rehashed/resized */ + value = pair->value; + pair->key = NULL; + this->count--; + } + return value; } METHOD(hashtable_t, remove_, void*, private_hashtable_t *this, const void *key) { - void *value = NULL; - pair_t *pair, *prev = NULL; - u_int row; - - row = this->hash(key) & this->mask; - pair = this->table[row]; - while (pair) - { - if (this->equals(key, pair->key)) - { - if (prev) - { - prev->next = pair->next; - } - else - { - this->table[row] = pair->next; - } - value = pair->value; - this->count--; - free(pair); - break; - } - prev = pair; - pair = pair->next; - } - return value; + pair_t *pair = find_key(this, key, NULL, NULL); + return remove_internal(this, pair); } METHOD(hashtable_t, remove_at, void, private_hashtable_t *this, private_enumerator_t *enumerator) { - if (enumerator->table == this && enumerator->current) - { - pair_t *current = enumerator->current; - if (enumerator->prev) - { - enumerator->prev->next = current->next; - } - else - { - this->table[enumerator->row] = current->next; - } - enumerator->current = enumerator->prev; - free(current); - this->count--; + if (enumerator->table == this && enumerator->index) + { /* the index is already advanced by one */ + u_int index = enumerator->index - 1; + remove_internal(this, &this->items[index]); } } @@ -383,34 +479,25 @@ METHOD(enumerator_t, enumerate, bool, { const void **key; void **value; + pair_t *pair; VA_ARGS_VGET(args, key, value); - while (this->count && this->row < this->table->capacity) + while (this->index < this->table->items_count) { - this->prev = this->current; - if (this->current) + pair = &this->table->items[this->index++]; + if (pair->key) { - this->current = this->current->next; - } - else - { - this->current = this->table->table[this->row]; - } - if (this->current) - { if (key) { - *key = this->current->key; + *key = pair->key; } if (value) { - *value = this->current->value; + *value = pair->value; } - this->count--; return TRUE; } - this->row++; } return FALSE; } @@ -427,32 +514,30 @@ METHOD(hashtable_t, create_enumerator, enumerator_t*, .destroy = (void*)free, }, .table = this, - .count = this->count, ); - return &enumerator->enumerator; } static void destroy_internal(private_hashtable_t *this, void (*fn)(void*,const void*)) { - pair_t *pair, *next; - u_int row; + pair_t *pair; + u_int i; - for (row = 0; row < this->capacity; row++) + profiler_cleanup(&this->profile, this->count, this->size); + + if (fn) { - pair = this->table[row]; - while (pair) + for (i = 0; i < this->items_count; i++) { - if (fn) + pair = &this->items[i]; + if (pair->key) { fn(pair->value, pair->key); } - next = pair->next; - free(pair); - pair = next; } } + free(this->items); free(this->table); free(this); } @@ -473,7 +558,7 @@ METHOD(hashtable_t, destroy_function, void, * Described in header. */ hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals, - u_int capacity) + u_int size) { private_hashtable_t *this; @@ -481,7 +566,6 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, h .public = { .put = _put, .get = _get, - .get_match = _get_match, .remove = _remove_, .remove_at = (void*)_remove_at, .get_count = _get_count, @@ -493,7 +577,9 @@ hashtable_t *hashtable_create(hashtable_hash_t hash, h .equals = equals, ); - init_hashtable(this, capacity); + init_hashtable(this, size); + + profiler_init(&this->profile, 2); return &this->public; }