dqn: Fix DSMap make slot resize bug, merge hash into key

This commit is contained in:
doyle 2023-03-28 22:47:25 +11:00
parent 0f15694de9
commit 9e3dc694d8
2 changed files with 223 additions and 145 deletions

269
dqn.h
View File

@ -1491,6 +1491,9 @@ DQN_API void Dqn_Arena_LogStats(Dqn_Arena const *arena);
// variable which is the Value part of the hash-table simplifying interface // variable which is the Value part of the hash-table simplifying interface
// complexity and cruft brought by C++. // complexity and cruft brought by C++.
// //
// Keys are value-copied into the hash-table. If the key uses a pointer to a
// buffer, this buffer must be valid throughout the lifetime of the hash table!
//
// NOTE: Dqn_DSMap API // NOTE: Dqn_DSMap API
// ============================================================================= // =============================================================================
// //
@ -1521,21 +1524,30 @@ DQN_API void Dqn_Arena_LogStats(Dqn_Arena const *arena);
// @proc Dqn_DSMap_HashToSlotIndex // @proc Dqn_DSMap_HashToSlotIndex
// @desc Calculate the index into the map's `slots` array from the given hash. // @desc Calculate the index into the map's `slots` array from the given hash.
// //
// @proc Dqn_DSMap_FindSlot // @proc Dqn_DSMap_FindSlot, Dqn_DSMap_Find
// @desc Find the slot in the map's `slots` array corresponding to the given // @desc Find the slot in the map's `slots` array corresponding to the given
// key and hash. If the map does not contain the key, a null pointer is // key and hash. If the map does not contain the key, a null pointer is
// returned. // returned.
// //
// @proc Dqn_DSMap_MakeSlot // `Find` returns the value.
// @desc Same as `DSMap_FindSlot` except if the key does not exist it will be // `FindSlot` returns the map's hash table slot.
// made and returned.
// //
// @proc Dqn_DSMap_Find // @proc Dqn_DSMap_MakeSlot, Dqn_DSMap_Make, Dqn_DSMap_Set, Dqn_DSMap_SetSlot
// @desc Same as `DSMap_FindSlot` except it returns the value for the key. If // @desc Same as `DSMap_Find*` except if the key does not exist in the table,
// the map does not contain the key, a null pointer is returned. // a hash-table slot will be made.
// //
// @proc Dqn_DSMap_Make // `Make` assigns the key to the table and returns the hash table slot's value.
// @desc Same as `DSMap_MakeSlot` except it returns the value for the key. // `Set` assigns the key-value to the table and returns the hash table slot's value.
// `MakeSlot` assigns the key to the table and returns the hash table slot.
// `SetSlot` assigns the key-value to the table and returns the hash table slot.
//
// If by adding the key-value pair to the table puts the table over 75% load,
// the table will be grown to 2x the current the size before insertion
// completes.
//
// @param found[out] Pass a pointer to a bool. The bool will be set to true
// if the item already existed in the map before, or false if the item was
// just created by this call.
// //
// @proc Dqn_DSMap_Resize // @proc Dqn_DSMap_Resize
// @desc Resize the table and move all elements to the new map. // @desc Resize the table and move all elements to the new map.
@ -1544,24 +1556,28 @@ DQN_API void Dqn_Arena_LogStats(Dqn_Arena const *arena);
// @return False if memory allocation fails, or the requested size is smaller // @return False if memory allocation fails, or the requested size is smaller
// than the current number of elements in the map to resize. True otherwise. // than the current number of elements in the map to resize. True otherwise.
// //
// @proc Dqn_Dqn_DSMap_Set
// @desc Assign the key-value pair to the table. Pre-existing keys are updated
// in place and new key-value pairs are inserted into the table. If by adding
// the key-value pair to the table puts the table over 75% load, the table
// will be grown to 2x the current the size before insertion completes.
//
// @proc Dqn_DSMap_Erase // @proc Dqn_DSMap_Erase
// @desc Remove the key-value pair from the table. If by erasing the key-value // @desc Remove the key-value pair from the table. If by erasing the key-value
// pair from the table puts the table under 25% load, the table will be shrunk // pair from the table puts the table under 25% load, the table will be shrunk
// by 1/2 the current size after erasing. The table will not shrink below the // by 1/2 the current size after erasing. The table will not shrink below the
// initial size that the table was initialised as. // initial size that the table was initialised as.
// //
// @proc Dqn_DSMap_KeyCStringLit, Dqn_DSMap_KeyU64, Dqn_DSMap_KeyBuffer // @proc Dqn_DSMap_KeyCStringLit, Dqn_DSMap_KeyU64, Dqn_DSMap_KeyBuffer,
// @desc Create a hash-table key given a cstring, uint64 or buffer (ptr + len) // Dqn_DSMap_KeyString8 Dqn_DSMap_KeyString8Copy
// respectively. // @desc Create a hash-table key given
//
// `KeyCStringLit` a cstring literal
// `KeyU64` a u64
// `KeyBuffer` a (ptr+len) slice of bytes
// `KeyString8` a Dqn_String8 string
// `KeyString8Copy` a Dqn_String8 string that is copied first using the allocator
//
// If the key points to an array of bytes, the lifetime of those bytes *must*
// remain valid throughout the lifetime of the map as the pointers are value
// copied into the hash table!
// //
// @proc Dqn_DSMap_KeyU64NoHash // @proc Dqn_DSMap_KeyU64NoHash
// @desc Create a hash-table key given the uint64. This key is *not* hashed to // @desc Create a hash-table key given the uint64. This u64 is *not* hashed to
// map values into the table. This is useful if you already have a source of // map values into the table. This is useful if you already have a source of
// data that is already sufficiently uniformly distributed already (e.g. // data that is already sufficiently uniformly distributed already (e.g.
// using 8 bytes taken from a SHA256 hash as the key). // using 8 bytes taken from a SHA256 hash as the key).
@ -1580,6 +1596,7 @@ enum Dqn_DSMapKeyType
struct Dqn_DSMapKey struct Dqn_DSMapKey
{ {
Dqn_DSMapKeyType type; Dqn_DSMapKeyType type;
uint32_t hash;
union Payload { union Payload {
struct Buffer { struct Buffer {
void const *data; void const *data;
@ -1593,8 +1610,7 @@ template <typename T>
struct Dqn_DSMapSlot struct Dqn_DSMapSlot
{ {
Dqn_DSMapKey key; ///< Hash table lookup key Dqn_DSMapKey key; ///< Hash table lookup key
T value; T value; ///< Hash table value
uint32_t hash; ///< hash(key) for this slot
}; };
using Dqn_DSMapHashFunction = uint32_t(Dqn_DSMapKey key, uint32_t seed); using Dqn_DSMapHashFunction = uint32_t(Dqn_DSMapKey key, uint32_t seed);
@ -1617,23 +1633,26 @@ DQN_API template <typename T> Dqn_DSMap<T> Dqn_DSMap_Init (uint32
DQN_API template <typename T> void Dqn_DSMap_Deinit (Dqn_DSMap<T> *map); DQN_API template <typename T> void Dqn_DSMap_Deinit (Dqn_DSMap<T> *map);
DQN_API template <typename T> bool Dqn_DSMap_IsValid (Dqn_DSMap<T> const *map); DQN_API template <typename T> bool Dqn_DSMap_IsValid (Dqn_DSMap<T> const *map);
DQN_API template <typename T> uint32_t Dqn_DSMap_Hash (Dqn_DSMap<T> const *map, Dqn_DSMapKey key); DQN_API template <typename T> uint32_t Dqn_DSMap_Hash (Dqn_DSMap<T> const *map, Dqn_DSMapKey key);
DQN_API template <typename T> uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap<T> const *map, Dqn_DSMapKey key, uint32_t hash); DQN_API template <typename T> uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap<T> const *map, Dqn_DSMapKey key);
DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_FindSlot (Dqn_DSMap<T> const *map, Dqn_DSMapKey key, uint32_t hash); DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_FindSlot (Dqn_DSMap<T> const *map, Dqn_DSMapKey key);
DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_MakeSlot (Dqn_DSMap<T> *map, Dqn_DSMapKey key, uint32_t hash); DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_MakeSlot (Dqn_DSMap<T> *map, Dqn_DSMapKey key, bool *found);
DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_SetSlot (Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value, bool *found);
DQN_API template <typename T> T * Dqn_DSMap_Find (Dqn_DSMap<T> const *map, Dqn_DSMapKey key); DQN_API template <typename T> T * Dqn_DSMap_Find (Dqn_DSMap<T> const *map, Dqn_DSMapKey key);
DQN_API template <typename T> T * Dqn_DSMap_Make (Dqn_DSMap<T> *map, Dqn_DSMapKey key); DQN_API template <typename T> T * Dqn_DSMap_Make (Dqn_DSMap<T> *map, Dqn_DSMapKey key, bool *found);
DQN_API template <typename T> T * Dqn_DSMap_Set (Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value, bool *found);
DQN_API template <typename T> bool Dqn_DSMap_Resize (Dqn_DSMap<T> *map, uint32_t size); DQN_API template <typename T> bool Dqn_DSMap_Resize (Dqn_DSMap<T> *map, uint32_t size);
DQN_API template <typename T> Dqn_DSMapSlot<T> *Dqn_DSMap_Set (Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value);
DQN_API template <typename T> bool Dqn_DSMap_Erase (Dqn_DSMap<T> *map, Dqn_DSMapKey key); DQN_API template <typename T> bool Dqn_DSMap_Erase (Dqn_DSMap<T> *map, Dqn_DSMapKey key);
// NOTE: Dqn_DSMapKey // NOTE: Dqn_DSMapKey
// ============================================================================= // =============================================================================
DQN_API template <typename T> Dqn_DSMapKey Dqn_DSMap_KeyBuffer (Dqn_DSMap<T> const *map, void const *data, uint32_t size);
DQN_API template <typename T> Dqn_DSMapKey Dqn_DSMap_KeyU64 (Dqn_DSMap<T> const *map, uint64_t u64);
DQN_API template <typename T> Dqn_DSMapKey Dqn_DSMap_KeyString8 (Dqn_DSMap<T> const *map, Dqn_String8 string);
DQN_API template <typename T> Dqn_DSMapKey Dqn_DSMap_KeyString8Copy (Dqn_DSMap<T> const *map, Dqn_Allocator allocator, Dqn_String8 string);
#define Dqn_DSMap_KeyCStringLit(map, string) Dqn_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1)
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash (uint64_t u64);
DQN_API bool Dqn_DSMap_KeyEquals (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); DQN_API bool Dqn_DSMap_KeyEquals (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs);
DQN_API bool operator== (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs); DQN_API bool operator== (Dqn_DSMapKey lhs, Dqn_DSMapKey rhs);
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyBuffer (void const *data, uint32_t size);
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64 (uint64_t u64);
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash (uint64_t u64);
#define Dqn_DSMap_KeyCStringLit(string) Dqn_DSMap_KeyBuffer(string, sizeof((string))/sizeof((string)[0]) - 1)
#endif // !defined(DQN_NO_DSMAP) #endif // !defined(DQN_NO_DSMAP)
// ---------------------------------+-----------------------------+--------------------------------- // ---------------------------------+-----------------------------+---------------------------------
@ -3402,19 +3421,19 @@ uint32_t Dqn_DSMap_Hash(Dqn_DSMap<T> const *map, Dqn_DSMapKey key)
} }
template <typename T> template <typename T>
uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap<T> const *map, Dqn_DSMapKey key, uint32_t hash) uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap<T> const *map, Dqn_DSMapKey key)
{ {
uint32_t result = DQN_DS_MAP_SENTINEL_SLOT; uint32_t result = DQN_DS_MAP_SENTINEL_SLOT;
if (!Dqn_DSMap_IsValid(map)) if (!Dqn_DSMap_IsValid(map))
return result; return result;
result = hash & (map->size - 1); result = key.hash & (map->size - 1);
for (;;) { for (;;) {
if (map->hash_to_slot[result] == DQN_DS_MAP_SENTINEL_SLOT) if (map->hash_to_slot[result] == DQN_DS_MAP_SENTINEL_SLOT)
return result; return result;
Dqn_DSMapSlot<T> *slot = map->slots + map->hash_to_slot[result]; Dqn_DSMapSlot<T> *slot = map->slots + map->hash_to_slot[result];
if (slot->key.type == Dqn_DSMapKeyType_Invalid || (slot->hash == hash && slot->key == key)) if (slot->key.type == Dqn_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key))
return result; return result;
result = (result + 1) & (map->size - 1); result = (result + 1) & (map->size - 1);
@ -3422,11 +3441,11 @@ uint32_t Dqn_DSMap_HashToSlotIndex(Dqn_DSMap<T> const *map, Dqn_DSMapKey key, ui
} }
template <typename T> template <typename T>
Dqn_DSMapSlot<T> *Dqn_DSMap_FindSlot(Dqn_DSMap<T> const *map, Dqn_DSMapKey key, uint32_t hash) Dqn_DSMapSlot<T> *Dqn_DSMap_FindSlot(Dqn_DSMap<T> const *map, Dqn_DSMapKey key)
{ {
Dqn_DSMapSlot<T> const *result = nullptr; Dqn_DSMapSlot<T> const *result = nullptr;
if (Dqn_DSMap_IsValid(map)) { if (Dqn_DSMap_IsValid(map)) {
uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key, hash); uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key);
if (map->hash_to_slot[index] != DQN_DS_MAP_SENTINEL_SLOT) if (map->hash_to_slot[index] != DQN_DS_MAP_SENTINEL_SLOT)
result = map->slots + map->hash_to_slot[index]; result = map->slots + map->hash_to_slot[index];
} }
@ -3434,40 +3453,71 @@ Dqn_DSMapSlot<T> *Dqn_DSMap_FindSlot(Dqn_DSMap<T> const *map, Dqn_DSMapKey key,
} }
template <typename T> template <typename T>
Dqn_DSMapSlot<T> *Dqn_DSMap_MakeSlot(Dqn_DSMap<T> *map, Dqn_DSMapKey key, uint32_t hash) Dqn_DSMapSlot<T> *Dqn_DSMap_MakeSlot(Dqn_DSMap<T> *map, Dqn_DSMapKey key, bool *found)
{ {
Dqn_DSMapSlot<T> *result = nullptr; Dqn_DSMapSlot<T> *result = nullptr;
if (Dqn_DSMap_IsValid(map)) { if (Dqn_DSMap_IsValid(map)) {
uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key, hash); uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key);
if (map->hash_to_slot[index] == DQN_DS_MAP_SENTINEL_SLOT) if (map->hash_to_slot[index] == DQN_DS_MAP_SENTINEL_SLOT) {
// NOTE: Create the slot
map->hash_to_slot[index] = map->occupied++; map->hash_to_slot[index] = map->occupied++;
// NOTE: Check if resize is required
bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3);
if (map_is_75pct_full) {
if (!Dqn_DSMap_Resize(map, map->size * 2))
return result;
result = Dqn_DSMap_MakeSlot(map, key, nullptr /*found*/);
} else {
result = map->slots + map->hash_to_slot[index]; result = map->slots + map->hash_to_slot[index];
} }
// NOTE: Update the slot
result->key = key;
if (found)
*found = false;
} else {
result = map->slots + map->hash_to_slot[index];
if (found)
*found = true;
}
}
return result;
}
template <typename T>
Dqn_DSMapSlot<T> *Dqn_DSMap_SetSlot(Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value, bool *found)
{
Dqn_DSMapSlot<T> *result = nullptr;
if (!Dqn_DSMap_IsValid(map))
return result;
result = Dqn_DSMap_MakeSlot(map, key, found);
result->value = value;
return result; return result;
} }
template <typename T> template <typename T>
T *Dqn_DSMap_Find(Dqn_DSMap<T> const *map, Dqn_DSMapKey key) T *Dqn_DSMap_Find(Dqn_DSMap<T> const *map, Dqn_DSMapKey key)
{ {
T const *result = nullptr; Dqn_DSMapSlot<T> const *slot = Dqn_DSMap_FindSlot(map, key);
if (Dqn_DSMap_IsValid(map)) { T const *result = slot ? &slot->value : nullptr;
uint32_t hash = Dqn_DSMap_Hash(map, key);
Dqn_DSMapSlot<T> *slot = Dqn_DSMap_FindSlot(map, key, hash);
result = slot ? &slot->value : nullptr;
}
return DQN_CAST(T *)result; return DQN_CAST(T *)result;
} }
template <typename T> template <typename T>
T *Dqn_DSMap_Make(Dqn_DSMap<T> *map, Dqn_DSMapKey key) T *Dqn_DSMap_Make(Dqn_DSMap<T> *map, Dqn_DSMapKey key, bool *found)
{ {
T const *result = nullptr; Dqn_DSMapSlot<T> *slot = Dqn_DSMap_MakeSlot(map, key, found);
if (Dqn_DSMap_IsValid(map)) { T *result = &slot->value;
uint32_t hash = Dqn_DSMap_Hash(map, key); return result;
Dqn_DSMapSlot<T> *slot = Dqn_DSMap_MakeSlot(map, key, hash);
result = &slot->value;
} }
return DQN_CAST(T *)result;
template <typename T>
T *Dqn_DSMap_Set(Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value, bool *found)
{
Dqn_DSMapSlot<T> *result = Dqn_DSMap_SetSlot(map, key, value, found);
return &result->value;
} }
template <typename T> template <typename T>
@ -3484,8 +3534,7 @@ bool Dqn_DSMap_Resize(Dqn_DSMap<T> *map, uint32_t size)
for (uint32_t old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) { for (uint32_t old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) {
Dqn_DSMapSlot<T> *old_slot = map->slots + old_index; Dqn_DSMapSlot<T> *old_slot = map->slots + old_index;
if (old_slot->key.type != Dqn_DSMapKeyType_Invalid) { if (old_slot->key.type != Dqn_DSMapKeyType_Invalid) {
Dqn_DSMapSlot<T> *slot = Dqn_DSMap_MakeSlot(&new_map, old_slot->key, old_slot->hash); Dqn_DSMap_Set(&new_map, old_slot->key, old_slot->value, nullptr /*found*/);
*slot = *old_slot;
} }
} }
@ -3495,32 +3544,6 @@ bool Dqn_DSMap_Resize(Dqn_DSMap<T> *map, uint32_t size)
return true; return true;
} }
template <typename T>
Dqn_DSMapSlot<T> *Dqn_DSMap_Set(Dqn_DSMap<T> *map, Dqn_DSMapKey key, T const &value)
{
Dqn_DSMapSlot<T> *result = nullptr;
if (!Dqn_DSMap_IsValid(map))
return result;
uint32_t hash = Dqn_DSMap_Hash(map, key);
result = Dqn_DSMap_MakeSlot(map, key, hash);
if (result->key.type != Dqn_DSMapKeyType_Invalid) { // NOTE: Exists already, just update
result->value = value;
return result;
}
bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3);
if (map_is_75pct_full) {
if (!Dqn_DSMap_Resize(map, map->size * 2))
return result;
result = Dqn_DSMap_MakeSlot(map, key, hash);
}
result->key = key;
result->value = value;
result->hash = hash;
return result;
}
template <typename T> template <typename T>
bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key) bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
@ -3528,8 +3551,7 @@ bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
if (!Dqn_DSMap_IsValid(map)) if (!Dqn_DSMap_IsValid(map))
return false; return false;
uint32_t hash = Dqn_DSMap_Hash(map, key); uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key);
uint32_t index = Dqn_DSMap_HashToSlotIndex(map, key, hash);
uint32_t slot_index = map->hash_to_slot[index]; uint32_t slot_index = map->hash_to_slot[index];
if (slot_index == DQN_DS_MAP_SENTINEL_SLOT) if (slot_index == DQN_DS_MAP_SENTINEL_SLOT)
return false; return false;
@ -3547,7 +3569,7 @@ bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
break; break;
Dqn_DSMapSlot<T> *probe = map->slots + map->hash_to_slot[probe_index]; Dqn_DSMapSlot<T> *probe = map->slots + map->hash_to_slot[probe_index];
uint32_t new_index = probe->hash & (map->size - 1); uint32_t new_index = probe->key.hash & (map->size - 1);
if (index <= probe_index) { if (index <= probe_index) {
if (index < new_index && new_index <= probe_index) if (index < new_index && new_index <= probe_index)
continue; continue;
@ -3559,7 +3581,7 @@ bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
map->hash_to_slot[index] = map->hash_to_slot[probe_index]; map->hash_to_slot[index] = map->hash_to_slot[probe_index];
map->hash_to_slot[probe_index] = DQN_DS_MAP_SENTINEL_SLOT; map->hash_to_slot[probe_index] = DQN_DS_MAP_SENTINEL_SLOT;
index = probe_index; index = probe_index;
DQN_ASSERT(Dqn_DSMap_FindSlot(map, probe->key, probe->hash) == probe); DQN_ASSERT(Dqn_DSMap_FindSlot(map, probe->key) == probe);
} }
// NOTE: We have erased a slot from the hash table, this leaves a gap // NOTE: We have erased a slot from the hash table, this leaves a gap
@ -3573,7 +3595,7 @@ bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
map->slots[slot_index] = *last_slot; map->slots[slot_index] = *last_slot;
// NOTE: Update the hash-to-slot mapping for the value that was copied in // NOTE: Update the hash-to-slot mapping for the value that was copied in
uint32_t hash_to_slot_index = Dqn_DSMap_HashToSlotIndex(map, last_slot->key, last_slot->hash); uint32_t hash_to_slot_index = Dqn_DSMap_HashToSlotIndex(map, last_slot->key);
map->hash_to_slot[hash_to_slot_index] = slot_index; map->hash_to_slot[hash_to_slot_index] = slot_index;
*last_slot = {}; // TODO: Optional? *last_slot = {}; // TODO: Optional?
} }
@ -3586,6 +3608,48 @@ bool Dqn_DSMap_Erase(Dqn_DSMap<T> *map, Dqn_DSMapKey key)
return true; return true;
} }
template <typename T>
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyBuffer(Dqn_DSMap<T> const *map, void const *data, uint32_t size)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_Buffer;
result.payload.buffer.data = data;
result.payload.buffer.size = size;
result.hash = Dqn_DSMap_Hash(map, result);
return result;
}
template <typename T>
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64(Dqn_DSMap<T> const *map, uint64_t u64)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_U64;
result.payload.u64 = u64;
result.hash = Dqn_DSMap_Hash(map, &result);
return result;
}
template <typename T>
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyString8(Dqn_DSMap<T> const *map, Dqn_String8 string)
{
DQN_ASSERT(string.size > 0 && string.size <= UINT32_MAX);
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_Buffer;
result.payload.buffer.data = string.data;
result.payload.buffer.size = DQN_CAST(uint32_t)string.size;
result.hash = Dqn_DSMap_Hash(map, &result);
return result;
}
template <typename T>
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyString8Copy(Dqn_DSMap<T> const *map, Dqn_Allocator allocator, Dqn_String8 string)
{
Dqn_String8 copy = Dqn_String8_Copy(allocator, string);
Dqn_DSMapKey result = Dqn_DSMap_KeyString8(map, copy);
return result;
}
#endif // !defined(DQN_NO_DSMAP) #endif // !defined(DQN_NO_DSMAP)
#if !defined(DQN_NO_FSTRING8) #if !defined(DQN_NO_FSTRING8)
@ -5677,13 +5741,22 @@ DQN_API void Dqn_Print_StdLnF(Dqn_PrintStd std_handle, char const *fmt, ...)
// ---------------------------------+-----------------------------+--------------------------------- // ---------------------------------+-----------------------------+---------------------------------
// [SECT-DMAP] Dqn_DSMap | DQN_NO_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair // [SECT-DMAP] Dqn_DSMap | DQN_NO_DSMAP | Hashtable, 70% max load, PoT size, linear probe, chain repair
// ---------------------------------+-----------------------------+--------------------------------- // ---------------------------------+-----------------------------+---------------------------------
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash(uint64_t u64)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_U64NoHash;
result.payload.u64 = u64;
result.hash = DQN_CAST(uint32_t)u64;
return result;
}
DQN_API bool Dqn_DSMap_KeyEquals(Dqn_DSMapKey lhs, Dqn_DSMapKey rhs) DQN_API bool Dqn_DSMap_KeyEquals(Dqn_DSMapKey lhs, Dqn_DSMapKey rhs)
{ {
bool result = false; bool result = false;
if (lhs.type == rhs.type) { if (lhs.type == rhs.type && lhs.hash == rhs.hash) {
switch (lhs.type) { switch (lhs.type) {
case Dqn_DSMapKeyType_Invalid: result = true; break; case Dqn_DSMapKeyType_Invalid: result = true; break;
case Dqn_DSMapKeyType_U64NoHash: /*FALLTHRU*/ case Dqn_DSMapKeyType_U64NoHash: result = true; break;
case Dqn_DSMapKeyType_U64: result = lhs.payload.u64 == rhs.payload.u64; break; case Dqn_DSMapKeyType_U64: result = lhs.payload.u64 == rhs.payload.u64; break;
case Dqn_DSMapKeyType_Buffer: result = lhs.payload.buffer.size == rhs.payload.buffer.size && case Dqn_DSMapKeyType_Buffer: result = lhs.payload.buffer.size == rhs.payload.buffer.size &&
memcmp(lhs.payload.buffer.data, rhs.payload.buffer.data, lhs.payload.buffer.size) == 0; break; memcmp(lhs.payload.buffer.data, rhs.payload.buffer.data, lhs.payload.buffer.size) == 0; break;
@ -5698,30 +5771,6 @@ DQN_API bool operator==(Dqn_DSMapKey lhs, Dqn_DSMapKey rhs)
return result; return result;
} }
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyBuffer(void const *data, uint32_t size)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_Buffer;
result.payload.buffer.data = data;
result.payload.buffer.size = size;
return result;
}
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64(uint64_t u64)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_U64;
result.payload.u64 = u64;
return result;
}
DQN_API Dqn_DSMapKey Dqn_DSMap_KeyU64NoHash(uint64_t u64)
{
Dqn_DSMapKey result = {};
result.type = Dqn_DSMapKeyType_U64NoHash;
result.payload.u64 = u64;
return result;
}
#endif // !defined(DQN_NO_DSMAP) #endif // !defined(DQN_NO_DSMAP)
// ---------------------------------+-----------------------------+--------------------------------- // ---------------------------------+-----------------------------+---------------------------------

View File

@ -243,18 +243,18 @@ Dqn_Tester Dqn_Test_DSMap()
DQN_DEFER { Dqn_DSMap_Deinit(&map); }; DQN_DEFER { Dqn_DSMap_Deinit(&map); };
DQN_TESTER_TEST("Find non-existent value") { DQN_TESTER_TEST("Find non-existent value") {
uint64_t *value = Dqn_DSMap_Find(&map, Dqn_DSMap_KeyCStringLit("Foo")); uint64_t *value = Dqn_DSMap_Find(&map, Dqn_DSMap_KeyCStringLit(&map, "Foo"));
DQN_ASSERT(!value); DQN_ASSERT(!value);
DQN_ASSERT(map.size == MAP_SIZE); DQN_ASSERT(map.size == MAP_SIZE);
DQN_ASSERT(map.initial_size == MAP_SIZE); DQN_ASSERT(map.initial_size == MAP_SIZE);
DQN_ASSERT(map.occupied == 1 /*Sentinel*/); DQN_ASSERT(map.occupied == 1 /*Sentinel*/);
} }
Dqn_DSMapKey key = Dqn_DSMap_KeyCStringLit("Bar"); Dqn_DSMapKey key = Dqn_DSMap_KeyCStringLit(&map, "Bar");
DQN_TESTER_TEST("Insert value and lookup") { DQN_TESTER_TEST("Insert value and lookup") {
uint64_t desired_value = 0xF00BAA; uint64_t desired_value = 0xF00BAA;
Dqn_DSMapSlot<uint64_t> *slot = Dqn_DSMap_Set(&map, key, desired_value); uint64_t *slot_value = Dqn_DSMap_Set(&map, key, desired_value, nullptr /*found*/);
DQN_ASSERT(slot); DQN_ASSERT(slot_value);
DQN_ASSERT(map.size == MAP_SIZE); DQN_ASSERT(map.size == MAP_SIZE);
DQN_ASSERT(map.initial_size == MAP_SIZE); DQN_ASSERT(map.initial_size == MAP_SIZE);
DQN_ASSERT(map.occupied == 2); DQN_ASSERT(map.occupied == 2);
@ -272,24 +272,37 @@ Dqn_Tester Dqn_Test_DSMap()
} }
} }
{ enum DSMapTestType { DSMapTestType_Set, DSMapTestType_MakeSlot, DSMapTestType_Count };
for (int test_type = 0; test_type < DSMapTestType_Count; test_type++) {
Dqn_String8 prefix = {};
switch (test_type) {
case DSMapTestType_Set: prefix = DQN_STRING8("Set"); break;
case DSMapTestType_MakeSlot: prefix = DQN_STRING8("Make slot"); break;
}
DQN_ARENA_TEMP_MEMORY_SCOPE(scratch.arena); DQN_ARENA_TEMP_MEMORY_SCOPE(scratch.arena);
uint32_t const MAP_SIZE = 64; uint32_t const MAP_SIZE = 64;
Dqn_DSMap<uint64_t> map = Dqn_DSMap_Init<uint64_t>(MAP_SIZE); Dqn_DSMap<uint64_t> map = Dqn_DSMap_Init<uint64_t>(MAP_SIZE);
DQN_DEFER { Dqn_DSMap_Deinit(&map); }; DQN_DEFER { Dqn_DSMap_Deinit(&map); };
DQN_TESTER_TEST("Test growing") { DQN_TESTER_TEST("%.*s: Test growing", DQN_STRING_FMT(prefix)) {
uint64_t map_start_size = map.size; uint64_t map_start_size = map.size;
uint64_t value = 0; uint64_t value = 0;
uint64_t grow_threshold = map_start_size * 3 / 4; uint64_t grow_threshold = map_start_size * 3 / 4;
for (; map.occupied != grow_threshold; value++) { for (; map.occupied != grow_threshold; value++) {
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy));
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));
DQN_ASSERT(!Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key))); DQN_ASSERT(!Dqn_DSMap_FindSlot<uint64_t>(&map, key));
Dqn_DSMap_Set(&map, key, value); bool found = false;
if (test_type == DSMapTestType_Set) {
Dqn_DSMap_Set(&map, key, value, &found);
} else {
Dqn_DSMap_MakeSlot(&map, key, &found);
}
DQN_ASSERT(!found);
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key))); DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key));
} }
DQN_ASSERT(map.initial_size == MAP_SIZE); DQN_ASSERT(map.initial_size == MAP_SIZE);
DQN_ASSERT(map.size == map_start_size); DQN_ASSERT(map.size == map_start_size);
@ -297,58 +310,70 @@ Dqn_Tester Dqn_Test_DSMap()
{ // NOTE: One more item should cause the table to grow by 2x { // NOTE: One more item should cause the table to grow by 2x
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy));
Dqn_DSMap_Set(&map, key, value++); bool found = false;
if (test_type == DSMapTestType_Set) {
Dqn_DSMap_Set(&map, key, value, &found);
} else {
Dqn_DSMap_MakeSlot(&map, key, &found);
}
value++;
DQN_ASSERT(!found);
DQN_ASSERT(map.size == map_start_size * 2); DQN_ASSERT(map.size == map_start_size * 2);
DQN_ASSERT(map.initial_size == MAP_SIZE); DQN_ASSERT(map.initial_size == MAP_SIZE);
DQN_ASSERT(map.occupied == 1 /*Sentinel*/ + value); DQN_ASSERT(map.occupied == 1 /*Sentinel*/ + value);
} }
} }
DQN_TESTER_TEST("Check the sentinel is present") { DQN_TESTER_TEST("%.*s: Check the sentinel is present", DQN_STRING_FMT(prefix)) {
Dqn_DSMapSlot<uint64_t> NIL_SLOT = {}; Dqn_DSMapSlot<uint64_t> NIL_SLOT = {};
Dqn_DSMapSlot<uint64_t> sentinel = map.slots[DQN_DS_MAP_SENTINEL_SLOT]; Dqn_DSMapSlot<uint64_t> sentinel = map.slots[DQN_DS_MAP_SENTINEL_SLOT];
DQN_ASSERT(DQN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0); DQN_ASSERT(DQN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0);
} }
DQN_TESTER_TEST("Recheck all the hash tables values after growing") { DQN_TESTER_TEST("%.*s: Recheck all the hash tables values after growing", DQN_STRING_FMT(prefix)) {
for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) { for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) {
Dqn_DSMapSlot<uint64_t> const *slot = map.slots + index; Dqn_DSMapSlot<uint64_t> const *slot = map.slots + index;
// NOTE: Validate each slot value // NOTE: Validate each slot value
uint64_t value_test = index - 1; uint64_t value_test = index - 1;
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&value_test, sizeof(value_test)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, &value_test, sizeof(value_test));
DQN_ASSERT(Dqn_DSMap_KeyEquals(slot->key, key)); DQN_ASSERT(Dqn_DSMap_KeyEquals(slot->key, key));
if (test_type == DSMapTestType_Set) {
DQN_ASSERT(slot->value == value_test); DQN_ASSERT(slot->value == value_test);
DQN_ASSERT(slot->hash == Dqn_DSMap_Hash(&map, slot->key)); } else {
DQN_ASSERT(slot->value == 0); // NOTE: Make slot does not set the key so should be 0
}
DQN_ASSERT(slot->key.hash == Dqn_DSMap_Hash(&map, slot->key));
// NOTE: Check the reverse lookup is correct // NOTE: Check the reverse lookup is correct
Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot<uint64_t>(&map, slot->key, slot->hash); Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot(&map, slot->key);
DQN_ASSERT(slot == check); DQN_ASSERT(slot == check);
} }
} }
DQN_TESTER_TEST("Test shrinking") { DQN_TESTER_TEST("%.*s: Test shrinking", DQN_STRING_FMT(prefix)) {
uint64_t start_map_size = map.size; uint64_t start_map_size = map.size;
uint64_t start_map_occupied = map.occupied; uint64_t start_map_occupied = map.occupied;
uint64_t value = 0; uint64_t value = 0;
uint64_t shrink_threshold = map.size * 1 / 4; uint64_t shrink_threshold = map.size * 1 / 4;
for (; map.occupied != shrink_threshold; value++) { for (; map.occupied != shrink_threshold; value++) {
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy));
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key))); DQN_ASSERT(Dqn_DSMap_FindSlot<uint64_t>(&map, key));
Dqn_DSMap_Erase(&map, key); Dqn_DSMap_Erase(&map, key);
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));
DQN_ASSERT(!Dqn_DSMap_FindSlot<uint64_t>(&map, key, Dqn_DSMap_Hash(&map, key))); DQN_ASSERT(!Dqn_DSMap_FindSlot(&map, key));
} }
DQN_ASSERT(map.size == start_map_size); DQN_ASSERT(map.size == start_map_size);
DQN_ASSERT(map.occupied == start_map_occupied - value); DQN_ASSERT(map.occupied == start_map_occupied - value);
{ // NOTE: One more item should cause the table to grow by 2x { // NOTE: One more item should cause the table to grow by 2x
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy));
Dqn_DSMap_Erase(&map, key); Dqn_DSMap_Erase(&map, key);
value++; value++;
@ -367,23 +392,27 @@ Dqn_Tester Dqn_Test_DSMap()
// NOTE: Generate the key // NOTE: Generate the key
uint64_t value_test = value + (index - 1); uint64_t value_test = value + (index - 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)&value_test, sizeof(value_test)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)&value_test, sizeof(value_test));
// NOTE: Validate each slot value // NOTE: Validate each slot value
Dqn_DSMapSlot<uint64_t> const *slot = Dqn_DSMap_FindSlot(&map, key, Dqn_DSMap_Hash(&map, key)); Dqn_DSMapSlot<uint64_t> const *slot = Dqn_DSMap_FindSlot(&map, key);
DQN_ASSERT(slot); DQN_ASSERT(slot);
DQN_ASSERT(slot->key == key); DQN_ASSERT(slot->key == key);
if (test_type == DSMapTestType_Set) {
DQN_ASSERT(slot->value == value_test); DQN_ASSERT(slot->value == value_test);
DQN_ASSERT(slot->hash == Dqn_DSMap_Hash(&map, slot->key)); } else {
DQN_ASSERT(slot->value == 0); // NOTE: Make slot does not set the key so should be 0
}
DQN_ASSERT(slot->key.hash == Dqn_DSMap_Hash(&map, slot->key));
// NOTE: Check the reverse lookup is correct // NOTE: Check the reverse lookup is correct
Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot<uint64_t>(&map, slot->key, slot->hash); Dqn_DSMapSlot<uint64_t> const *check = Dqn_DSMap_FindSlot(&map, slot->key);
DQN_ASSERT(slot == check); DQN_ASSERT(slot == check);
} }
for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table
uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1); uint64_t *val_copy = Dqn_Arena_Copy(scratch.arena, uint64_t, &value, 1);
Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer((char *)val_copy, sizeof(*val_copy)); Dqn_DSMapKey key = Dqn_DSMap_KeyBuffer(&map, (char *)val_copy, sizeof(*val_copy));
DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(Dqn_DSMap_Find<uint64_t>(&map, key));
Dqn_DSMap_Erase(&map, key); Dqn_DSMap_Erase(&map, key);
DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key)); DQN_ASSERT(!Dqn_DSMap_Find<uint64_t>(&map, key));