This commit is contained in:
@@ -0,0 +1,248 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
#include "table.h"
|
||||||
|
#include "../hasher/hasher.h"
|
||||||
|
#include "../../mem/allocator/mem_allocator.h"
|
||||||
|
#include "../../stream/stream.h"
|
||||||
|
#include "../../../common/aliases/aliases.h"
|
||||||
|
#include "../../../common/assert/assert.h"
|
||||||
|
#include "../../../common/misc/misc_utils.h"
|
||||||
|
|
||||||
|
wp_persist inline void hashTableValidate(const WpHashTable *table, u64 key_size, u64 value_size);
|
||||||
|
|
||||||
|
void *hashTableKeys(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return table->keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hashTableValues(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return table->values;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size) {
|
||||||
|
hashTableValidate(table, key_size, 0);
|
||||||
|
return arrayGet(table->keys, bucket.item_index, key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size) {
|
||||||
|
hashTableValidate(table, 0, value_size);
|
||||||
|
return table->has_values ? arrayGet(table->values, bucket.item_index, value_size) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hashTableGetKeyFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 key_size) {
|
||||||
|
return hashTableGetKeyFromBucket(table, table->buckets[ref.bucket_index], key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *hashTableGetValueFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 value_size) {
|
||||||
|
return hashTableGetValueFromBucket(table, table->buckets[ref.bucket_index], value_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableCapacity(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return wpArrayCapacity(table->keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableCount(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return wpArrayCount(table->keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableKeySize(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return wpArrayItemSize(table->keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableValueSize(WpHashTable *table) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return table->has_values ? wpArrayItemSize(table->values) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size) {
|
||||||
|
hashTableValidate(table, key_size, 0);
|
||||||
|
wpDebugAssert(key != NULL, "`key` should not be NULL");
|
||||||
|
WpU8Stream stream = table->encoder(key);
|
||||||
|
return wpHasherGetHash64(&table->hasher, &stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hashTableGrow(WpHashTable *table, const WpAllocator *allocator, u64 key_size, u64 value_size) {
|
||||||
|
hashTableValidate(table, key_size, value_size);
|
||||||
|
wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");
|
||||||
|
|
||||||
|
u64 capacity = hashTableCapacity(table) * 2;
|
||||||
|
u64 mask = capacity - wpU64Const(1);
|
||||||
|
WpArray keys = arrayAllocCapacity(allocator, capacity, WP_ARRAY_INIT_NONE, key_size);
|
||||||
|
WpArray values = NULL;
|
||||||
|
WpHashBucketArray buckets = wpArrayAllocCapacity(WpHashBucket, allocator, capacity, WP_ARRAY_INIT_FILLED);
|
||||||
|
|
||||||
|
if (table->has_values) {
|
||||||
|
values = arrayAllocCapacity(allocator, capacity, WP_ARRAY_INIT_NONE, value_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
WpArray old_keys = table->keys;
|
||||||
|
WpArray old_values = table->values;
|
||||||
|
|
||||||
|
table->keys = keys;
|
||||||
|
table->values = values;
|
||||||
|
table->buckets = buckets;
|
||||||
|
table->bucket_mask = mask;
|
||||||
|
|
||||||
|
for (u64 i = 0; i < wpArrayCount(old_keys); ++i) {
|
||||||
|
void *key = arrayGet(old_keys, i, key_size);
|
||||||
|
void *value = table->has_values ? arrayGet(old_values, i, value_size) : NULL;
|
||||||
|
u64 hash = hashTableCalcHash(table, key, key_size);
|
||||||
|
WpHashBucketRef ref = hashTableBucketRef(table, hash);
|
||||||
|
hashTableInsert(table, key, value, ref, key_size, value_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WpHashLookupResult hashTableLookup(WpHashTable *table, void *key, WpHashBucketRef ref, u64 key_size) {
|
||||||
|
hashTableValidate(table, key_size, 0);
|
||||||
|
wpDebugAssert(key != NULL, "`key` should not be NULL");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
WpHashBucket *bucket = &table->buckets[ref.bucket_index];
|
||||||
|
|
||||||
|
if (ref.dist_and_tag == bucket->dist_and_tag) {
|
||||||
|
b8 key_equal = table->key_eq(key, arrayGet(table->keys, bucket->item_index, key_size));
|
||||||
|
return (WpHashLookupResult){
|
||||||
|
.ref.dist_and_tag = ref.dist_and_tag,
|
||||||
|
.ref.bucket_index = ref.bucket_index,
|
||||||
|
.found = key_equal,
|
||||||
|
};
|
||||||
|
} else if (hashTableGetDist(ref.dist_and_tag) > hashTableGetDist(bucket->dist_and_tag)) {
|
||||||
|
return (WpHashLookupResult){
|
||||||
|
.ref.dist_and_tag = ref.dist_and_tag,
|
||||||
|
.ref.bucket_index = ref.bucket_index,
|
||||||
|
.found = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.dist_and_tag = hashTableDistInc(ref.dist_and_tag);
|
||||||
|
ref.bucket_index = hashTableNextBucket(table, ref.bucket_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hashTableUpdate(WpHashTable *table, void *value, WpHashBucketRef ref, u64 value_size) {
|
||||||
|
hashTableValidate(table, 0, value_size);
|
||||||
|
if (!(table->has_values)) {
|
||||||
|
wpRuntimeAssert(0, "Attempting to update a hash table that has no values");
|
||||||
|
}
|
||||||
|
wpDebugAssert(value != NULL, "`value` should not be NULL");
|
||||||
|
|
||||||
|
WpHashBucket *bucket = &table->buckets[ref.bucket_index];
|
||||||
|
arraySet(table->values, bucket->item_index, value, value_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hashTableInsert(WpHashTable *table, void *key, void *value, WpHashBucketRef ref, u64 key_size,
|
||||||
|
u64 value_size) {
|
||||||
|
hashTableValidate(table, key_size, value_size);
|
||||||
|
wpDebugAssert(key != NULL, "`key` should not be NULL");
|
||||||
|
|
||||||
|
u64 index = wpArrayCount(table->keys);
|
||||||
|
while (true) {
|
||||||
|
WpHashBucket *bucket = &table->buckets[ref.bucket_index];
|
||||||
|
|
||||||
|
if (bucket->dist_and_tag == 0) {
|
||||||
|
bucket->dist_and_tag = ref.dist_and_tag;
|
||||||
|
bucket->item_index = index;
|
||||||
|
break;
|
||||||
|
} else if (hashTableGetDist(ref.dist_and_tag) > hashTableGetDist(bucket->dist_and_tag)) {
|
||||||
|
wpMiscUtilsSwap(u64, &ref.dist_and_tag, &bucket->dist_and_tag);
|
||||||
|
wpMiscUtilsSwap(u64, &index, &bucket->item_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.dist_and_tag = hashTableDistInc(ref.dist_and_tag);
|
||||||
|
ref.bucket_index = hashTableNextBucket(table, ref.bucket_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayAppendCapped(table->keys, key, key_size);
|
||||||
|
if (table->has_values) {
|
||||||
|
arrayAppendCapped(table->values, value, value_size);
|
||||||
|
}
|
||||||
|
table->load_factor = (f32)wpArrayCount(table->keys) / (f32)wpArrayCapacity(table->keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hashTableRemove(WpHashTable *table, WpHashBucketRef ref, u64 key_size, u64 value_size) {
|
||||||
|
hashTableValidate(table, key_size, value_size);
|
||||||
|
|
||||||
|
WpHashBucket *bucket = &table->buckets[ref.bucket_index];
|
||||||
|
|
||||||
|
u64 last_item_index = wpArrayCount(table->keys) - 1;
|
||||||
|
|
||||||
|
if (bucket->item_index < last_item_index) {
|
||||||
|
// Update the bucket for the last item before swapping
|
||||||
|
u64 hash = hashTableCalcHash(table, arrayGet(table->keys, last_item_index, key_size), key_size);
|
||||||
|
|
||||||
|
WpHashLookupResult result = hashTableLookup(table, arrayGet(table->keys, last_item_index, key_size),
|
||||||
|
hashTableBucketRef(table, hash), key_size);
|
||||||
|
wpRuntimeAssert(result.found, "Hash table item should exist but it doesn't");
|
||||||
|
|
||||||
|
table->buckets[result.ref.bucket_index].item_index = bucket->item_index;
|
||||||
|
|
||||||
|
arrayRemoveSwapEnd(table->keys, bucket->item_index, key_size);
|
||||||
|
if (table->has_values) {
|
||||||
|
arrayRemoveSwapEnd(table->values, bucket->item_index, value_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bucket->dist_and_tag > 0) {
|
||||||
|
u64 next_index = hashTableNextBucket(table, ref.bucket_index);
|
||||||
|
WpHashBucket *next = &table->buckets[next_index];
|
||||||
|
|
||||||
|
if (hashTableGetDist(next->dist_and_tag) > WP_HASH_DIST_INC) {
|
||||||
|
bucket->dist_and_tag = hashTableDistDec(next->dist_and_tag);
|
||||||
|
bucket->item_index = next->item_index;
|
||||||
|
|
||||||
|
ref.bucket_index = next_index;
|
||||||
|
bucket = &table->buckets[ref.bucket_index];
|
||||||
|
} else {
|
||||||
|
bucket->dist_and_tag = 0;
|
||||||
|
bucket->item_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hashTableClear(WpHashTable *table, u64 key_size, u64 value_size) {
|
||||||
|
hashTableValidate(table, key_size, value_size);
|
||||||
|
arrayClear(table->keys, key_size);
|
||||||
|
if (table->has_values) {
|
||||||
|
arrayClear(table->values, value_size);
|
||||||
|
}
|
||||||
|
wpArrayZero(WpHashBucket, table->buckets);
|
||||||
|
}
|
||||||
|
|
||||||
|
WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash) {
|
||||||
|
hashTableValidate(table, 0, 0);
|
||||||
|
return (WpHashBucketRef){
|
||||||
|
.dist_and_tag = WP_HASH_DIST_INC | (hash & WP_HASH_TAG_MASK),
|
||||||
|
.bucket_index = hash & table->bucket_mask,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableGetDist(u64 dist_and_tag) {
|
||||||
|
return dist_and_tag & (~WP_HASH_TAG_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index) {
|
||||||
|
return (++bucket_index) & table->bucket_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableDistInc(u64 dist_and_tag) {
|
||||||
|
return dist_and_tag + WP_HASH_DIST_INC;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hashTableDistDec(u64 dist_and_tag) {
|
||||||
|
return dist_and_tag - WP_HASH_DIST_INC;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_persist inline void hashTableValidate(const WpHashTable *table, u64 key_size, u64 value_size) {
|
||||||
|
wpDebugAssert(table != NULL, "`table` should not be NULL");
|
||||||
|
wpRuntimeAssert(WP_HASH_TABLE_MAGIC == table->magic, "`table` is not a valid wapp hash table");
|
||||||
|
if (key_size > 0) {
|
||||||
|
wpRuntimeAssert(key_size == wpArrayItemSize(table->keys), "Invalid key type provided");
|
||||||
|
}
|
||||||
|
if (value_size > 0) {
|
||||||
|
wpRuntimeAssert(value_size == wpArrayItemSize(table->values), "Invalid value type provided");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hash table implementation with Robin Hood open addressing for collision resolution.
|
||||||
|
*
|
||||||
|
* The implementation is based on a version of this C++ implementation:
|
||||||
|
* https://github.com/martinus/unordered_dense
|
||||||
|
*
|
||||||
|
* Extra references:
|
||||||
|
* - https://faultlore.com/blah/robinhood-part-1/
|
||||||
|
* - https://blog.hieunt.me/blog/robinhood-hashing
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* This is the internal hash table implementation and it is not intended to be used directly.
|
||||||
|
* Please check ../map/map.h and ../set/set.h for the public APIs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TABLE_H
|
||||||
|
#define TABLE_H
|
||||||
|
|
||||||
|
#include "../hasher/hasher.h"
|
||||||
|
#include "../../array/array.h"
|
||||||
|
#include "../../mem/allocator/mem_allocator.h"
|
||||||
|
#include "../../stream/stream.h"
|
||||||
|
#include "../../../common/aliases/aliases.h"
|
||||||
|
#include "../../../common/platform/platform.h"
|
||||||
|
|
||||||
|
#ifdef WP_PLATFORM_CPP
|
||||||
|
BEGIN_C_LINKAGE
|
||||||
|
#endif // !WP_PLATFORM_CPP
|
||||||
|
|
||||||
|
#define WP_HASH_TABLE_MAGIC wpU64Const(0x57504854424c)
|
||||||
|
#define WP_HASH_TAG_BITS wpU64Const(32)
|
||||||
|
#define WP_HASH_DIST_INC (wpU64Const(1) << WP_HASH_TAG_BITS)
|
||||||
|
#define WP_HASH_TAG_MASK (WP_HASH_DIST_INC - wpU64Const(1))
|
||||||
|
#define WP_HASH_MAX_LOAD_FACTOR 0.65f
|
||||||
|
|
||||||
|
typedef WpU8Stream (*WpU8StreamEncoder)(void *data);
|
||||||
|
typedef b8 (*WpKeyEqualTest)(void *a, void *b);
|
||||||
|
|
||||||
|
typedef struct WpHashBucket {
|
||||||
|
u64 dist_and_tag;
|
||||||
|
u64 item_index;
|
||||||
|
} WpHashBucket;
|
||||||
|
typedef WpHashBucket *WpHashBucketArray;
|
||||||
|
|
||||||
|
typedef struct WpHashTable {
|
||||||
|
WpHasher hasher;
|
||||||
|
WpU8StreamEncoder encoder;
|
||||||
|
WpKeyEqualTest key_eq;
|
||||||
|
WpArray keys;
|
||||||
|
WpArray values;
|
||||||
|
WpHashBucketArray buckets;
|
||||||
|
u64 magic;
|
||||||
|
u64 bucket_mask;
|
||||||
|
f32 load_factor;
|
||||||
|
b8 has_values;
|
||||||
|
} WpHashTable;
|
||||||
|
|
||||||
|
typedef struct WpHashBucketRef {
|
||||||
|
u64 dist_and_tag;
|
||||||
|
u64 bucket_index;
|
||||||
|
} WpHashBucketRef;
|
||||||
|
|
||||||
|
typedef struct WpHashLookupResult {
|
||||||
|
WpHashBucketRef ref;
|
||||||
|
b8 found;
|
||||||
|
} WpHashLookupResult;
|
||||||
|
|
||||||
|
void *hashTableKeys(WpHashTable *table);
|
||||||
|
void *hashTableValues(WpHashTable *table);
|
||||||
|
void *hashTableGetKeyFromBucket(WpHashTable *table, WpHashBucket bucket, u64 key_size);
|
||||||
|
void *hashTableGetValueFromBucket(WpHashTable *table, WpHashBucket bucket, u64 value_size);
|
||||||
|
void *hashTableGetKeyFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 key_size);
|
||||||
|
void *hashTableGetValueFromBucketRef(WpHashTable *table, WpHashBucketRef ref, u64 value_size);
|
||||||
|
u64 hashTableCapacity(WpHashTable *table);
|
||||||
|
u64 hashTableCount(WpHashTable *table);
|
||||||
|
u64 hashTableKeySize(WpHashTable *table);
|
||||||
|
u64 hashTableValueSize(WpHashTable *table);
|
||||||
|
u64 hashTableCalcHash(WpHashTable *table, void *key, u64 key_size);
|
||||||
|
void hashTableGrow(WpHashTable *table, const WpAllocator *allocator, u64 key_size,
|
||||||
|
u64 value_size);
|
||||||
|
WpHashLookupResult hashTableLookup(WpHashTable *table, void *key, WpHashBucketRef ref, u64 key_size);
|
||||||
|
void hashTableUpdate(WpHashTable *table, void *value, WpHashBucketRef ref, u64 value_size);
|
||||||
|
void hashTableInsert(WpHashTable *table, void *key, void *value, WpHashBucketRef ref,
|
||||||
|
u64 key_size, u64 value_size);
|
||||||
|
void hashTableRemove(WpHashTable *table, WpHashBucketRef ref, u64 key_size, u64 value_size);
|
||||||
|
void hashTableClear(WpHashTable *table, u64 key_size, u64 value_size);
|
||||||
|
WpHashBucketRef hashTableBucketRef(WpHashTable *table, u64 hash);
|
||||||
|
u64 hashTableGetDist(u64 dist_and_tag);
|
||||||
|
u64 hashTableNextBucket(WpHashTable *table, u64 bucket_index);
|
||||||
|
u64 hashTableDistInc(u64 dist_and_tag);
|
||||||
|
u64 hashTableDistDec(u64 dist_and_tag);
|
||||||
|
|
||||||
|
#ifdef WP_PLATFORM_CPP
|
||||||
|
END_C_LINKAGE
|
||||||
|
#endif // !WP_PLATFORM_CPP
|
||||||
|
|
||||||
|
#endif // !TABLE_H
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "hash/hasher/hasher.c"
|
#include "hash/hasher/hasher.c"
|
||||||
#include "hash/hasher/murmur3.c"
|
#include "hash/hasher/murmur3.c"
|
||||||
#include "hash/hasher/siphash.c"
|
#include "hash/hasher/siphash.c"
|
||||||
|
#include "hash/table/table.c"
|
||||||
#include "queue/queue.c"
|
#include "queue/queue.c"
|
||||||
#include "stream/stream.c"
|
#include "stream/stream.c"
|
||||||
#include "mem/allocator/mem_allocator.c"
|
#include "mem/allocator/mem_allocator.c"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "hash/hasher/hasher.h"
|
#include "hash/hasher/hasher.h"
|
||||||
#include "hash/hasher/murmur3.h"
|
#include "hash/hasher/murmur3.h"
|
||||||
#include "hash/hasher/siphash.h"
|
#include "hash/hasher/siphash.h"
|
||||||
|
#include "hash/table/table.h"
|
||||||
#include "queue/queue.h"
|
#include "queue/queue.h"
|
||||||
#include "stream/stream.h"
|
#include "stream/stream.h"
|
||||||
#include "mem/allocator/mem_allocator.h"
|
#include "mem/allocator/mem_allocator.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user