RPNX::DataStructures
Header-only C++ data structures and supporting utilities.
Loading...
Searching...
No Matches
sharded_unordered_map.hpp
1// Copyright (c) 2026 Ryan P. Nicholl <rnicholl@protonmail.com>
2
3#ifndef RPNXDATASTRUCTURES_SHARDED_MAP_HPP
4#define RPNXDATASTRUCTURES_SHARDED_MAP_HPP
5
6#include <iterator>
7#include <mutex>
8#include <new>
9#include <thread>
10#include <type_traits>
11#include <unordered_map>
12#include <utility>
13#include <vector>
14
15namespace rpnx
16{
34 template < typename Key, typename Value, typename Hash = std::hash< Key >, typename KeyEqual = std::equal_to< Key >, typename Alloc = std::allocator< std::pair< const Key, Value > > >
36 {
37 struct alignas(std::hardware_destructive_interference_size) shard
38 {
39 mutable std::mutex m_mutex;
40 std::unordered_map< Key, Value, Hash, KeyEqual, Alloc > m_map;
41
42 std::mutex& get_mutex() const
43 {
44 return m_mutex;
45 }
46
47 shard(Alloc alloc) : m_map(0, Hash(), KeyEqual(), alloc)
48 {
49 }
50
51 shard(shard const&) = delete;
52 shard& operator=(shard const&) = delete;
53
54 shard(shard&& other) noexcept(std::is_nothrow_move_constructible_v< decltype(m_map) >) : m_map(std::move(other.m_map))
55 {
56 }
57
58 shard& operator=(shard&&) = delete;
59 };
60
61 std::vector< shard, typename std::allocator_traits< Alloc >::template rebind_alloc< shard > > m_shards;
62
63 template < bool IsConst >
64 class basic_iterator
65 {
66 using map_type = std::conditional_t< IsConst, const conc_sharded_unordered_map, conc_sharded_unordered_map >;
67 using shard_type = std::conditional_t< IsConst, const shard, shard >;
68 using inner_iterator = std::conditional_t< IsConst, typename std::unordered_map< Key, Value, Hash, KeyEqual, Alloc >::const_iterator, typename std::unordered_map< Key, Value, Hash, KeyEqual, Alloc >::iterator >;
69
70 map_type* m_map;
71 std::size_t m_shard_index;
72 inner_iterator m_inner;
73
74 void advance_to_next_valid()
75 {
76 while (m_inner == m_map->m_shards[m_shard_index].m_map.end())
77 {
78 m_shard_index++;
79 if (m_shard_index >= m_map->m_shards.size())
80 {
81 break;
82 }
83 m_inner = m_map->m_shards[m_shard_index].m_map.begin();
84 }
85 }
86
87 public:
88 using iterator_category = std::forward_iterator_tag;
89 using value_type = std::pair< const Key, Value >;
90 using difference_type = std::ptrdiff_t;
91 using pointer = std::conditional_t< IsConst, const value_type*, value_type* >;
92 using reference = std::conditional_t< IsConst, const value_type&, value_type& >;
93
94 basic_iterator() : m_map(nullptr), m_shard_index(0), m_inner()
95 {
96 }
97 basic_iterator(map_type* map, std::size_t shard_index, inner_iterator inner) : m_map(map), m_shard_index(shard_index), m_inner(inner)
98 {
99 if (m_map && m_shard_index < m_map->m_shards.size())
100 {
101 advance_to_next_valid();
102 }
103 }
104
105 reference operator*() const
106 {
107 return *m_inner;
108 }
109 pointer operator->() const
110 {
111 return &(*m_inner);
112 }
113
114 basic_iterator& operator++()
115 {
116 ++m_inner;
117 advance_to_next_valid();
118 return *this;
119 }
120
121 basic_iterator operator++(int)
122 {
123 basic_iterator tmp = *this;
124 ++(*this);
125 return tmp;
126 }
127
128 [[nodiscard]] bool operator==(const basic_iterator& other) const
129 {
130 if (m_map != other.m_map)
131 return false;
132 if (m_shard_index != other.m_shard_index)
133 return false;
134 if (m_shard_index >= (m_map ? m_map->m_shards.size() : 0))
135 return true;
136 return m_inner == other.m_inner;
137 }
138
139 bool operator!=(const basic_iterator& other) const
140 {
141 return !(*this == other);
142 }
143 };
144
145 template < typename Iterator >
146 struct range
147 {
148 Iterator m_begin;
149 Iterator m_end;
150 Iterator begin()
151 {
152 return std::move(m_begin);
153 }
154 Iterator end()
155 {
156 return std::move(m_end);
157 }
158 };
159
160 public:
167 using iterator = basic_iterator< false >;
168
175 using const_iterator = basic_iterator< true >;
176
185 [[nodiscard]] range< iterator > range_exclusive()
186 {
187 return {iterator(this, 0, m_shards[0].m_map.begin()), iterator(this, m_shards.size(), {})};
188 }
189
198 [[nodiscard]] range< const_iterator > range_exclusive() const
199 {
200 return {const_iterator(this, 0, m_shards[0].m_map.begin()), const_iterator(this, m_shards.size(), {})};
201 }
202
214 [[nodiscard]] std::size_t estimate_size() const
215 {
216 std::size_t total_size = 0;
217 for (const auto& shard : m_shards)
218 {
219 std::lock_guard< std::mutex > lock(shard.get_mutex());
220 total_size += shard.m_map.size();
221 }
222 return total_size;
223 }
224
232 [[nodiscard]] std::size_t size_exclusive() const
233 {
234 std::size_t total_size = 0;
235 for (const auto& shard : m_shards)
236 {
237 total_size += shard.m_map.size();
238 }
239 return total_size;
240 }
241
249 explicit conc_sharded_unordered_map(std::size_t shard_count = std::thread::hardware_concurrency() * 2, Alloc const& alloc = Alloc()) : m_shards((typename std::allocator_traits< Alloc >::template rebind_alloc< shard >)(alloc))
250 {
251 if (shard_count == 0 || (shard_count & (shard_count - 1)) != 0)
252 {
253 // Ensure shard_count is a power of two
254 std::size_t power = 1;
255 while (power < shard_count)
256 {
257 power <<= 1;
258 }
259 shard_count = power;
260 }
261 m_shards.reserve(shard_count);
262 for (std::size_t i = 0; i < shard_count; ++i)
263 {
264 m_shards.emplace_back(alloc);
265 }
266 }
267
272
277
282
287
294
304 void put(Key const& key, Value value)
305 {
306 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
307 shard& target_shard = m_shards[shard_index];
308 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
309 target_shard.m_map[key] = std::move(value);
310 }
311
322 template < typename Func >
323 void put_exec(Key const& key, Func func)
324 {
325 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
326 shard& target_shard = m_shards[shard_index];
327 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
328 target_shard.m_map[key] = func();
329 }
330
340 template < typename Func >
341 bool try_put_exec(Key const& key, Func func)
342 {
343 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
344 shard& target_shard = m_shards[shard_index];
345 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
346 if (target_shard.m_map.find(key) != target_shard.m_map.end())
347 {
348 return false;
349 }
350 target_shard.m_map[key] = func();
351 return true;
352 }
353
366 template < typename Func >
367 Value& get_or_create(Key const& key, Func func)
368 {
369 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
370 shard& target_shard = m_shards[shard_index];
371 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
372 if (auto it = target_shard.m_map.find(key); it != target_shard.m_map.end())
373 {
374 return it->second;
375 }
376 else
377 {
378 auto [it2, inserted] = target_shard.m_map.emplace(key, func());
379 return it2->second;
380 }
381 }
382
396 template < typename Func >
397 Value& get_or_init(Key const& key, Func func)
398 {
399 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
400 shard& target_shard = m_shards[shard_index];
401 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
402 if (auto it = target_shard.m_map.find(key); it != target_shard.m_map.end())
403 {
404 return it->second;
405 }
406 else
407 {
408 try
409 {
410 auto& val = target_shard.m_map[key];
411 func(val);
412 return val;
413 }
414 catch (...)
415 {
416 target_shard.m_map.erase(key);
417 throw;
418 }
419 }
420 }
421
435 template < typename Func >
436 Value& get_or_init_iter(Key const& key, Func func)
437 {
438 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
439 shard& target_shard = m_shards[shard_index];
440 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
441 if (auto it = target_shard.m_map.find(key); it != target_shard.m_map.end())
442 {
443 return it->second;
444 }
445 else
446 {
447 try
448 {
449 auto val = target_shard.m_map.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple());
450 func(val.first->first, val.first->second);
451 return val.first->second;
452 }
453 catch (...)
454 {
455 target_shard.m_map.erase(key);
456 throw;
457 }
458 }
459 }
460
469 bool try_put(Key const& key, Value value)
470 {
471 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
472 shard& target_shard = m_shards[shard_index];
473 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
474 auto [it, inserted] = target_shard.m_map.emplace(key, std::move(value));
475 return inserted;
476 }
477
487 Value get(Key const& key)
488 {
489 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
490 shard& target_shard = m_shards[shard_index];
491 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
492 return target_shard.m_map.at(key);
493 }
494
503 void erase(Key const& key)
504 {
505 std::size_t shard_index = Hash{}(key) & (m_shards.size() - 1);
506 shard& target_shard = m_shards[shard_index];
507 std::lock_guard< std::mutex > lock(target_shard.get_mutex());
508 target_shard.m_map.erase(key);
509 }
510 };
511} // namespace rpnx
512
513#endif // RPNXDATASTRUCTURES_SHARDED_MAP_HPP
range< const_iterator > range_exclusive() const
Definition sharded_unordered_map.hpp:198
Value & get_or_create(Key const &key, Func func)
Definition sharded_unordered_map.hpp:367
conc_sharded_unordered_map(conc_sharded_unordered_map &&)=delete
bool try_put(Key const &key, Value value)
Definition sharded_unordered_map.hpp:469
void put(Key const &key, Value value)
Definition sharded_unordered_map.hpp:304
Value & get_or_init_iter(Key const &key, Func func)
Definition sharded_unordered_map.hpp:436
basic_iterator< true > const_iterator
Definition sharded_unordered_map.hpp:175
std::size_t size_exclusive() const
Definition sharded_unordered_map.hpp:232
conc_sharded_unordered_map & operator=(conc_sharded_unordered_map const &)=delete
range< iterator > range_exclusive()
Definition sharded_unordered_map.hpp:185
basic_iterator< false > iterator
Definition sharded_unordered_map.hpp:167
void erase(Key const &key)
Definition sharded_unordered_map.hpp:503
std::size_t estimate_size() const
Definition sharded_unordered_map.hpp:214
conc_sharded_unordered_map(std::size_t shard_count=std::thread::hardware_concurrency() *2, Alloc const &alloc=Alloc())
Definition sharded_unordered_map.hpp:249
void put_exec(Key const &key, Func func)
Definition sharded_unordered_map.hpp:323
Value get(Key const &key)
Definition sharded_unordered_map.hpp:487
conc_sharded_unordered_map(conc_sharded_unordered_map const &)=delete
conc_sharded_unordered_map & operator=(conc_sharded_unordered_map &&)=delete
Value & get_or_init(Key const &key, Func func)
Definition sharded_unordered_map.hpp:397
bool try_put_exec(Key const &key, Func func)
Definition sharded_unordered_map.hpp:341
Ordered key-value container with size-first ordering.
Definition map.hpp:31
Containers, iterator adapters, callable wrappers, and value utilities.
Definition annex.hpp:14
bool operator!=(annex< T, Alloc > const &lhs, annex< T, Alloc > const &rhs)
Compares two annex objects for inequality.
Definition annex.hpp:615
bool operator==(annex< T, Alloc > const &lhs, annex< T, Alloc > const &rhs)
Compares two annex objects for equality.
Definition annex.hpp:598