Roc Toolkit internal modules
Roc Toolkit: real-time audio streaming
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"
17 #include "roc_core/hashmap_impl.h"
18 #include "roc_core/hashmap_node.h"
19 #include "roc_core/hashsum.h"
20 #include "roc_core/iarena.h"
21 #include "roc_core/macro_helpers.h"
22 #include "roc_core/noncopyable.h"
24 #include "roc_core/panic.h"
25 #include "roc_core/stddefs.h"
26 
27 namespace roc {
28 namespace 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.
83 template <class T,
84  size_t EmbeddedCapacity = 0,
85  template <class TT> class OwnershipPolicy = RefCountedOwnership,
86  class Node = HashmapNode<> >
87 class Hashmap : public NonCopyable<> {
88 public:
89  //! Pointer type.
90  //! @remarks
91  //! either raw or smart pointer depending on the ownership policy.
92  typedef typename OwnershipPolicy<T>::Pointer Pointer;
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);
108  OwnershipPolicy<T>::release(*elem);
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.
243  ROC_ATTR_NODISCARD bool insert(T& elem) {
244  HashmapData* data = to_node_data_(elem);
245  if (!insert_(elem.key(), data)) {
246  return false;
247  }
248  OwnershipPolicy<T>::acquire(elem);
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);
268  OwnershipPolicy<T>::release(elem);
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 
291 private:
292  enum {
293  // how much buckets are embedded directly into Hashmap object
294  NumEmbeddedBuckets = ((int)(EmbeddedCapacity == 0 ? 0
295  : EmbeddedCapacity <= 16 ? 16
296  : EmbeddedCapacity)
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
HashmapData * front() const
Get first node in hashmap.
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 * prevof(HashmapData *node) const
Get hashmap node previous to given one.
HashmapData * nextof(HashmapData *node) const
Get hashmap node next to given one.
HashmapData * back() const
Get last node in hashmap.
HashmapData * find_node(hashsum_t hash, const void *key, key_equals_callback callback) const
Find node in the hashmap.
void remove(HashmapData *node, bool skip_rehash)
Remove node from hashmap.
ROC_ATTR_NODISCARD bool grow()
Grow hashtable capacity.
bool contains(const HashmapData *node) const
Check if node belongs to hashmap.
size_t capacity() const
Get maximum number of nodes that can be added to hashmap before grow() should be called.
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
ROC_ATTR_NODISCARD 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
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
ROC_ATTR_NODISCARD bool insert(T &elem)
Insert element into hashmap.
Definition: hashmap.h:243
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
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.
Definition: hashmap_node.h:25