Added HandleHashMap.

This commit is contained in:
Branimir Karadžić
2016-09-05 20:55:30 -07:00
parent ef5a01f0ea
commit 5fe975a39b
26 changed files with 752 additions and 81 deletions

View File

@@ -8,9 +8,11 @@
#include "bx.h"
#include "allocator.h"
#include "uint32_t.h"
namespace bx
{
///
class HandleAlloc
{
public:
@@ -127,6 +129,7 @@ namespace bx
BX_FREE(_allocator, _handleAlloc);
}
///
template <uint16_t MaxHandlesT>
class HandleAllocT : public HandleAlloc
{
@@ -144,6 +147,7 @@ namespace bx
uint16_t m_padding[2*MaxHandlesT];
};
///
template <uint16_t MaxHandlesT>
class HandleListT
{
@@ -327,6 +331,7 @@ namespace bx
Link m_links[MaxHandlesT];
};
///
template <uint16_t MaxHandlesT>
class HandleAllocLruT
{
@@ -422,6 +427,293 @@ namespace bx
HandleAllocT<MaxHandlesT> m_alloc;
};
///
template <uint32_t MaxCapacityT, typename KeyT = uint32_t>
class HandleHashMapT
{
public:
static const uint16_t invalid = UINT16_MAX;
HandleHashMapT()
: m_maxCapacity(MaxCapacityT)
{
reset();
}
~HandleHashMapT()
{
}
bool insert(KeyT _key, uint16_t _handle)
{
if (invalid == _handle)
{
return false;
}
const KeyT hash = mix(_key);
const uint32_t first = hash % MaxCapacityT;
uint32_t idx = first;
do
{
if (m_handle[idx] == invalid)
{
m_key[idx] = _key;
m_handle[idx] = _handle;
++m_numElements;
return true;
}
if (m_key[idx] == _key)
{
return false;
}
idx = (idx + 1) % MaxCapacityT;
} while (idx != first);
return false;
}
void removeByKey(KeyT _key)
{
uint32_t idx = findIndex(_key);
if (UINT32_MAX != idx)
{
m_handle[idx] = invalid;
--m_numElements;
}
}
void removeByHandle(uint16_t _handle)
{
if (invalid != _handle)
{
for (uint32_t idx = 0; idx < MaxCapacityT; ++idx)
{
if (m_handle[idx] == _handle)
{
m_handle[idx] = invalid;
--m_numElements;
}
}
}
}
uint16_t find(KeyT _key) const
{
uint32_t idx = findIndex(_key);
if (UINT32_MAX != idx)
{
return m_handle[idx];
}
return invalid;
}
void reset()
{
memset(m_handle, 0xff, sizeof(m_handle) );
m_numElements = 0;
}
uint32_t getNumElements() const
{
return m_numElements;
}
uint32_t getMaxCapacity() const
{
return m_maxCapacity;
}
struct Iterator
{
uint16_t handle;
private:
friend class HandleHashMapT<MaxCapacityT, KeyT>;
uint32_t pos;
uint32_t num;
};
Iterator first() const
{
Iterator it;
it.handle = invalid;
it.pos = 0;
it.num = m_numElements;
if (0 == it.num)
{
return it;
}
++it.num;
next(it);
return it;
}
bool next(Iterator& _it) const
{
if (0 == _it.num)
{
return false;
}
for (
;_it.pos < MaxCapacityT && invalid == m_handle[_it.pos]
; ++_it.pos
);
_it.handle = m_handle[_it.pos];
++_it.pos;
--_it.num;
return true;
}
private:
uint32_t findIndex(KeyT _key) const
{
const KeyT hash = mix(_key);
const uint32_t first = hash % MaxCapacityT;
uint32_t idx = first;
do
{
if (m_handle[idx] == invalid)
{
return UINT32_MAX;
}
if (m_key[idx] == _key)
{
return idx;
}
idx = (idx + 1) % MaxCapacityT;
} while (idx != first);
return UINT32_MAX;
}
uint32_t mix(uint32_t _x) const
{
const uint32_t tmp0 = uint32_mul(_x, UINT32_C(2246822519) );
const uint32_t tmp1 = uint32_rol(tmp0, 13);
const uint32_t result = uint32_mul(tmp1, UINT32_C(2654435761) );
return result;
}
uint64_t mix(uint64_t _x) const
{
const uint64_t tmp0 = uint64_mul(_x, UINT64_C(14029467366897019727) );
const uint64_t tmp1 = uint64_rol(tmp0, 31);
const uint64_t result = uint64_mul(tmp1, UINT64_C(11400714785074694791) );
return result;
}
uint32_t m_maxCapacity;
uint32_t m_numElements;
KeyT m_key[MaxCapacityT];
uint16_t m_handle[MaxCapacityT];
};
///
template <uint16_t MaxHandlesT, typename KeyT = uint32_t>
class HandleHashMapAllocT
{
public:
static const uint16_t invalid = UINT16_MAX;
HandleHashMapAllocT()
{
reset();
}
~HandleHashMapAllocT()
{
}
uint16_t alloc(KeyT _key)
{
uint16_t handle = m_alloc.alloc();
if (invalid == handle)
{
return invalid;
}
bool ok = m_table.insert(_key, handle);
if (!ok)
{
m_alloc.free(handle);
return invalid;
}
return handle;
}
void free(KeyT _key)
{
uint16_t handle = m_table.find(_key);
if (invalid == handle)
{
return;
}
m_table.removeByKey(_key);
m_alloc.free(handle);
}
void free(uint16_t _handle)
{
m_table.removeByHandle(_handle);
m_alloc.free(_handle);
}
uint16_t find(KeyT _key) const
{
return m_table.find(_key);
}
const uint16_t* getHandles() const
{
return m_alloc.getHandles();
}
uint16_t getHandleAt(uint16_t _at) const
{
return m_alloc.getHandleAt(_at);
}
uint16_t getNumHandles() const
{
return m_alloc.getNumHandles();
}
uint16_t getMaxHandles() const
{
return m_alloc.getMaxHandles();
}
bool isValid(uint16_t _handle) const
{
return m_alloc.isValid(_handle);
}
void reset()
{
m_table.reset();
m_alloc.reset();
}
private:
HandleHashMapT<MaxHandlesT+MaxHandlesT/2, KeyT> m_table;
HandleAllocT<MaxHandlesT> m_alloc;
};
} // namespace bx
#endif // BX_HANDLE_ALLOC_H_HEADER_GUARD

View File

@@ -162,6 +162,7 @@ namespace bx
template <typename Ty>
inline uint32_t hashMurmur2A(const Ty& _data)
{
BX_STATIC_ASSERT(BX_TYPE_IS_POD(Ty) );
return hashMurmur2A(&_data, sizeof(Ty) );
}

View File

@@ -14,12 +14,146 @@
#include <string.h>
#include <wchar.h> // wchar_t
#include <bx/allocator.h>
#include <bx/hash.h>
#ifndef va_copy
# define va_copy(_a, _b) (_a) = (_b)
#endif // va_copy
namespace bx
{
/// Non-zero-terminated string view.
class StringView
{
public:
StringView()
{
clear();
}
StringView(const char* _ptr, uint32_t _len = UINT32_MAX)
{
clear();
if (NULL != _ptr)
{
uint32_t len = UINT32_MAX == _len ? strlen(_ptr) : _len;
if (0 != len)
{
m_len = len;
m_ptr = _ptr;
}
}
}
void clear()
{
m_ptr = "";
m_len = 0;
}
const char* getPtr() const
{
return m_ptr;
}
const char* getTerm() const
{
return m_ptr + m_len;
}
bool isEmpty() const
{
return 0 == m_len;
}
uint32_t getLength() const
{
return m_len;
}
protected:
friend uint32_t hashMurmur2A(const StringView& _data);
const char* m_ptr;
uint32_t m_len;
};
inline uint32_t hashMurmur2A(const StringView& _data)
{
return hashMurmur2A(_data.m_ptr, _data.m_len);
}
inline uint32_t hashMurmur2A(const char* _data)
{
return hashMurmur2A(StringView(_data) );
}
/// ASCII string
template<bx::AllocatorI** allocator>
class StringT : public StringView
{
public:
StringT()
: StringView("", 0)
{
}
StringT(const char* _rhs)
{
clear();
if (NULL != _rhs)
{
uint32_t len = strlen(_rhs);
m_len = len;
if (0 != len)
{
++len;
char* ptr = (char*)BX_ALLOC(*allocator, len);
memcpy(ptr, _rhs, len);
*const_cast<char**>(&m_ptr) = ptr;
}
}
}
StringT(const StringView& _str)
{
uint32_t len = _str.getLength();
m_len = len;
if (0 != len)
{
++len;
char* ptr = (char*)BX_ALLOC(*allocator, len);
memcpy(ptr, _str.getPtr(), len-1);
ptr[len] = '\0';
*const_cast<char**>(&m_ptr) = ptr;
}
}
~StringT()
{
clear();
}
void clear()
{
if (0 != m_len)
{
BX_FREE(*allocator, const_cast<char*>(m_ptr) );
StringView::clear();
}
}
};
///
inline bool toBool(const char* _str)
{

View File

@@ -692,6 +692,46 @@ namespace bx
#endif // BX_COMPILER_
}
inline uint64_t uint64_sll(uint64_t _a, int _sa)
{
return _a << _sa;
}
inline uint64_t uint64_srl(uint64_t _a, int _sa)
{
return _a >> _sa;
}
inline uint64_t uint64_sra(uint64_t _a, int _sa)
{
return ( (int64_t)_a) >> _sa;
}
inline uint64_t uint64_rol(uint64_t _a, int _sa)
{
return ( _a << _sa) | (_a >> (32-_sa) );
}
inline uint64_t uint64_ror(uint64_t _a, int _sa)
{
return ( _a >> _sa) | (_a << (32-_sa) );
}
inline uint64_t uint64_add(uint64_t _a, uint64_t _b)
{
return _a + _b;
}
inline uint64_t uint64_sub(uint64_t _a, uint64_t _b)
{
return _a - _b;
}
inline uint64_t uint64_mul(uint64_t _a, uint64_t _b)
{
return _a * _b;
}
/// Greatest common divisor.
inline uint32_t uint32_gcd(uint32_t _a, uint32_t _b)
{

View File

@@ -49,8 +49,64 @@ project "bx.test"
}
files {
path.join(BX_DIR, "tests/**.cpp"),
path.join(BX_DIR, "tests/**.H"),
path.join(BX_DIR, "tests/*_test.cpp"),
path.join(BX_DIR, "tests/*_test.H"),
path.join(BX_DIR, "tests/dbg.*"),
}
configuration { "vs* or mingw*" }
links {
"psapi",
}
configuration { "android*" }
targetextension ".so"
linkoptions {
"-shared",
}
configuration { "nacl or nacl-arm" }
targetextension ".nexe"
links {
"ppapi",
"pthread",
}
configuration { "pnacl" }
targetextension ".pexe"
links {
"ppapi",
"pthread",
}
configuration { "linux-*" }
links {
"pthread",
}
configuration { "osx" }
links {
"Cocoa.framework",
}
configuration {}
strip()
project "bx.bench"
kind "ConsoleApp"
debugdir (path.join(BX_DIR, "tests"))
includedirs {
path.join(BX_DIR, "include"),
BX_THIRD_PARTY_DIR,
}
files {
path.join(BX_DIR, "tests/*_bench.cpp"),
path.join(BX_DIR, "tests/*_bench.h"),
path.join(BX_DIR, "tests/dbg.*"),
}
configuration { "vs* or mingw*" }

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2010-2016 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
*/
#include "test.h"
#include <bx/handlealloc.h>
TEST(HandleListT)
{
bx::HandleListT<32> list;
list.pushBack(16);
CHECK(list.getFront() == 16);
CHECK(list.getBack() == 16);
list.pushFront(7);
CHECK(list.getFront() == 7);
CHECK(list.getBack() == 16);
uint16_t expected0[] = { 15, 31, 7, 16, 17, 11, 13 };
list.pushBack(17);
list.pushBack(11);
list.pushBack(13);
list.pushFront(31);
list.pushFront(15);
uint16_t count = 0;
for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count)
{
CHECK(it == expected0[count]);
}
CHECK(count == BX_COUNTOF(expected0) );
list.remove(17);
list.remove(31);
list.remove(16);
list.pushBack(16);
uint16_t expected1[] = { 15, 7, 11, 13, 16 };
count = 0;
for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count)
{
CHECK(it == expected1[count]);
}
CHECK(count == BX_COUNTOF(expected1) );
list.popBack();
list.popFront();
list.popBack();
list.popBack();
CHECK(list.getFront() == 7);
CHECK(list.getBack() == 7);
list.popBack();
CHECK(list.getFront() == UINT16_MAX);
CHECK(list.getBack() == UINT16_MAX);
}
TEST(HandleAllocLruT)
{
bx::HandleAllocLruT<16> lru;
uint16_t handle[4] =
{
lru.alloc(),
lru.alloc(),
lru.alloc(),
lru.alloc(),
};
lru.touch(handle[1]);
uint16_t expected0[] = { handle[1], handle[3], handle[2], handle[0] };
uint16_t count = 0;
for (uint16_t it = lru.getFront(); it != UINT16_MAX; it = lru.getNext(it), ++count)
{
CHECK(it == expected0[count]);
}
}

76
tests/handle_bench.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include <bx/bx.h>
#include <bx/timer.h>
#include <bx/handlealloc.h>
#include <tinystl/allocator.h>
#include <tinystl/unordered_map.h>
#include <unordered_map>
#include <stdio.h>
#include <assert.h>
int main()
{
const uint32_t numElements = 4<<10;
const uint32_t numIterations = 16;
//
{
int64_t elapsed = -bx::getHPCounter();
for (uint32_t ii = 0; ii < numIterations; ++ii)
{
typedef tinystl::unordered_map<uint64_t, uint16_t> TinyStlUnorderedMap;
TinyStlUnorderedMap map;
for (uint32_t jj = 0; jj < numElements; ++jj)
{
tinystl::pair<TinyStlUnorderedMap::iterator, bool> ok = map.insert(tinystl::make_pair(uint64_t(jj), uint16_t(jj) ) );
assert(ok.second);
}
}
elapsed += bx::getHPCounter();
printf(" TinyStl: %15f\n", double(elapsed) );
}
///
{
int64_t elapsed = -bx::getHPCounter();
for (uint32_t ii = 0; ii < numIterations; ++ii)
{
typedef std::unordered_map<uint64_t, uint16_t> StdUnorderedMap;
StdUnorderedMap map;
for (uint32_t jj = 0; jj < numElements; ++jj)
{
std::pair<StdUnorderedMap::iterator, bool> ok = map.insert(std::make_pair(uint64_t(jj), uint16_t(jj) ) );
assert(ok.second);
}
}
elapsed += bx::getHPCounter();
printf(" STL: %15f\n", double(elapsed) );
}
///
{
int64_t elapsed = -bx::getHPCounter();
for (uint32_t ii = 0; ii < numIterations; ++ii)
{
typedef bx::HandleHashMapT<numElements+numElements/2, uint64_t> HandleHashMap;
HandleHashMap map;
for (uint32_t jj = 0; jj < numElements; ++jj)
{
bool ok = map.insert(jj, uint16_t(jj) );
assert(ok);
}
}
elapsed += bx::getHPCounter();
printf("HandleHashMap: %15f\n", double(elapsed) );
}
return EXIT_SUCCESS;
}

119
tests/handle_test.cpp Normal file
View File

@@ -0,0 +1,119 @@
/*
* Copyright 2010-2016 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
*/
#include "test.h"
#include <bx/handlealloc.h>
#include <bx/string.h>
TEST_CASE("HandleListT", "")
{
bx::HandleListT<32> list;
list.pushBack(16);
REQUIRE(list.getFront() == 16);
REQUIRE(list.getBack() == 16);
list.pushFront(7);
REQUIRE(list.getFront() == 7);
REQUIRE(list.getBack() == 16);
uint16_t expected0[] = { 15, 31, 7, 16, 17, 11, 13 };
list.pushBack(17);
list.pushBack(11);
list.pushBack(13);
list.pushFront(31);
list.pushFront(15);
uint16_t count = 0;
for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count)
{
REQUIRE(it == expected0[count]);
}
REQUIRE(count == BX_COUNTOF(expected0) );
list.remove(17);
list.remove(31);
list.remove(16);
list.pushBack(16);
uint16_t expected1[] = { 15, 7, 11, 13, 16 };
count = 0;
for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count)
{
REQUIRE(it == expected1[count]);
}
REQUIRE(count == BX_COUNTOF(expected1) );
list.popBack();
list.popFront();
list.popBack();
list.popBack();
REQUIRE(list.getFront() == 7);
REQUIRE(list.getBack() == 7);
list.popBack();
REQUIRE(list.getFront() == UINT16_MAX);
REQUIRE(list.getBack() == UINT16_MAX);
}
TEST_CASE("HandleAllocLruT", "")
{
bx::HandleAllocLruT<16> lru;
uint16_t handle[4] =
{
lru.alloc(),
lru.alloc(),
lru.alloc(),
lru.alloc(),
};
lru.touch(handle[1]);
uint16_t expected0[] = { handle[1], handle[3], handle[2], handle[0] };
uint16_t count = 0;
for (uint16_t it = lru.getFront(); it != UINT16_MAX; it = lru.getNext(it), ++count)
{
REQUIRE(it == expected0[count]);
}
}
TEST_CASE("HandleHashTable", "")
{
typedef bx::HandleHashMapT<512> HashMap;
HashMap hm;
REQUIRE(512 == hm.getMaxCapacity() );
bx::StringView sv0("test0");
bool ok = hm.insert(bx::hashMurmur2A(sv0), 0);
REQUIRE(ok);
ok = hm.insert(bx::hashMurmur2A(sv0), 0);
REQUIRE(!ok);
REQUIRE(1 == hm.getNumElements() );
bx::StringView sv1("test1");
ok = hm.insert(bx::hashMurmur2A(sv1), 0);
REQUIRE(ok);
REQUIRE(2 == hm.getNumElements() );
hm.removeByHandle(0);
REQUIRE(0 == hm.getNumElements() );
ok = hm.insert(bx::hashMurmur2A(sv0), 0);
REQUIRE(ok);
hm.removeByKey(bx::hashMurmur2A(sv0) );
REQUIRE(0 == hm.getNumElements() );
for (uint32_t ii = 0, num = hm.getMaxCapacity(); ii < num; ++ii)
{
ok = hm.insert(ii, uint16_t(ii) );
REQUIRE(ok);
}
}

32
tests/string_test.cpp Normal file
View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010-2016 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
*/
#include "test.h"
#include <bx/string.h>
#include <bx/crtimpl.h>
#include <bx/handlealloc.h>
bx::AllocatorI* g_allocator;
TEST_CASE("StringView", "")
{
bx::StringView sv("test");
REQUIRE(4 == sv.getLength() );
bx::CrtAllocator crt;
g_allocator = &crt;
typedef bx::StringT<&g_allocator> String;
String st(sv);
REQUIRE(4 == st.getLength() );
st.clear();
REQUIRE(0 == st.getLength() );
REQUIRE(4 == sv.getLength() );
sv.clear();
REQUIRE(0 == sv.getLength() );
}