Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
Loading...
Searching...
No Matches
hashmap.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 Roc Streaming authors
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 */
8
9//! @file roc_core/hashmap.h
10//! @brief Intrusive hash table.
11
12#ifndef ROC_CORE_HASHMAP_H_
13#define ROC_CORE_HASHMAP_H_
14
16#include "roc_core/attributes.h"
19#include "roc_core/hashsum.h"
20#include "roc_core/iarena.h"
24#include "roc_core/panic.h"
25#include "roc_core/stddefs.h"
26
27namespace roc {
28namespace core {
29
30//! Intrusive hash table.
31//!
32//! Characteristics:
33//! 1) Intrusive. Hash table nodes are stored directly in elements. No allocations
34//! are needed to insert a node. Arena is used only to allocate an array
35//! of buckets.
36//! 2) Collision-chaining. Implemented as an array of buckets, where a bucket is
37//! the head of a doubly-linked lists of bucket elements.
38//! 3) Controllable allocations. Allocations and deallocations are performed only
39//! when the hash table is explicitly growed. All other operations don't touch
40//! arena.
41//! 4) Zero allocations for small hash tables. A fixed number of buckets can be
42//! embedded directly into hash table object.
43//! 5) Incremental rehashing. After hash table growth, rehashing is performed
44//! incrementally when inserting and removing elements. The slower hash table
45//! size growth is, the less overhead rehashing adds to each operation.
46//! 6) Allows to iterate elements in insertion order. Implements safe iteration with
47//! regards to element insertion and deletion. Elements deleted during iteration
48//! won't be visited. Elements inserted during iteration will be visited.
49//!
50//! Incremental rehashing technique is inspired by Go's map implementation, though
51//! there are differences. Load factor value is taken from it as well.
52//! Prime numbers for sizes are from https://planetmath.org/goodhashtableprimes.
53//!
54//! @tparam T defines object type, it must inherit HashmapNode and additionally
55//! implement three methods:
56//!
57//! @code
58//! // get object key
59//! Key key() const;
60//!
61//! // compute key hash
62//! static core::hashsum_t key_hash(Key key);
63//!
64//! // compare two keys for equality
65//! static bool key_equal(Key key1, Key key2);
66//! @endcode
67//!
68//! "Key" can be any type. Hashmap doesn't use it directly. It is never stored and
69//! is always accessed via the three methods above. The hash is computed for a key
70//! only once when an object is inserted into hash table.
71//!
72//! @tparam EmbeddedCapacity defines the capacity embedded directly into Hashmap.
73//! It is used instead of dynamic memory while the number of elements is smaller
74//! than this capacity. The actual object size occupied to provide the requested
75//! capacity is implementation defined.
76//!
77//! @tparam OwnershipPolicy defines ownership policy which is used to acquire an element
78//! ownership when it's added to the hashmap and release ownership when it's removed
79//! from the hashmap.
80//!
81//! @tparam Node defines base class of hashmap nodes. It is needed if HashmapNode
82//! is used with non-default tag.
83template <class T,
84 size_t EmbeddedCapacity = 0,
85 template <class TT> class OwnershipPolicy = RefCountedOwnership,
86 class Node = HashmapNode<> >
87class Hashmap : public NonCopyable<> {
88public:
89 //! Pointer type.
90 //! @remarks
91 //! either raw or smart pointer depending on the ownership policy.
93
94 //! Initialize empty hashmap with arena.
95 //! @remarks
96 //! Hashmap capacity may grow using arena.
97 explicit Hashmap(IArena& arena)
98 : impl_(embedded_buckets_.memory(), NumEmbeddedBuckets, arena) {
99 }
100
101 //! Release ownership of all elements.
103 HashmapData* data = impl_.front();
104
105 while (data != NULL) {
106 impl_.remove(data, true);
107 T* elem = from_node_data_(data);
109 data = impl_.front();
110 }
111 }
112
113 //! Get maximum number of elements that can be added to hashmap before
114 //! grow() should be called.
115 size_t capacity() const {
116 return impl_.capacity();
117 }
118
119 //! Get number of elements added to hashmap.
120 size_t size() const {
121 return impl_.size();
122 }
123
124 //! Check if size is zero.
125 bool is_empty() const {
126 return size() == 0;
127 }
128
129 //! Check if element belongs to hashmap.
130 //!
131 //! @note
132 //! - has O(1) complexity
133 //! - doesn't compute key hashes
134 bool contains(const T& elem) const {
135 const HashmapData* data = to_node_data_(elem);
136 return impl_.contains(data);
137 }
138
139 //! Find element in the hashmap by key.
140 //!
141 //! @returns
142 //! Pointer to the element with given key or NULL if it's not found.
143 //!
144 //! @note
145 //! - has O(1) complexity in average and O(n) in the worst case
146 //! - computes key hash
147 //!
148 //! @note
149 //! The worst case is achieved when the hash function produces many collisions.
150 template <class Key> Pointer find(const Key& key) const {
151 const hashsum_t hash = T::key_hash(key);
152 HashmapData* data = impl_.find_node(
153 hash, (const void*)&key,
155 if (!data) {
156 return NULL;
157 }
158 return from_node_data_(data);
159 }
160
161 //! Get first element in hashmap.
162 //! Elements are ordered by insertion.
163 //! @returns
164 //! first element or NULL if hashmap is empty.
165 Pointer front() const {
166 HashmapData* data = impl_.front();
167 if (!data) {
168 return NULL;
169 }
170 return from_node_data_(data);
171 }
172
173 //! Get last element in hashmap.
174 //! Elements are ordered by insertion.
175 //! @returns
176 //! last element or NULL if hashmap is empty.
177 Pointer back() const {
178 HashmapData* node = impl_.back();
179 if (!node) {
180 return NULL;
181 }
182 return from_node_data_(node);
183 }
184
185 //! Get hashmap element next to given one.
186 //! Elements are ordered by insertion.
187 //!
188 //! @returns
189 //! hashmap element following @p elem if @p elem is not
190 //! last, or NULL otherwise.
191 //!
192 //! @pre
193 //! @p elem should be member of this hashmap.
194 Pointer nextof(T& elem) const {
195 HashmapData* data = to_node_data_(elem);
196 HashmapData* next_data = impl_.nextof(data);
197 if (!next_data) {
198 return NULL;
199 }
200 return from_node_data_(next_data);
201 }
202
203 //! Get hashmap element previous to given one.
204 //! Elements are ordered by insertion.
205 //!
206 //! @returns
207 //! hashmap element preceding @p elem if @p elem is not
208 //! first, or NULL otherwise.
209 //!
210 //! @pre
211 //! @p elem should be member of this hashmap.
212 Pointer prevof(T& elem) const {
213 HashmapData* data = to_node_data_(elem);
214 HashmapData* prev_data = impl_.prevof(data);
215 if (!prev_data) {
216 return NULL;
217 }
218 return from_node_data_(prev_data);
219 }
220
221 //! Insert element into hashmap.
222 //!
223 //! @remarks
224 //! - acquires ownership of @p elem
225 //!
226 //! @returns
227 //! false if the allocation failed
228 //!
229 //! @pre
230 //! - @p elem should not be member of any hashmap
231 //! - hashmap shouldn't have an element with the same key
232 //!
233 //! @note
234 //! - has O(1) complexity in average and O(n) in the worst case
235 //! - computes key hash
236 //! - doesn't make allocations or deallocations
237 //! - proceeds lazy rehashing
238 //!
239 //! @note
240 //! Insertion speed is higher when insert to remove ratio is close to one or lower,
241 //! and slows down when it becomes higher than one. The slow down is caused by
242 //! the incremental rehashing algorithm.
244 HashmapData* data = to_node_data_(elem);
245 if (!insert_(elem.key(), data)) {
246 return false;
247 }
249 return true;
250 }
251
252 //! Remove element from hashmap.
253 //!
254 //! @remarks
255 //! - releases ownership of @p elem
256 //!
257 //! @pre
258 //! @p elem should be member of this hashmap.
259 //!
260 //! @note
261 //! - has O(1) complexity
262 //! - doesn't compute key hash
263 //! - doesn't make allocations or deallocations
264 //! - proceeds lazy rehashing
265 void remove(T& elem) {
266 HashmapData* data = to_node_data_(elem);
267 impl_.remove(data, false);
269 }
270
271 //! Grow hashtable capacity.
272 //!
273 //! @remarks
274 //! Check if hash table is full (size is equal to capacity), and if so, increase
275 //! hash table capacity and initiate incremental rehashing. Rehashing will be
276 //! performed during subsequent insertions and removals.
277 //!
278 //! @returns
279 //! - true if no growth needed or growth succeeded
280 //! - false if allocation failed
281 //!
282 //! @note
283 //! - has O(1) complexity
284 //! - doesn't computes key hashes
285 //! - makes allocations and deallocations
286 //! - doesn't proceed lazy rehashing
288 return impl_.grow();
289 }
290
291private:
292 enum {
293 // how much buckets are embedded directly into Hashmap object
294 NumEmbeddedBuckets = ((int)(EmbeddedCapacity == 0 ? 0
295 : EmbeddedCapacity <= 16 ? 16
297 * HashmapImpl::LoadFactorDen
298 + HashmapImpl::LoadFactorNum - 1)
299 / HashmapImpl::LoadFactorNum * 2
300 };
301
302 static HashmapData* to_node_data_(const T& elem) {
303 return static_cast<const Node&>(elem).hashmap_data();
304 }
305
306 static T* from_node_data_(HashmapData* data) {
307 return static_cast<T*>(static_cast<Node*>(Node::hashmap_node(data)));
308 }
309
310 template <class Key> static bool key_equal_(HashmapData* node, const void* key) {
311 T* elem = from_node_data_(node);
312 const Key& key_ref = *(const Key*)key;
313 return T::key_equal(elem->key(), key_ref);
314 }
315
316 template <class Key> bool insert_(const Key& key, HashmapData* node) {
317 const hashsum_t hash = T::key_hash(key);
318 return impl_.insert(
319 node, hash, (const void*)&key,
320 &Hashmap<T, EmbeddedCapacity, OwnershipPolicy, Node>::key_equal_<Key>);
321 }
322
323 AlignedStorage<NumEmbeddedBuckets * sizeof(HashmapImpl::Bucket)> embedded_buckets_;
324
325 HashmapImpl impl_;
326};
327
328} // namespace core
329} // namespace roc
330
331#endif // ROC_CORE_HASHMAP_H_
Aligned storage.
Compiler attributes.
#define ROC_ATTR_NODISCARD
Emit warning if function result is not checked.
Definition attributes.h:31
Intrusive hash table internal implementation.
bool insert(HashmapData *node, hashsum_t hash, const void *key, key_equals_callback callback)
Insert node into hashmap.
size_t size() const
Get number of nodes added to hashmap.
HashmapData * front() const
Get first node in hashmap.
HashmapData * back() const
Get last node in hashmap.
void remove(HashmapData *node, bool skip_rehash)
Remove node from hashmap.
HashmapData * prevof(HashmapData *node) const
Get hashmap node previous to given one.
bool grow()
Grow hashtable capacity.
bool contains(const HashmapData *node) const
Check if node belongs to hashmap.
HashmapData * nextof(HashmapData *node) const
Get hashmap node next to given one.
size_t capacity() const
Get maximum number of nodes that can be added to hashmap before grow() should be called.
HashmapData * find_node(hashsum_t hash, const void *key, key_equals_callback callback) const
Find node in the hashmap.
Intrusive hash table.
Definition hashmap.h:87
bool contains(const T &elem) const
Check if element belongs to hashmap.
Definition hashmap.h:134
void remove(T &elem)
Remove element from hashmap.
Definition hashmap.h:265
Pointer front() const
Get first element in hashmap. Elements are ordered by insertion.
Definition hashmap.h:165
Pointer nextof(T &elem) const
Get hashmap element next to given one. Elements are ordered by insertion.
Definition hashmap.h:194
bool grow()
Grow hashtable capacity.
Definition hashmap.h:287
size_t size() const
Get number of elements added to hashmap.
Definition hashmap.h:120
bool is_empty() const
Check if size is zero.
Definition hashmap.h:125
OwnershipPolicy< T >::Pointer Pointer
Pointer type.
Definition hashmap.h:92
Pointer find(const Key &key) const
Find element in the hashmap by key.
Definition hashmap.h:150
bool insert(T &elem)
Insert element into hashmap.
Definition hashmap.h:243
size_t capacity() const
Get maximum number of elements that can be added to hashmap before grow() should be called.
Definition hashmap.h:115
Pointer prevof(T &elem) const
Get hashmap element previous to given one. Elements are ordered by insertion.
Definition hashmap.h:212
Pointer back() const
Get last element in hashmap. Elements are ordered by insertion.
Definition hashmap.h:177
~Hashmap()
Release ownership of all elements.
Definition hashmap.h:102
Hashmap(IArena &arena)
Initialize empty hashmap with arena.
Definition hashmap.h:97
Memory arena interface.
Definition iarena.h:23
Base class for non-copyable objects.
Definition noncopyable.h:23
Shared ownership intrusive pointer.
Definition shared_ptr.h:32
Intrusive hash table implementation file.
Hashmap node.
Hash sum.
Memory arena interface.
Helper macros.
size_t hashsum_t
Hash type.
Definition hashsum.h:21
Root namespace.
Non-copyable object.
Ownership policies.
Panic.
Commonly used types and functions.
Hashmap node internal data.