From 0e6ab862d1539b644a85dce42e655b158f1b0324 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Jan 2026 23:55:33 -0500 Subject: [PATCH] xo-arena: DArenaHashMap [WIP] --- xo-arena/include/xo/arena/DArenaHashMap.hpp | 150 ++++++++++++++++++++ xo-arena/include/xo/arena/DArenaVector.hpp | 2 + xo-arena/utest/CMakeLists.txt | 1 + xo-arena/utest/DArenaHashMap.test.cpp | 26 ++++ xo-arena/utest/DArenaVector.test.cpp | 2 +- 5 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 xo-arena/include/xo/arena/DArenaHashMap.hpp create mode 100644 xo-arena/utest/DArenaHashMap.test.cpp diff --git a/xo-arena/include/xo/arena/DArenaHashMap.hpp b/xo-arena/include/xo/arena/DArenaHashMap.hpp new file mode 100644 index 00000000..bc454e19 --- /dev/null +++ b/xo-arena/include/xo/arena/DArenaHashMap.hpp @@ -0,0 +1,150 @@ +/** @file DArenaHashMap.hpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include "DArenaVector.hpp" +#include + +namespace xo { + namespace mm { + /** @brief flat hash map of key-value pairs using dedicated DArenas for storage + * + * Replicates (to the extent feasible) std::unordered_map + * + * @tparam K key type. + * @tparam V value type. + **/ + template , + typename Equal = std::equal_to> + struct DArenaHashMap { + public: + using size_type = std::size_t; + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using key_hash = Hash; + using key_equal = Equal; + using byte = std::byte; + + /** create hash map **/ + DArenaHashMap(size_type hint_max_capacity, + bool debug_flag = false); + DArenaHashMap(Hash && hash = Hash(), + Equal && eq = Equal(), + size_type hint_max_capacity = 0, + bool debug_flag = false); + + /** find smallest x such that 2^x >= n. Return {x, 2^x} **/ + static std::pair lub_exp2(size_t n); + static constexpr size_type group_size() { return c_group_size; } +#ifdef NOT_YET + static size_type min_groups(); + static size_type min_size() { return min_groups() * c_group_size; } +#endif + + size_type empty() const noexcept { return size_ == 0; } + size_type capacity() const noexcept { return n_group_ * c_group_size; } + +#ifdef NOT_YET + // TODO: std::pair + void + insert(std::pair & kv_pair) { + uint64_t h = hash_(kv_pair.first); + } +#endif + + private: + /** group size **/ + static constexpr std::size_t c_group_size = 16; + + /** hash function **/ + key_hash hash_; + /** key equal **/ + key_equal equal_; + /** number of pairs in this table **/ + std::size_t size_ = 0; + /** base-2 logarithm of n_group_ **/ + std::size_t n_group_exponent_ = 0; + /** table has capacity for this number of groups. always an exact power of two. + * number of slots is n_group_ * c_group_size + **/ + std::size_t n_group_ = 1 << n_group_exponent_; + /** control_[] partitioned into groups of c_group_size (16) consecutive elements **/ + DArenaVector control_; + /** slots_[] holds {key,value} pairs **/ + DArenaVector slots_; + /** true to enable debug logging **/ + bool debug_flag_ = false; + }; + + template + DArenaHashMap::DArenaHashMap(size_type hint_max_capacity, + bool debug_flag) + : DArenaHashMap(Hash(), Equal(), hint_max_capacity, debug_flag) + { + } + + template + DArenaHashMap::DArenaHashMap(Hash && hash, + Equal && eq, + size_type hint_max_capacity, + bool debug_flag) + : hash_{std::move(hash)}, + equal_{std::move(eq)}, + size_{0}, + n_group_exponent_{lub_exp2(hint_max_capacity).first}, + n_group_{lub_exp2(hint_max_capacity).second}, + control_{DArenaVector::map(ArenaConfig{.size_ = n_group_})}, + slots_{DArenaVector::map(ArenaConfig{.size_ = n_group_ * sizeof(value_type)})}, + debug_flag_{debug_flag} + { + } + + template + auto + DArenaHashMap::lub_exp2(size_t n) -> std::pair + + { + size_type ngx = 0; + size_type ng = 1; + + while (ng < n) { + ++ngx; + ng *= 2; + } + + return std::make_pair(ngx, ng);; + } + +#ifdef NOT_YET + template + auto + DArenaHashMap::min_groups() -> size_type + { + size_type page_z = getpagesize(); + + // 1 page of slots + size_type n_slot = page_z / sizeof(value_type); + + // 1 page of groups + size_type n_group = n_slot / c_group_size; + + // glb power of 2, but at least 1 + size_type ng = 1; + + while (2 * ng < n_group) + ng *= 2; + + return ng; + } +#endif + + } +} /*namespace xo*/ + +/* end DArenaHashMap.hpp */ diff --git a/xo-arena/include/xo/arena/DArenaVector.hpp b/xo-arena/include/xo/arena/DArenaVector.hpp index a85f8158..5b2e38f1 100644 --- a/xo-arena/include/xo/arena/DArenaVector.hpp +++ b/xo-arena/include/xo/arena/DArenaVector.hpp @@ -3,6 +3,8 @@ * @author Roland Conybeare, Jan 2026 **/ +#pragma once + #include "DArena.hpp" #include diff --git a/xo-arena/utest/CMakeLists.txt b/xo-arena/utest/CMakeLists.txt index 26b4e3d7..5b45103f 100644 --- a/xo-arena/utest/CMakeLists.txt +++ b/xo-arena/utest/CMakeLists.txt @@ -7,6 +7,7 @@ set(UTEST_SRCS # objectmodel.test.cpp DArena.test.cpp DArenaVector.test.cpp + DArenaHashMap.test.cpp # DArenaIterator.test.cpp # Collector.test.cpp # DX1CollectorIterator.test.cpp diff --git a/xo-arena/utest/DArenaHashMap.test.cpp b/xo-arena/utest/DArenaHashMap.test.cpp new file mode 100644 index 00000000..675ba987 --- /dev/null +++ b/xo-arena/utest/DArenaHashMap.test.cpp @@ -0,0 +1,26 @@ +/** @file DArenaHashMap.test.cpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#include "DArenaHashMap.hpp" +#include + +namespace xo { + using xo::mm::DArenaHashMap; + //using xo::mM::ArenaConfig; + + namespace ut { + TEST_CASE("DArenaHashMap-ctor", "[arena][DArenaHashMap]") + { + using HashMap = DArenaHashMap; + + HashMap map; + + REQUIRE(map.empty()); + REQUIRE(map.capacity() == HashMap::group_size()); + } + } +} + +/* end DArenaHashMap.test.cpp */ diff --git a/xo-arena/utest/DArenaVector.test.cpp b/xo-arena/utest/DArenaVector.test.cpp index 4366cdf7..963c9513 100644 --- a/xo-arena/utest/DArenaVector.test.cpp +++ b/xo-arena/utest/DArenaVector.test.cpp @@ -3,7 +3,7 @@ * @author Roland Conybeare, Jan 2026 **/ -#include "xo/arena/DArenaVector.hpp" +#include "DArenaVector.hpp" #include namespace xo {