586 lines
12 KiB
C
586 lines
12 KiB
C
/*
|
|
* Copyright (C) 2008-2020 Tobias Brunner
|
|
* HSR Hochschule fuer Technik Rapperswil
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include "hashtable.h"
|
|
#include "hashtable_profiler.h"
|
|
|
|
#include <utils/chunk.h>
|
|
#include <utils/debug.h>
|
|
|
|
/** 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.
|
|
*/
|
|
const void *key;
|
|
|
|
/**
|
|
* Value of a hash table item.
|
|
*/
|
|
void *value;
|
|
|
|
/**
|
|
* Cached hash (used in case of a resize).
|
|
*/
|
|
u_int hash;
|
|
};
|
|
|
|
typedef struct private_hashtable_t private_hashtable_t;
|
|
|
|
/**
|
|
* Private data of a hashtable_t object.
|
|
*
|
|
*/
|
|
struct private_hashtable_t {
|
|
|
|
/**
|
|
* Public part of hash table.
|
|
*/
|
|
hashtable_t public;
|
|
|
|
/**
|
|
* The number of items in the hash table.
|
|
*/
|
|
u_int count;
|
|
|
|
/**
|
|
* The current size of the hash table (always a power of 2).
|
|
*/
|
|
u_int size;
|
|
|
|
/**
|
|
* The current mask to calculate the row index (size - 1).
|
|
*/
|
|
u_int mask;
|
|
|
|
/**
|
|
* All items in the order they were inserted (removed items are marked by
|
|
* setting the key to NULL until resized).
|
|
*/
|
|
pair_t *items;
|
|
|
|
/**
|
|
* Number of available slots in the array above and the table in general,
|
|
* is set to CAPACITY(size) when the hash table is initialized.
|
|
*/
|
|
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;
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
struct private_enumerator_t {
|
|
|
|
/**
|
|
* Implements enumerator interface
|
|
*/
|
|
enumerator_t enumerator;
|
|
|
|
/**
|
|
* Associated hash table
|
|
*/
|
|
private_hashtable_t *table;
|
|
|
|
/**
|
|
* Current index
|
|
*/
|
|
u_int index;
|
|
};
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
u_int hashtable_hash_ptr(const void *key)
|
|
{
|
|
return chunk_hash(chunk_from_thing(key));
|
|
}
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
u_int hashtable_hash_str(const void *key)
|
|
{
|
|
return chunk_hash(chunk_from_str((char*)key));
|
|
}
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
bool hashtable_equals_ptr(const void *key, const void *other_key)
|
|
{
|
|
return key == other_key;
|
|
}
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
bool hashtable_equals_str(const void *key, const void *other_key)
|
|
{
|
|
return streq(key, other_key);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
u_int hashtable_get_nearest_powerof2(u_int n)
|
|
{
|
|
u_int i;
|
|
|
|
--n;
|
|
for (i = 1; i < sizeof(u_int) * 8; i <<= 1)
|
|
{
|
|
n |= n >> i;
|
|
}
|
|
return ++n;
|
|
}
|
|
|
|
/**
|
|
* Init hash table to the given size
|
|
*/
|
|
static void init_hashtable(private_hashtable_t *this, u_int size)
|
|
{
|
|
u_int index_size = sizeof(u_int);
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Calculate the next bucket using quadratic probing (the sequence is h(k) + 1,
|
|
* h(k) + 3, h(k) + 6, h(k) + 10, ...).
|
|
*/
|
|
static inline u_int get_next(private_hashtable_t *this, u_int row, u_int *p)
|
|
{
|
|
*p += 1;
|
|
return (row + *p) & this->mask;
|
|
}
|
|
|
|
/**
|
|
* 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 NULL;
|
|
}
|
|
|
|
lookup_start();
|
|
|
|
hash = this->hash(key);
|
|
row = hash & this->mask;
|
|
index = get_index(this, row);
|
|
while (index)
|
|
{
|
|
lookup_probing();
|
|
pair = &this->items[index-1];
|
|
|
|
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);
|
|
}
|
|
if (out_hash)
|
|
{
|
|
*out_hash = hash;
|
|
}
|
|
if (out_row)
|
|
{
|
|
*out_row = found_removed ? removed : row;
|
|
}
|
|
lookup_failure(&this->profile);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
u_int index = this->items_count++;
|
|
|
|
/* 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++)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
free(old_items);
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(hashtable_t, put, void*,
|
|
private_hashtable_t *this, const void *key, void *value)
|
|
{
|
|
void *old_value = NULL;
|
|
pair_t *pair;
|
|
u_int index, hash = 0, row = 0;
|
|
|
|
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 = find_key(this, key, &hash, &row);
|
|
if (pair)
|
|
{
|
|
old_value = pair->value;
|
|
pair->value = value;
|
|
pair->key = key;
|
|
return old_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)
|
|
{
|
|
pair_t *pair = find_key(this, key, NULL, NULL);
|
|
return pair ? pair->value : NULL;
|
|
}
|
|
|
|
/**
|
|
* Remove the given item from the table, returns the currently stored value.
|
|
*/
|
|
static void *remove_internal(private_hashtable_t *this, pair_t *pair)
|
|
{
|
|
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)
|
|
{
|
|
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->index)
|
|
{ /* the index is already advanced by one */
|
|
u_int index = enumerator->index - 1;
|
|
remove_internal(this, &this->items[index]);
|
|
}
|
|
}
|
|
|
|
METHOD(hashtable_t, get_count, u_int,
|
|
private_hashtable_t *this)
|
|
{
|
|
return this->count;
|
|
}
|
|
|
|
METHOD(enumerator_t, enumerate, bool,
|
|
private_enumerator_t *this, va_list args)
|
|
{
|
|
const void **key;
|
|
void **value;
|
|
pair_t *pair;
|
|
|
|
VA_ARGS_VGET(args, key, value);
|
|
|
|
while (this->index < this->table->items_count)
|
|
{
|
|
pair = &this->table->items[this->index++];
|
|
if (pair->key)
|
|
{
|
|
if (key)
|
|
{
|
|
*key = pair->key;
|
|
}
|
|
if (value)
|
|
{
|
|
*value = pair->value;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
METHOD(hashtable_t, create_enumerator, enumerator_t*,
|
|
private_hashtable_t *this)
|
|
{
|
|
private_enumerator_t *enumerator;
|
|
|
|
INIT(enumerator,
|
|
.enumerator = {
|
|
.enumerate = enumerator_enumerate_default,
|
|
.venumerate = _enumerate,
|
|
.destroy = (void*)free,
|
|
},
|
|
.table = this,
|
|
);
|
|
return &enumerator->enumerator;
|
|
}
|
|
|
|
static void destroy_internal(private_hashtable_t *this,
|
|
void (*fn)(void*,const void*))
|
|
{
|
|
pair_t *pair;
|
|
u_int i;
|
|
|
|
profiler_cleanup(&this->profile, this->count, this->size);
|
|
|
|
if (fn)
|
|
{
|
|
for (i = 0; i < this->items_count; i++)
|
|
{
|
|
pair = &this->items[i];
|
|
if (pair->key)
|
|
{
|
|
fn(pair->value, pair->key);
|
|
}
|
|
}
|
|
}
|
|
free(this->items);
|
|
free(this->table);
|
|
free(this);
|
|
}
|
|
|
|
METHOD(hashtable_t, destroy, void,
|
|
private_hashtable_t *this)
|
|
{
|
|
destroy_internal(this, NULL);
|
|
}
|
|
|
|
METHOD(hashtable_t, destroy_function, void,
|
|
private_hashtable_t *this, void (*fn)(void*,const void*))
|
|
{
|
|
destroy_internal(this, fn);
|
|
}
|
|
|
|
/*
|
|
* Described in header.
|
|
*/
|
|
hashtable_t *hashtable_create(hashtable_hash_t hash, hashtable_equals_t equals,
|
|
u_int size)
|
|
{
|
|
private_hashtable_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.put = _put,
|
|
.get = _get,
|
|
.remove = _remove_,
|
|
.remove_at = (void*)_remove_at,
|
|
.get_count = _get_count,
|
|
.create_enumerator = _create_enumerator,
|
|
.destroy = _destroy,
|
|
.destroy_function = _destroy_function,
|
|
},
|
|
.hash = hash,
|
|
.equals = equals,
|
|
);
|
|
|
|
init_hashtable(this, size);
|
|
|
|
profiler_init(&this->profile, 2);
|
|
|
|
return &this->public;
|
|
}
|