Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Public/UbaHashMap.h
2025-05-18 13:04:45 +08:00

334 lines
8.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UbaMemory.h"
#include "UbaPlatform.h"
namespace uba
{
// Hash map that contains three parts
// 1. Lookup table indexed with hashed key. Points to index in entry table
// 2. Entry table. Contains actual key and an index to next entry. Entry table order is stable during rehash
// 3. Value table. Matches entry table ordering.
template<typename Key, typename Value, bool AllowGrow = false>
struct HashMap
{
struct Entry
{
Key key;
u32 next; // If this is 0 it means that it is unused, ~0u means it is the end of a chain, otherwise offset into entry array
};
~HashMap()
{
if (m_ownsMemory)
delete m_memory;
}
void Init(MemoryBlock& mem, u64 reserveCount, const tchar* hint = TC(""))
{
reserveCount = Max(reserveCount + 1, 4ull);
m_memory = &mem;
UBA_ASSERT(!AllowGrow);
InternalInit(reserveCount, RoundUpPow2(reserveCount), hint);
}
void Init(u64 reserveCount = 4, const tchar* hint = TC(""))
{
reserveCount = Max(reserveCount + 1, 4ull);
u64 lookupCount = RoundUpPow2(reserveCount);
u64 lookupSize = lookupCount*sizeof(u32);
u64 entriesSize = sizeof(Entry)*(reserveCount+1);
u64 valuesSize = sizeof(Value)*(reserveCount+1);
m_memory = new MemoryBlock();
m_memory->Init(lookupSize + entriesSize + valuesSize);
m_ownsMemory = true;
InternalInit(reserveCount, lookupCount, hint);
}
bool IsInitialized()
{
return m_memory != nullptr;
}
Value& Insert(const Key& key)
{
bool added = false;
return Insert(key, added);
}
Value& Insert(const Key& key, bool& outAdded)
{
u32 index = InsertIndex(key, outAdded);
return m_values[index];
}
u32 InsertIndex(const Key& key, bool& outAdded)
{
UBA_ASSERT(m_nextAvailableEntry < m_reserveCount || AllowGrow);
if constexpr (AllowGrow)
if (m_nextAvailableEntry == m_reserveCount)
{
// Create a new map, populate and swap. Note that we need the entries and values to be at the same index as before
HashMap newMap;
newMap.Init(GetNextReserveCount(m_reserveCount));
for (u32 i=1, e=m_nextAvailableEntry; i!=e; ++i)
newMap.Insert(m_entries[i].key) = m_values[i];
Swap(newMap);
}
u32 index = std::hash<Key>()(key) & m_mask;
u32 entryIndex = m_lookup[index];
if (entryIndex == 0)
{
entryIndex = m_nextAvailableEntry++;
m_lookup[index] = entryIndex;
Entry& entry = m_entries[entryIndex];
entry.key = key;
entry.next = ~0u;
outAdded = true;
return entryIndex;
}
while (true)
{
Entry& entry = m_entries[entryIndex];
if (entry.key == key)
return entryIndex;
if (entry.next != ~0u)
{
entryIndex = entry.next;
//static u32 counter = 0;
//printf("MULTIPLE: %u\n", counter++);
continue;
}
u32 newEntryIndex = entry.next = m_nextAvailableEntry++;
Entry& newEntry = m_entries[newEntryIndex];
newEntry.key = key;
newEntry.next = ~0u;
outAdded = true;
return newEntryIndex;
}
}
Value* Find(const Key& key) const
{
u32 index = std::hash<Key>()(key) & m_mask;
u32 entryIndex = m_lookup[index];
if (entryIndex == 0)
return nullptr;
Entry* entry = m_entries + entryIndex;
while (true)
{
if (entry->key == key)
return m_values + entryIndex;
entryIndex = entry->next;
if (entryIndex == ~0u)
return nullptr;
entry = m_entries + entryIndex;
}
}
const Key* GetKey(Value* value)
{
u32 pos = u32(value - m_values);
auto& entry = m_entries[pos];
if (entry.next == 0)
return nullptr;
return &entry.key;
}
Value& GetValueFromIndex(u32 index)
{
return m_values[index];
}
u32 Size()
{
return m_nextAvailableEntry - 1;
}
void Erase(const Key& key)
{
UBA_ASSERT(!AllowGrow);
u32 index = std::hash<Key>()(key) & m_mask;
u32 entryIndex = m_lookup[index];
if (entryIndex == 0)
return;
Entry* entry = m_entries + entryIndex;
u32* prevNext = nullptr;
while (true)
{
if (entry->key == key)
{
if (!prevNext)
{
if (entry->next == ~0u)
m_lookup[index] = 0;
else
m_lookup[index] = entry->next;
}
else
*prevNext = entry->next;
entry->next = 0;
// TODO: We could swap with last and reduce size
return;
}
entryIndex = entry->next;
if (entryIndex == ~0u)
return;
prevNext = &entry->next;
entry = m_entries + entryIndex;
}
}
Value* ValuesBegin() const { return m_values + 1; }
Value* ValuesEnd() const { return m_values + m_nextAvailableEntry; }
void Swap(HashMap& o)
{
u8 temp[sizeof(HashMap)];
memcpy(temp, this, sizeof(HashMap));
memcpy(this, &o, sizeof(HashMap));
memcpy(&o, temp, sizeof(HashMap));
}
u64 GetMemoryNeeded(u64 reserveCount)
{
reserveCount = Max(reserveCount + 1, 4ull);
u64 lookupCount = RoundUpPow2(reserveCount);
return lookupCount*sizeof(u32) + reserveCount*sizeof(Entry) + reserveCount*sizeof(Value) + 256; // Add some padding for alignment between tables
}
private:
void InternalInit(u64 reserveCount, u64 lookupCount, const tchar* hint)
{
m_mask = u32(lookupCount - 1);
m_nextAvailableEntry = 1;
m_reserveCount = u32(reserveCount);
// This code relies on that allocated memory is zeroed out
m_lookup = (u32*)m_memory->AllocateNoLock(lookupCount*sizeof(u32), 1, hint);
m_entries = (Entry*)m_memory->AllocateNoLock(m_reserveCount*sizeof(Entry), alignof(Entry), hint);
m_values = (Value*)m_memory->AllocateNoLock(m_reserveCount*sizeof(Value), alignof(Value), hint);
}
u64 GetNextReserveCount(u64 reserveCount)
{
if (reserveCount < 50'000)
return reserveCount*2;
return reserveCount + 50'000;
}
MemoryBlock* m_memory = nullptr;
Entry* m_entries = nullptr;
Value* m_values = nullptr;
u32* m_lookup = nullptr;
u32 m_mask = 0;
u32 m_nextAvailableEntry = 0;
u32 m_reserveCount = 0;
bool m_ownsMemory = false;
};
// Hash map that contains two parts
// 1. Lookup table indexed with hashed key. Points to index in entry table
// 2. Entry table. Contains actual key and value and an index to next entry. Entry table order is stable during rehash
template<typename Key, typename Value>
struct HashMap2
{
struct Entry
{
Key key;
Value value;
u32 next; // If this is 0 it means that it is unused, ~0u means it is the end of a chain, otherwise offset into entry array
};
void Init(MemoryBlock& memory, u64 maxSize, const tchar* hint = TC(""))
{
u32 v = RoundUpPow2(maxSize);
u32 lookupSize = v;// << 1u;
mask = lookupSize - 1;
nextAvailableEntry = 1;
// This code relies on that AllocateNoLockd memory is zeroed out
lookup = (u32*)memory.AllocateNoLock(lookupSize*sizeof(u32), 1, hint);
entries = (Entry*)memory.AllocateNoLock((maxSize+1)*sizeof(Entry), alignof(Entry), hint);
}
Value& Insert(const Key& key)
{
u32 index = std::hash<Key>()(key) & mask;
u32 entryIndex = lookup[index];
if (entryIndex == 0)
{
entryIndex = nextAvailableEntry++;
lookup[index] = entryIndex;
Entry& entry = entries[entryIndex];
entry.key = key;
entry.next = ~0u;
return entry.value;
}
while (true)
{
Entry& entry = entries[entryIndex];
if (entry.key == key)
return entry.value;
if (entry.next != ~0u)
{
entryIndex = entry.next;
//static u32 counter = 0;
//printf("MULTIPLE: %u\n", counter++);
continue;
}
u32 newEntryIndex = entry.next = nextAvailableEntry++;
Entry& newEntry = entries[newEntryIndex];
newEntry.key = key;
newEntry.next = ~0u;
return newEntry.value;
}
}
Value* Find(const Key& key) const
{
u32 index = std::hash<Key>()(key) & mask;
u32 entryIndex = lookup[index];
if (entryIndex == 0)
return nullptr;
Entry* entry = entries + entryIndex;
while (true)
{
if (entry->key == key)
return &entry->value;
entryIndex = entry->next;
if (entryIndex == ~0u)
return nullptr;
entry = entries + entryIndex;
}
}
u32 Size()
{
return nextAvailableEntry - 1;
}
Entry* entries;
u32* lookup;
u32 mask;
u32 nextAvailableEntry;
};
}