ATLAS Offline Software
ConcurrentHashmapImpl.icc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration.
3  */
4 /**
5  * @file CxxUtils/ConcurrentHashmapImpl.h
6  * @author scott snyder <snyder@bnl.gov>
7  * @date Dec, 2020
8  * @brief Hash table allowing concurrent, lockless reads.
9  */
10 
11 
12 #include <cassert>
13 #include <bit>
14 
15 
16 namespace CxxUtils {
17 namespace detail {
18 
19 
20 /**
21  * @brief Constructor.
22  * @param hash The hash of the entry we're looking for.
23  * @param mask Table mask; i.e., the table capacity - 1.
24  * @param maskBits Number of 1 bits in mask.
25  * @param probeLimit Maximum number of probes to try before failing.
26  */
27 template <unsigned ENTRIES_PER_CACHELINE>
28 inline
29 CHMTableIterator<ENTRIES_PER_CACHELINE>::CHMTableIterator
30  (size_t hash, size_t mask, size_t maskBits, size_t probeLimit)
31  // Mask limited to the table. Also mask off the part within a cache line.
32  : m_mask ((mask & ~ENTRIES_PER_CACHELINE_MASK)),
33  // Offset of hash within a cache line.
34  m_boffs (hash & ENTRIES_PER_CACHELINE_MASK),
35  // Starting increment between hash probes.
36  // Must be large enough to go to a new cache line, which is why
37  // we or in ENTRIES_PER_CACHELINE.
38  m_stride (((hash >> maskBits) | hash | ENTRIES_PER_CACHELINE) & ENTRIES_PER_CACHELINE_MASK),
39  m_probeLimit (probeLimit),
40  // Index at the start of the cache line containing hash.
41  m_bucket (hash & m_mask),
42  m_nprobes (0)
43 {
44 }
45 
46 
47 /**
48  * @brief Offset of the element currently being probed.
49  */
50 template <unsigned ENTRIES_PER_CACHELINE>
51 inline
52 size_t CHMTableIterator<ENTRIES_PER_CACHELINE>::offset() const
53 {
54  // m_bucket the starting index of the current cache line.
55  // Count within the cache line in a circular manner starting
56  // at m_boffs.
57  return m_bucket + ((m_nprobes + m_boffs)&ENTRIES_PER_CACHELINE_MASK);
58 }
59 
60 
61 /**
62  * @brief Return the number of probes performed so far.
63  */
64 template <unsigned ENTRIES_PER_CACHELINE>
65 inline
66 size_t CHMTableIterator<ENTRIES_PER_CACHELINE>::nprobes() const
67 {
68  return m_nprobes;
69 }
70 
71 
72 /**
73  * @brief Move to the next probe.
74  * Returns true if we should continue, or false if we've hit the maximum
75  * number of probes.
76  */
77 template <unsigned ENTRIES_PER_CACHELINE>
78 inline
79 bool CHMTableIterator<ENTRIES_PER_CACHELINE>::next()
80 {
81  // Increment number of probes and stop if we've hit the maximum.
82  if (++m_nprobes >= m_probeLimit) {
83  return false;
84  }
85  // We've finished a cache line if the low bits are back to 0.
86  if ((m_nprobes & ENTRIES_PER_CACHELINE_MASK) == 0) {
87  // Move to a different cacheline.
88  // cf. knuth AOCP exercise 6.4.20.
89  // By construction, the low bits (within the cacheline) of
90  // m_bucket, m_nprobes, and m_stride should all be 0.
91  m_bucket = (m_bucket + m_nprobes + m_stride) & m_mask;
92  }
93  return true;
94 }
95 
96 
97 //*****************************************************************************
98 
99 
100 #define T_CHMIMPL \
101  template <template <class> class UPDATER_, \
102  typename HASHER_, \
103  typename MATCHER_, \
104  uintptr_t NULLVAL_, \
105  uintptr_t TOMBSTONE_>
106 
107 #define CHMIMPL ConcurrentHashmapImpl<UPDATER_, HASHER_, MATCHER_, NULLVAL_, TOMBSTONE_>
108 
109 
110 /**
111  * @brief Constructor.
112  * @param capacity Number of entries in the table. Must be a power of 2.
113  * @param hasher Hash object to use.
114  * @param matcher Key match object to use.
115  */
116 T_CHMIMPL
117 CHMIMPL::Table::Table (size_t capacity,
118  const Hasher_t& hasher /*= Hasher_t()*/,
119  const Matcher_t& matcher /*= Matcher_t()*/)
120  : m_capacity (capacity),
121  m_maxProbe (capacity / 4),
122  m_mask (capacity-1),
123  m_maskBits (std::countr_zero (capacity)),
124  m_hasher (hasher),
125  m_matcher (matcher),
126  m_longestProbe (0)
127 {
128  // Clear all the keys.
129  for (size_t i = 0; i < capacity; i++) {
130  m_entries[i].m_key = nullval;
131  }
132 }
133 
134 
135 /**
136  * @brief Allocator for table objects.
137  * @param capacity Size of the table (must be a power of 2).
138  *
139  * Allocate with enough space for the table of entries.
140  * Also align on a cache line.
141  */
142 T_CHMIMPL
143 void* CHMIMPL::Table::operator new (size_t, size_t capacity)
144 {
145  void* memptr = nullptr;
146  // Allocate aligned memory block.
147  // The Table structure includes one entry at the end,
148  // so subtract 1 from capacity.
149  posix_memalign (&memptr, CACHELINE, sizeof(Table) + (capacity-1)*sizeof(entry_t));
150  if (!memptr) std::abort();
151  return memptr;
152 }
153 
154 
155 /**
156  * @brief Deallocator for table objects.
157  */
158 T_CHMIMPL
159 void CHMIMPL::Table::operator delete (void* p)
160 {
161  free (p);
162 }
163 
164 
165 /**
166  * @brief Find a table entry for reading.
167  * @param key The key for which to search.
168  * @param hash The hash of the key.
169  *
170  * Returns the matching entry, or nullptr.
171  */
172 T_CHMIMPL
173 size_t CHMIMPL::Table::probeRead (val_t key, size_t hash) const
174 {
175  // Iterate over table probes.
176  // We don't need to check more than the longest probe sequence
177  // used so far.
178  TableIterator it (hash, m_mask, m_maskBits, m_longestProbe);
179  do {
180  size_t offset = it.offset();
181  const entry_t* ent = m_entries + offset;
182  if (ent->m_key == nullval) {
183  // If we hit an empty key, report failure.
184  return INVALID;
185  }
186  if (m_matcher (ent->m_key, key)) {
187  // Found a matching key.
188  return offset;
189  }
190  } while (it.next());
191  // Too many probes --- return failure.
192  return INVALID;
193 }
194 
195 
196 /**
197  * @brief Find a table entry for writing.
198  * @param key The key for which to search.
199  * @param hash The hash of the key.
200  * @param entry[out] The entry found.
201  *
202  * If we find the entry, return true with @c entry pointing at it.
203  * If we don't find it, and there's still room in the table, return false
204  * with @c entry pointing at the next empty entry.
205  * Otherwise, return false with @c entry set to nullptr.
206  */
207 T_CHMIMPL
208 size_t CHMIMPL::Table::probeWrite (val_t key, size_t hash, bool& insert)
209 {
210  // Iterate over table probes.
211  TableIterator it (hash, m_mask, m_maskBits, m_maxProbe);
212  do {
213  size_t offset = it.offset();
214  entry_t* ent = m_entries + offset;
215  if (ent->m_key == nullval) {
216  // We hit an empty key; a new entry could be added here.
217  // Update the longest probe count.
218  CxxUtils::atomic_fetch_max (&m_longestProbe, it.nprobes()+1);
219  insert = true;
220  return offset;
221  }
222  if (m_matcher (ent->m_key, key)) {
223  // Found a matching key.
224  insert = false;
225  return offset;
226  }
227  } while (it.next());
228  // Too many probes --- return failure.
229  return INVALID;
230 }
231 
232 
233 /**
234  * @brief The number of entries in the table.
235  */
236 T_CHMIMPL
237 inline
238 size_t CHMIMPL::Table::capacity() const
239 {
240  return m_capacity;
241 }
242 
243 
244 /**
245  * @brief Return the entry for an offset.
246  * @param offset The index of the desired entry.
247  */
248 T_CHMIMPL
249 inline
250 const typename CHMIMPL::entry_t& CHMIMPL::Table::entry (size_t offset) const
251 {
252  return m_entries[offset];
253 }
254 
255 
256 /**
257  * @brief Return the entry for an offset (non-const).
258  * @param offset The index of the desired entry.
259  */
260 T_CHMIMPL
261 inline
262 typename CHMIMPL::entry_t& CHMIMPL::Table::entry (size_t offset)
263 {
264  return m_entries[offset];
265 }
266 
267 
268 //*****************************************************************************
269 
270 
271 /**
272  * @brief Constructor.
273  * @param updater Object used to manage memory
274  * (see comments at the start of the class).
275  * @param capacity Minimum initial table size.
276  * @param hasher Hash object to use.
277  * @param matcher Key match object to use.
278  * @param ctx Execution context.
279  */
280 T_CHMIMPL
281 CHMIMPL::ConcurrentHashmapImpl (Updater_t&& updater,
282  size_t capacity_in,
283  const Hasher_t& hasher,
284  const Matcher_t& matcher,
285  const typename Updater_t::Context_t& ctx)
286  : m_updater (std::move (updater)),
287  m_hasher (hasher),
288  m_matcher (matcher),
289  m_size (0),
290  m_erased (0)
291 {
292  // Round up capacity to a power of 2.
293  size_t capacity = round_up (capacity_in);
294 
295  // cppcheck-suppress noDestructor -- ownership goes to the updater
296  m_table = new (capacity) Table (capacity, hasher, matcher);
297  m_updater.update (std::unique_ptr<Table> (m_table), ctx);
298 }
299 
300 
301 /**
302  * @brief Take a lock on the container.
303  *
304  * Take a lock on the container.
305  * The lock can then be passed to put(), allowing to factor out the locking
306  * when put() gets used in a loop. The lock will be released when the
307  * lock object is destroyed.
308  */
309 T_CHMIMPL
310 inline
311 typename CHMIMPL::Lock_t CHMIMPL::lock()
312 {
313  return Lock_t (m_mutex);
314 }
315 
316 
317 /**
318  * @brief Add an entry to the table, with external locking.
319  * @param lock The lock object returned from lock().
320  * @param key The key to insert.
321  * @param hash The hash of the key.
322  * @param val The value to insert.
323  * @param overwrite If true, then overwrite an existing entry.
324  * If false, an existing entry will not be changed.
325  * @param ctx Execution context.
326  *
327  * If the key already exists, then its value will be updated.
328  * Returns an iterator pointing at the entry and a flag which is
329  * true if a new element was added.
330  */
331 T_CHMIMPL
332 std::pair<typename CHMIMPL::const_iterator, bool>
333 CHMIMPL::put (const Lock_t& lock,
334  val_t key, size_t hash, val_t val, bool overwrite,
335  const typename Updater_t::Context_t& ctx)
336 {
337  assert (key != nullval && key != tombstone);
338 
339  do {
340  bool insert;
341  size_t offset = m_table->probeWrite (key, hash, insert);
342  if (offset != INVALID) {
343  entry_t& ent = m_table->entry (offset);
344  if (insert) {
345  // Found a place to put it.
346  // Be sure not to set the key until the value is in place.
347  ent.m_val = val;
348  ent.m_key = key;
349  ++m_size;
350  }
351  else {
352  // Found --- update the entry if wanted.
353  if (overwrite) {
354  if (val != ent.m_val) {
355  ent.m_val = val;
356  }
357  }
358  }
359  return std::make_pair (const_iterator (*m_table, offset), insert);
360  }
361 
362  // Need to grow the table.
363  } while (grow (lock, ctx));
364 
365  // grow() failed.
366  return std::make_pair (end(), false);
367 }
368 
369 
370 /**
371  * @brief Add an entry to the table.
372  * @param key The key to insert.
373  * @param hash The hash of the key.
374  * @param val The value to insert.
375  * @param overwrite If true, then overwrite an existing entry.
376  * If false, an existing entry will not be changed.
377  * @param ctx Execution context.
378  *
379  * If the key already exists, then its value will be updated.
380  * Returns an iterator pointing at the entry and a flag which is
381  * true if a new element was added.
382  */
383 T_CHMIMPL
384 inline
385 std::pair<typename CHMIMPL::const_iterator, bool>
386 CHMIMPL::put (val_t key, size_t hash, val_t val, bool overwrite,
387  const typename Updater_t::Context_t& ctx)
388 {
389  Lock_t l (m_mutex);
390  return put (l, key, hash, val, overwrite, ctx);
391 }
392 
393 
394 /**
395  * @brief Look up an entry in the table.
396  * @param key The key to find.
397  * @param hash The hash of the key.
398  *
399  * Returns an iterator pointing at the found entry, or end().
400  */
401 T_CHMIMPL
402 typename CHMIMPL::const_iterator CHMIMPL::get (val_t key, size_t hash) const
403 {
404  const Table& table = m_updater.get();
405  size_t offset = table.probeRead (key, hash);
406  // Offset will be -1 if not found --- invalid iterator.
407  return const_iterator (table, offset);
408 }
409 
410 
411 /**
412  * @brief Erase an entry from the table, with external locking.
413  * @param lock The lock object returned from lock().
414  * @param key The key to find.
415  * @param hash The hash of the key.
416  *
417  * Mark the corresponding entry as deleted.
418  * Return true on success, false on failure (key not found).
419  *
420  * The tombstone value must be different from the null value.
421  *
422  * Take care if the key or value types require memory allocation.
423  *
424  * This may cause the key type returned by an iterator to change
425  * asynchronously to the tombstone value.
426  **/
427 T_CHMIMPL
428 bool
429 CHMIMPL::erase (const Lock_t& /*lock*/, val_t key, size_t hash)
430 {
431  static_assert (nullval != tombstone);
432  const Table& table = m_updater.get();
433  size_t offset = table.probeRead (key, hash);
434  if (offset != INVALID) {
435  entry_t& ent = m_table->entry (offset);
436  ++m_erased;
437  --m_size;
438  ent.m_key = tombstone;
439  return true;
440  }
441  return false;
442 }
443 
444 
445 /**
446  * @brief Erase an entry from the table.
447  * @param key The key to find.
448  * @param hash The hash of the key.
449  *
450  * Mark the corresponding entry as deleted.
451  * Return true on success, false on failure (key not found).
452  *
453  * The tombstone value must be different from the null value.
454  *
455  * Take care if the key or value types require memory allocation.
456  *
457  * This may cause the key type returned by an iterator to change
458  * asynchronously to the tombstone value.
459  **/
460 T_CHMIMPL
461 inline
462 bool
463 CHMIMPL::erase (val_t key, size_t hash)
464 {
465  return erase (lock(), key, hash);
466 }
467 
468 
469 /**
470  * @brief Return the number of items currently stored.
471  */
472 T_CHMIMPL
473 size_t CHMIMPL::size() const
474 {
475  return m_size;
476 }
477 
478 
479 /**
480  * @brief Return the current table size.
481  */
482 T_CHMIMPL
483 size_t CHMIMPL::capacity() const
484 {
485  const Table& table = m_updater.get();
486  return table.capacity();
487 }
488 
489 
490 /**
491  * @brief The number of erased elements in the current table.
492  */
493 T_CHMIMPL
494 size_t CHMIMPL::erased() const
495 {
496  return m_erased;
497 }
498 
499 
500 /**
501  * @brief Return the hasher object.
502  */
503 T_CHMIMPL
504 inline
505 const typename CHMIMPL::Hasher_t& CHMIMPL::hasher() const
506 {
507  return m_hasher;
508 }
509 
510 
511 /**
512  * @brief Return the matcher object.
513  */
514 T_CHMIMPL
515 inline
516 const typename CHMIMPL::Matcher_t& CHMIMPL::matcher() const
517 {
518  return m_matcher;
519 }
520 
521 
522 /**
523  * @brief Constructor.
524  * @param table The table instance we're referencing.
525  * @param end If true, initialize this to an end iterator.
526  * Otherwise, initialize it to a a begin iterator.
527  */
528 T_CHMIMPL
529 inline
530 CHMIMPL::const_iterator::const_iterator (const Table& table, bool end)
531  : m_table (table),
532  m_offset (INVALID)
533 {
534  // For an end iterator, we want offset to be -1.
535  // For a begin iterator, we need to advance to the first non-null entry.
536  if (!end) {
537  next();
538  }
539 }
540 
541 
542 /**
543  * @brief Constructor.
544  * @param table The table instance we're referencing.
545  * @param offset Offset of the iterator within the table.
546  * (Must point at an occupied entry.)
547  */
548 T_CHMIMPL
549 CHMIMPL::const_iterator::const_iterator (const Table& table, size_t offset)
550  : m_table (table),
551  m_offset (offset)
552 {
553  assert (offset == INVALID || m_table.entry (offset).m_key != nullval);
554 }
555 
556 
557 /**
558  * @brief Advance the iterator to the next occupied entry.
559  */
560 T_CHMIMPL
561 inline
562 void CHMIMPL::const_iterator::next()
563 {
564  val_t key;
565  do {
566  ++m_offset;
567  if (m_offset >= m_table.capacity()) {
568  m_offset = INVALID;
569  break;
570  }
571  key = m_table.entry (m_offset).m_key;
572  } while (key == nullval || key == tombstone);
573 }
574 
575 
576 /**
577  * @brief Move the iterator back to the previous occupied entry.
578  */
579 T_CHMIMPL
580 inline
581 void CHMIMPL::const_iterator::prev()
582 {
583  if (m_offset == INVALID) {
584  m_offset = m_table.capacity();
585  }
586  val_t key;
587  do {
588  --m_offset;
589  if (m_offset >= m_table.capacity()) {
590  m_offset = INVALID;
591  break;
592  }
593  key = m_table.entry (m_offset).m_key;
594  } while (key == nullval || key == tombstone);
595 }
596 
597 
598 /**
599  * @brief Return the key for this iterator.
600  * If deletions are allowed, then the key may change asynchronously
601  * to the tombstone value.
602  */
603 T_CHMIMPL
604 inline
605 typename CHMIMPL::val_t CHMIMPL::const_iterator::key() const
606 {
607  return m_table.entry (m_offset).m_key;
608 }
609 
610 
611 /**
612  * @brief Return the value for this iterator.
613  */
614 T_CHMIMPL
615 inline
616 typename CHMIMPL::val_t CHMIMPL::const_iterator::value() const
617 {
618  return m_table.entry (m_offset).m_val;
619 }
620 
621 
622 /**
623  * @brief Compare two iterators.
624  */
625 T_CHMIMPL
626 inline
627 bool CHMIMPL::const_iterator::operator!= (const const_iterator& other) const
628 {
629  if (m_offset != other.m_offset) return true;
630  if (m_offset == INVALID) return false;
631  return &m_table != &other.m_table;
632 }
633 
634 
635 /**
636  * @brief Check that the iterator is valid (not pointing at the end).
637  */
638 T_CHMIMPL
639 inline
640 bool CHMIMPL::const_iterator::valid() const
641 {
642  return m_offset != INVALID;
643 }
644 
645 
646 /**
647  * @brief Return a range that can be used to iterate over the container.
648  */
649 T_CHMIMPL
650 inline
651 typename CHMIMPL::const_iterator_range CHMIMPL::range() const
652 {
653  const Table& table = m_updater.get();
654  return const_iterator_range (const_iterator (table, false),
655  const_iterator (table, true));
656 }
657 
658 
659 /**
660  * @brief A begin iterator for the container.
661  */
662 T_CHMIMPL
663 inline
664 typename CHMIMPL::const_iterator CHMIMPL::begin() const
665 {
666  const Table& table = m_updater.get();
667  return const_iterator (table, false);
668 }
669 
670 
671 /**
672  * @brief An end iterator for the container.
673  */
674 T_CHMIMPL
675 inline
676 typename CHMIMPL::const_iterator CHMIMPL::end() const
677 {
678  const Table& table = m_updater.get();
679  return const_iterator (table, true);
680 }
681 
682 
683 /**
684  * @brief Erase the table and change the capacity.
685  * @param capacity The new table capacity.
686  * @param ctx Execution context.
687  *
688  * Returns an iterator pointing at the start of the old table.
689  */
690 T_CHMIMPL
691 typename CHMIMPL::const_iterator
692 CHMIMPL::clear (size_t capacity,
693  const typename Updater_t::Context_t& ctx)
694 {
695  capacity = round_up (capacity);
696  Lock_t lock (m_mutex);
697  std::unique_ptr<Table> new_table (new (capacity) Table (capacity,
698  m_hasher,
699  m_matcher));
700 
701  // Save an iterator to the old table.
702  const_iterator it = begin();
703 
704  // Publish the new table.
705  m_size = 0;
706  m_erased = 0;
707  // cppcheck-suppress danglingLifetime -- ok
708  m_table = new_table.get();
709  m_updater.update (std::move (new_table), ctx);
710  return it;
711 }
712 
713 
714 /**
715  * @brief Erase the table (don't change the capacity).
716  * @param ctx Execution context.
717  *
718  * Returns an iterator pointing at the start of the old table.
719  */
720 T_CHMIMPL
721 typename CHMIMPL::const_iterator
722 CHMIMPL::clear (const typename Updater_t::Context_t& ctx)
723 {
724  const Table& table = m_updater.get();
725  // Possible race here in that the container capacity could increase
726  // before we take out the lock in clear(). In practice, this should
727  // not actually be a problem.
728  return clear (table.capacity(), ctx);
729 }
730 
731 
732 /**
733  * @brief Erase the table by filling it with nulls.
734  *
735  * This method is not safe to use concurrently --- no other threads
736  * may be accessing the container at the same time, either for read
737  * or write.
738  */
739 T_CHMIMPL
740 void CHMIMPL::forceClear()
741 {
742  Lock_t lock (m_mutex);
743  size_t cap = m_table->capacity();
744  for (size_t i = 0; i < cap; i++) {
745  m_table->entry(i).m_key.store (nullval, std::memory_order_relaxed);
746  }
747  m_size = 0;
748  m_erased = 0;
749  std::atomic_thread_fence (std::memory_order_seq_cst);
750 }
751 
752 
753 /**
754  * @brief Increase the table capacity.
755  * @param capacity The new table capacity.
756  * @param ctx Execution context.
757  *
758  * No action will be taken if @c capacity is smaller
759  * than the current capacity.
760  */
761 T_CHMIMPL
762 void CHMIMPL::reserve (size_t capacity,
763  const typename Updater_t::Context_t& ctx)
764 {
765  Lock_t lock (m_mutex);
766  if (capacity < m_table->capacity()) return;
767  grow (lock, round_up (capacity), ctx);
768 }
769 
770 
771 /**
772  * @brief Called when this thread is no longer referencing anything
773  * from this container.
774  * @param ctx Execution context.
775  */
776 T_CHMIMPL
777 void CHMIMPL::quiescent (const typename Updater_t::Context_t& ctx)
778 {
779  m_updater.quiescent (ctx);
780 }
781 
782 
783 /**
784  * @brief Swap this container with another.
785  * @param other The container with which to swap.
786  *
787  * This will also call swap on the Updater object; hence, the Updater
788  * object must also support swap. The Hasher and Matcher instances
789  * are NOT swapped.
790  *
791  * This operation is NOT thread-safe. No other threads may be accessing
792  * either container during this operation.
793  */
794 T_CHMIMPL
795 void CHMIMPL::swap (ConcurrentHashmapImpl& other)
796 {
797  // Shouldn't be needed since we specified that no other threads can be
798  // accessing this...
799  Lock_t lock (m_mutex);
800  Lock_t lock_other (other.m_mutex);
801 
802  m_updater.swap (other.m_updater);
803  std::swap (m_table, other.m_table);
804 
805  auto swap_atomic = [] (std::atomic<size_t>& a, std::atomic<size_t>& b)
806  {
807  size_t tmp = a.load (std::memory_order_relaxed);
808  a.store (b.load (std::memory_order_relaxed),
809  std::memory_order_relaxed);
810  b.store (tmp, std::memory_order_relaxed);
811  };
812 
813  swap_atomic (m_size, other.m_size);
814  swap_atomic (m_erased, other.m_erased);
815 }
816 
817 
818 /**
819  * @brief Access the Updater instance.
820  */
821 T_CHMIMPL
822 inline
823 typename CHMIMPL::Updater_t& CHMIMPL::updater()
824 {
825  return m_updater;
826 }
827 
828 
829 /**
830  * @brief Make the table larger.
831  * @param ctx Execution context.
832  *
833  * Must be holding a lock on the mutex to call this.
834  */
835 T_CHMIMPL
836 bool CHMIMPL::grow (const Lock_t& lock, const typename Updater_t::Context_t& ctx)
837 {
838  // Allocate a new table with twice the capacity, unless there
839  // have been many erasures.
840  size_t new_capacity = m_erased >= m_table->capacity()/2 ?
841  m_table->capacity() : 2*m_table->capacity();
842  return grow (lock, new_capacity, ctx);
843 }
844 
845 
846 /**
847  * @brief Make the table larger.
848  * @param new_capacity The new table capacity (must be a power of 2).
849  * @param ctx Execution context.
850  *
851  * Must be holding a lock on the mutex to call this.
852  */
853 T_CHMIMPL
854 bool CHMIMPL::grow (const Lock_t& /*lock*/,
855  size_t new_capacity,
856  const typename Updater_t::Context_t& ctx)
857 {
858  // The current table.
859  const Table& table = *m_table;
860 
861  std::unique_ptr<Table> new_table (new (new_capacity) Table (new_capacity,
862  m_hasher,
863  m_matcher));
864 
865  // Copy data from the existing table to the new one.
866  size_t capacity = table.capacity();
867  for (size_t i = 0; i < capacity; i++) {
868  const entry_t& ent = table.entry(i);
869  if (ent.m_key != nullval) {
870  size_t hash = m_hasher (ent.m_key);
871  bool insert;
872  size_t offset = new_table->probeWrite (ent.m_key, hash, insert);
873  if (offset == INVALID) {
874  std::abort();
875  }
876  entry_t& new_entry = new_table->entry (offset);
877  new_entry.m_key = static_cast<val_t> (ent.m_key);
878  new_entry.m_val = static_cast<val_t> (ent.m_val);
879  }
880  }
881 
882  m_erased = 0;
883 
884  // Publish the new table.
885  // cppcheck-suppress danglingLifetime -- ok
886  m_table = new_table.get();
887  m_updater.update (std::move (new_table), ctx);
888 
889  return true;
890 }
891 
892 
893 /**
894  * @brief Round up to a power of 2.
895  */
896 T_CHMIMPL
897 uint64_t CHMIMPL::round_up (uint64_t x)
898 {
899  if (x <= 64) return 64;
900  return std::bit_ceil (x);
901 }
902 
903 
904 #undef CHMIMPL
905 #undef T_CHMIMPL
906 
907 
908 
909 } // namespace detail
910 } // namespace CxxUtils