|
|
@ -10,11 +10,9 @@
|
|
|
|
#include "hashmap.h"
|
|
|
|
#include "hashmap.h"
|
|
|
|
|
|
|
|
|
|
|
|
static void *(*_malloc)(size_t) = NULL;
|
|
|
|
static void *(*_malloc)(size_t) = NULL;
|
|
|
|
|
|
|
|
static void *(*_realloc)(void *, size_t) = NULL;
|
|
|
|
static void (*_free)(void *) = NULL;
|
|
|
|
static void (*_free)(void *) = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
#define hmmalloc (_malloc?_malloc:malloc)
|
|
|
|
|
|
|
|
#define hmfree (_free?_free:free)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap_set_allocator allows for configuring a custom allocator for
|
|
|
|
// hashmap_set_allocator allows for configuring a custom allocator for
|
|
|
|
// all hashmap library operations. This function, if needed, should be called
|
|
|
|
// all hashmap library operations. This function, if needed, should be called
|
|
|
|
// only once at startup and a prior to calling hashmap_new().
|
|
|
|
// only once at startup and a prior to calling hashmap_new().
|
|
|
@ -36,6 +34,9 @@ struct bucket {
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap is an open addressed hash map using robinhood hashing.
|
|
|
|
// hashmap is an open addressed hash map using robinhood hashing.
|
|
|
|
struct hashmap {
|
|
|
|
struct hashmap {
|
|
|
|
|
|
|
|
void *(*malloc)(size_t);
|
|
|
|
|
|
|
|
void *(*realloc)(void *, size_t);
|
|
|
|
|
|
|
|
void (*free)(void *);
|
|
|
|
bool oom;
|
|
|
|
bool oom;
|
|
|
|
size_t elsize;
|
|
|
|
size_t elsize;
|
|
|
|
size_t cap;
|
|
|
|
size_t cap;
|
|
|
@ -67,22 +68,13 @@ static uint64_t get_hash(struct hashmap *map, void *key) {
|
|
|
|
return map->hash(key, map->seed0, map->seed1) << 16 >> 16;
|
|
|
|
return map->hash(key, map->seed0, map->seed1) << 16 >> 16;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap_new returns a new hash map.
|
|
|
|
// hashmap_new_with_allocator returns a new hash map using a custom allocator.
|
|
|
|
// Param `elsize` is the size of each element in the tree. Every element that
|
|
|
|
// See hashmap_new for more information information
|
|
|
|
// is inserted, deleted, or retrieved will be this size.
|
|
|
|
struct hashmap *hashmap_new_with_allocator(
|
|
|
|
// Param `cap` is the default lower capacity of the hashmap. Setting this to
|
|
|
|
void *(*_malloc)(size_t),
|
|
|
|
// zero will default to 16.
|
|
|
|
void *(*_realloc)(void*, size_t),
|
|
|
|
// Params `seed0` and `seed1` are optional seed values that are passed to the
|
|
|
|
void (*_free)(void*),
|
|
|
|
// following `hash` function. These can be any value you wish but it's often
|
|
|
|
size_t elsize, size_t cap,
|
|
|
|
// best to use randomly generated values.
|
|
|
|
|
|
|
|
// Param `hash` is a function that generates a hash value for an item. It's
|
|
|
|
|
|
|
|
// important that you provide a good hash function, otherwise it will perform
|
|
|
|
|
|
|
|
// poorly or be vulnerable to Denial-of-service attacks. This implementation
|
|
|
|
|
|
|
|
// comes with two helper functions `hashmap_sip()` and `hashmap_murmur()`.
|
|
|
|
|
|
|
|
// Param `compare` is a function that compares items in the tree. See the
|
|
|
|
|
|
|
|
// qsort stdlib function for an example of how this function works.
|
|
|
|
|
|
|
|
// The hashmap must be freed with hashmap_free().
|
|
|
|
|
|
|
|
struct hashmap *hashmap_new(size_t elsize, size_t cap,
|
|
|
|
|
|
|
|
uint64_t seed0, uint64_t seed1,
|
|
|
|
uint64_t seed0, uint64_t seed1,
|
|
|
|
uint64_t (*hash)(const void *item,
|
|
|
|
uint64_t (*hash)(const void *item,
|
|
|
|
uint64_t seed0, uint64_t seed1),
|
|
|
|
uint64_t seed0, uint64_t seed1),
|
|
|
@ -90,6 +82,9 @@ struct hashmap *hashmap_new(size_t elsize, size_t cap,
|
|
|
|
void *udata),
|
|
|
|
void *udata),
|
|
|
|
void *udata)
|
|
|
|
void *udata)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
_malloc = _malloc ? _malloc : malloc;
|
|
|
|
|
|
|
|
_realloc = _realloc ? _realloc : realloc;
|
|
|
|
|
|
|
|
_free = _free ? _free : free;
|
|
|
|
int ncap = 16;
|
|
|
|
int ncap = 16;
|
|
|
|
if (cap < ncap) {
|
|
|
|
if (cap < ncap) {
|
|
|
|
cap = ncap;
|
|
|
|
cap = ncap;
|
|
|
@ -105,7 +100,7 @@ struct hashmap *hashmap_new(size_t elsize, size_t cap,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// hashmap + spare + edata
|
|
|
|
// hashmap + spare + edata
|
|
|
|
size_t size = sizeof(struct hashmap)+bucketsz*2;
|
|
|
|
size_t size = sizeof(struct hashmap)+bucketsz*2;
|
|
|
|
struct hashmap *map = hmmalloc(size);
|
|
|
|
struct hashmap *map = _malloc(size);
|
|
|
|
if (!map) {
|
|
|
|
if (!map) {
|
|
|
|
return NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -122,17 +117,52 @@ struct hashmap *hashmap_new(size_t elsize, size_t cap,
|
|
|
|
map->cap = cap;
|
|
|
|
map->cap = cap;
|
|
|
|
map->nbuckets = cap;
|
|
|
|
map->nbuckets = cap;
|
|
|
|
map->mask = map->nbuckets-1;
|
|
|
|
map->mask = map->nbuckets-1;
|
|
|
|
map->buckets = hmmalloc(map->bucketsz*map->nbuckets);
|
|
|
|
map->buckets = _malloc(map->bucketsz*map->nbuckets);
|
|
|
|
if (!map->buckets) {
|
|
|
|
if (!map->buckets) {
|
|
|
|
hmfree(map);
|
|
|
|
_free(map);
|
|
|
|
return NULL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
memset(map->buckets, 0, map->bucketsz*map->nbuckets);
|
|
|
|
memset(map->buckets, 0, map->bucketsz*map->nbuckets);
|
|
|
|
map->growat = map->nbuckets*0.75;
|
|
|
|
map->growat = map->nbuckets*0.75;
|
|
|
|
map->shrinkat = map->nbuckets*0.10;
|
|
|
|
map->shrinkat = map->nbuckets*0.10;
|
|
|
|
|
|
|
|
map->malloc = _malloc;
|
|
|
|
|
|
|
|
map->realloc = _realloc;
|
|
|
|
|
|
|
|
map->free = _free;
|
|
|
|
return map;
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap_new returns a new hash map.
|
|
|
|
|
|
|
|
// Param `elsize` is the size of each element in the tree. Every element that
|
|
|
|
|
|
|
|
// is inserted, deleted, or retrieved will be this size.
|
|
|
|
|
|
|
|
// Param `cap` is the default lower capacity of the hashmap. Setting this to
|
|
|
|
|
|
|
|
// zero will default to 16.
|
|
|
|
|
|
|
|
// Params `seed0` and `seed1` are optional seed values that are passed to the
|
|
|
|
|
|
|
|
// following `hash` function. These can be any value you wish but it's often
|
|
|
|
|
|
|
|
// best to use randomly generated values.
|
|
|
|
|
|
|
|
// Param `hash` is a function that generates a hash value for an item. It's
|
|
|
|
|
|
|
|
// important that you provide a good hash function, otherwise it will perform
|
|
|
|
|
|
|
|
// poorly or be vulnerable to Denial-of-service attacks. This implementation
|
|
|
|
|
|
|
|
// comes with two helper functions `hashmap_sip()` and `hashmap_murmur()`.
|
|
|
|
|
|
|
|
// Param `compare` is a function that compares items in the tree. See the
|
|
|
|
|
|
|
|
// qsort stdlib function for an example of how this function works.
|
|
|
|
|
|
|
|
// The hashmap must be freed with hashmap_free().
|
|
|
|
|
|
|
|
struct hashmap *hashmap_new(size_t elsize, size_t cap,
|
|
|
|
|
|
|
|
uint64_t seed0, uint64_t seed1,
|
|
|
|
|
|
|
|
uint64_t (*hash)(const void *item,
|
|
|
|
|
|
|
|
uint64_t seed0, uint64_t seed1),
|
|
|
|
|
|
|
|
int (*compare)(const void *a, const void *b,
|
|
|
|
|
|
|
|
void *udata),
|
|
|
|
|
|
|
|
void *udata)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return hashmap_new_with_allocator(
|
|
|
|
|
|
|
|
(_malloc?_malloc:malloc),
|
|
|
|
|
|
|
|
(_realloc?_realloc:realloc),
|
|
|
|
|
|
|
|
(_free?_free:free),
|
|
|
|
|
|
|
|
elsize, cap, seed0, seed1, hash, compare, udata
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap_clear quickly clears the map.
|
|
|
|
// hashmap_clear quickly clears the map.
|
|
|
|
// When the update_cap is provided, the map's capacity will be updated to match
|
|
|
|
// When the update_cap is provided, the map's capacity will be updated to match
|
|
|
|
// the currently number of allocated buckets. This is an optimization to ensure
|
|
|
|
// the currently number of allocated buckets. This is an optimization to ensure
|
|
|
@ -142,9 +172,9 @@ void hashmap_clear(struct hashmap *map, bool update_cap) {
|
|
|
|
if (update_cap) {
|
|
|
|
if (update_cap) {
|
|
|
|
map->cap = map->nbuckets;
|
|
|
|
map->cap = map->nbuckets;
|
|
|
|
} else if (map->nbuckets != map->cap) {
|
|
|
|
} else if (map->nbuckets != map->cap) {
|
|
|
|
void *new_buckets = hmmalloc(map->bucketsz*map->cap);
|
|
|
|
void *new_buckets = map->malloc(map->bucketsz*map->cap);
|
|
|
|
if (new_buckets) {
|
|
|
|
if (new_buckets) {
|
|
|
|
hmfree(map->buckets);
|
|
|
|
map->free(map->buckets);
|
|
|
|
map->buckets = new_buckets;
|
|
|
|
map->buckets = new_buckets;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
map->nbuckets = map->cap;
|
|
|
|
map->nbuckets = map->cap;
|
|
|
@ -185,13 +215,13 @@ static bool resize(struct hashmap *map, size_t new_cap) {
|
|
|
|
entry->dib += 1;
|
|
|
|
entry->dib += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hmfree(map->buckets);
|
|
|
|
map->free(map->buckets);
|
|
|
|
map->buckets = map2->buckets;
|
|
|
|
map->buckets = map2->buckets;
|
|
|
|
map->nbuckets = map2->nbuckets;
|
|
|
|
map->nbuckets = map2->nbuckets;
|
|
|
|
map->mask = map2->mask;
|
|
|
|
map->mask = map2->mask;
|
|
|
|
map->growat = map2->growat;
|
|
|
|
map->growat = map2->growat;
|
|
|
|
map->shrinkat = map2->shrinkat;
|
|
|
|
map->shrinkat = map2->shrinkat;
|
|
|
|
hmfree(map2);
|
|
|
|
map->free(map2);
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -329,8 +359,8 @@ size_t hashmap_count(struct hashmap *map) {
|
|
|
|
// hashmap_free frees the hash map
|
|
|
|
// hashmap_free frees the hash map
|
|
|
|
void hashmap_free(struct hashmap *map) {
|
|
|
|
void hashmap_free(struct hashmap *map) {
|
|
|
|
if (!map) return;
|
|
|
|
if (!map) return;
|
|
|
|
hmfree(map->buckets);
|
|
|
|
map->free(map->buckets);
|
|
|
|
hmfree(map);
|
|
|
|
map->free(map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// hashmap_oom returns true if the last hashmap_set() call failed due to the
|
|
|
|
// hashmap_oom returns true if the last hashmap_set() call failed due to the
|
|
|
|