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  m_table = new (capacity) Table (capacity, hasher, matcher);
296  m_updater.update (std::unique_ptr<Table> (m_table), ctx);
297 }
298 
299 
300 /**
301  * @brief Take a lock on the container.
302  *
303  * Take a lock on the container.
304  * The lock can then be passed to put(), allowing to factor out the locking
305  * when put() gets used in a loop. The lock will be released when the
306  * lock object is destroyed.
307  */
308 T_CHMIMPL
309 inline
310 typename CHMIMPL::Lock_t CHMIMPL::lock()
311 {
312  return Lock_t (m_mutex);
313 }
314 
315 
316 /**
317  * @brief Add an entry to the table, with external locking.
318  * @param lock The lock object returned from lock().
319  * @param key The key to insert.
320  * @param hash The hash of the key.
321  * @param val The value to insert.
322  * @param overwrite If true, then overwrite an existing entry.
323  * If false, an existing entry will not be changed.
324  * @param ctx Execution context.
325  *
326  * If the key already exists, then its value will be updated.
327  * Returns an iterator pointing at the entry and a flag which is
328  * true if a new element was added.
329  */
330 T_CHMIMPL
331 std::pair<typename CHMIMPL::const_iterator, bool>
332 CHMIMPL::put (const Lock_t& lock,
333  val_t key, size_t hash, val_t val, bool overwrite,
334  const typename Updater_t::Context_t& ctx)
335 {
336  assert (key != nullval && key != tombstone);
337 
338  do {
339  bool insert;
340  size_t offset = m_table->probeWrite (key, hash, insert);
341  if (offset != INVALID) {
342  entry_t& ent = m_table->entry (offset);
343  if (insert) {
344  // Found a place to put it.
345  // Be sure not to set the key until the value is in place.
346  ent.m_val = val;
347  ent.m_key = key;
348  ++m_size;
349  }
350  else {
351  // Found --- update the entry if wanted.
352  if (overwrite) {
353  if (val != ent.m_val) {
354  ent.m_val = val;
355  }
356  }
357  }
358  return std::make_pair (const_iterator (*m_table, offset), insert);
359  }
360 
361  // Need to grow the table.
362  } while (grow (lock, ctx));
363 
364  // grow() failed.
365  return std::make_pair (end(), false);
366 }
367 
368 
369 /**
370  * @brief Add an entry to the table.
371  * @param key The key to insert.
372  * @param hash The hash of the key.
373  * @param val The value to insert.
374  * @param overwrite If true, then overwrite an existing entry.
375  * If false, an existing entry will not be changed.
376  * @param ctx Execution context.
377  *
378  * If the key already exists, then its value will be updated.
379  * Returns an iterator pointing at the entry and a flag which is
380  * true if a new element was added.
381  */
382 T_CHMIMPL
383 inline
384 std::pair<typename CHMIMPL::const_iterator, bool>
385 CHMIMPL::put (val_t key, size_t hash, val_t val, bool overwrite,
386  const typename Updater_t::Context_t& ctx)
387 {
388  Lock_t l (m_mutex);
389  return put (l, key, hash, val, overwrite, ctx);
390 }
391 
392 
393 /**
394  * @brief Look up an entry in the table.
395  * @param key The key to find.
396  * @param hash The hash of the key.
397  *
398  * Returns an iterator pointing at the found entry, or end().
399  */
400 T_CHMIMPL
401 typename CHMIMPL::const_iterator CHMIMPL::get (val_t key, size_t hash) const
402 {
403  const Table& table = m_updater.get();
404  size_t offset = table.probeRead (key, hash);
405  // Offset will be -1 if not found --- invalid iterator.
406  return const_iterator (table, offset);
407 }
408 
409 
410 /**
411  * @brief Erase an entry from the table, with external locking.
412  * @param lock The lock object returned from lock().
413  * @param key The key to find.
414  * @param hash The hash of the key.
415  *
416  * Mark the corresponding entry as deleted.
417  * Return true on success, false on failure (key not found).
418  *
419  * The tombstone value must be different from the null value.
420  *
421  * Take care if the key or value types require memory allocation.
422  *
423  * This may cause the key type returned by an iterator to change
424  * asynchronously to the tombstone value.
425  **/
426 T_CHMIMPL
427 bool
428 CHMIMPL::erase (const Lock_t& /*lock*/, val_t key, size_t hash)
429 {
430  static_assert (nullval != tombstone);
431  const Table& table = m_updater.get();
432  size_t offset = table.probeRead (key, hash);
433  if (offset != INVALID) {
434  entry_t& ent = m_table->entry (offset);
435  ++m_erased;
436  --m_size;
437  ent.m_key = tombstone;
438  return true;
439  }
440  return false;
441 }
442 
443 
444 /**
445  * @brief Erase an entry from the table.
446  * @param key The key to find.
447  * @param hash The hash of the key.
448  *
449  * Mark the corresponding entry as deleted.
450  * Return true on success, false on failure (key not found).
451  *
452  * The tombstone value must be different from the null value.
453  *
454  * Take care if the key or value types require memory allocation.
455  *
456  * This may cause the key type returned by an iterator to change
457  * asynchronously to the tombstone value.
458  **/
459 T_CHMIMPL
460 inline
461 bool
462 CHMIMPL::erase (val_t key, size_t hash)
463 {
464  return erase (lock(), key, hash);
465 }
466 
467 
468 /**
469  * @brief Return the number of items currently stored.
470  */
471 T_CHMIMPL
472 size_t CHMIMPL::size() const
473 {
474  return m_size;
475 }
476 
477 
478 /**
479  * @brief Return the current table size.
480  */
481 T_CHMIMPL
482 size_t CHMIMPL::capacity() const
483 {
484  const Table& table = m_updater.get();
485  return table.capacity();
486 }
487 
488 
489 /**
490  * @brief The number of erased elements in the current table.
491  */
492 T_CHMIMPL
493 size_t CHMIMPL::erased() const
494 {
495  return m_erased;
496 }
497 
498 
499 /**
500  * @brief Return the hasher object.
501  */
502 T_CHMIMPL
503 inline
504 const typename CHMIMPL::Hasher_t& CHMIMPL::hasher() const
505 {
506  return m_hasher;
507 }
508 
509 
510 /**
511  * @brief Return the matcher object.
512  */
513 T_CHMIMPL
514 inline
515 const typename CHMIMPL::Matcher_t& CHMIMPL::matcher() const
516 {
517  return m_matcher;
518 }
519 
520 
521 /**
522  * @brief Constructor.
523  * @param table The table instance we're referencing.
524  * @param end If true, initialize this to an end iterator.
525  * Otherwise, initialize it to a a begin iterator.
526  */
527 T_CHMIMPL
528 inline
529 CHMIMPL::const_iterator::const_iterator (const Table& table, bool end)
530  : m_table (table),
531  m_offset (INVALID)
532 {
533  // For an end iterator, we want offset to be -1.
534  // For a begin iterator, we need to advance to the first non-null entry.
535  if (!end) {
536  next();
537  }
538 }
539 
540 
541 /**
542  * @brief Constructor.
543  * @param table The table instance we're referencing.
544  * @param offset Offset of the iterator within the table.
545  * (Must point at an occupied entry.)
546  */
547 T_CHMIMPL
548 CHMIMPL::const_iterator::const_iterator (const Table& table, size_t offset)
549  : m_table (table),
550  m_offset (offset)
551 {
552  assert (offset == INVALID || m_table.entry (offset).m_key != nullval);
553 }
554 
555 
556 /**
557  * @brief Advance the iterator to the next occupied entry.
558  */
559 T_CHMIMPL
560 inline
561 void CHMIMPL::const_iterator::next()
562 {
563  val_t key;
564  do {
565  ++m_offset;
566  if (m_offset >= m_table.capacity()) {
567  m_offset = INVALID;
568  break;
569  }
570  key = m_table.entry (m_offset).m_key;
571  } while (key == nullval || key == tombstone);
572 }
573 
574 
575 /**
576  * @brief Move the iterator back to the previous occupied entry.
577  */
578 T_CHMIMPL
579 inline
580 void CHMIMPL::const_iterator::prev()
581 {
582  if (m_offset == INVALID) {
583  m_offset = m_table.capacity();
584  }
585  val_t key;
586  do {
587  --m_offset;
588  if (m_offset >= m_table.capacity()) {
589  m_offset = INVALID;
590  break;
591  }
592  key = m_table.entry (m_offset).m_key;
593  } while (key == nullval || key == tombstone);
594 }
595 
596 
597 /**
598  * @brief Return the key for this iterator.
599  * If deletions are allowed, then the key may change asynchronously
600  * to the tombstone value.
601  */
602 T_CHMIMPL
603 inline
604 typename CHMIMPL::val_t CHMIMPL::const_iterator::key() const
605 {
606  return m_table.entry (m_offset).m_key;
607 }
608 
609 
610 /**
611  * @brief Return the value for this iterator.
612  */
613 T_CHMIMPL
614 inline
615 typename CHMIMPL::val_t CHMIMPL::const_iterator::value() const
616 {
617  return m_table.entry (m_offset).m_val;
618 }
619 
620 
621 /**
622  * @brief Compare two iterators.
623  */
624 T_CHMIMPL
625 inline
626 bool CHMIMPL::const_iterator::operator!= (const const_iterator& other) const
627 {
628  if (m_offset != other.m_offset) return true;
629  if (m_offset == INVALID) return false;
630  return &m_table != &other.m_table;
631 }
632 
633 
634 /**
635  * @brief Check that the iterator is valid (not pointing at the end).
636  */
637 T_CHMIMPL
638 inline
639 bool CHMIMPL::const_iterator::valid() const
640 {
641  return m_offset != INVALID;
642 }
643 
644 
645 /**
646  * @brief Return a range that can be used to iterate over the container.
647  */
648 T_CHMIMPL
649 inline
650 typename CHMIMPL::const_iterator_range CHMIMPL::range() const
651 {
652  const Table& table = m_updater.get();
653  return const_iterator_range (const_iterator (table, false),
654  const_iterator (table, true));
655 }
656 
657 
658 /**
659  * @brief A begin iterator for the container.
660  */
661 T_CHMIMPL
662 inline
663 typename CHMIMPL::const_iterator CHMIMPL::begin() const
664 {
665  const Table& table = m_updater.get();
666  return const_iterator (table, false);
667 }
668 
669 
670 /**
671  * @brief An end iterator for the container.
672  */
673 T_CHMIMPL
674 inline
675 typename CHMIMPL::const_iterator CHMIMPL::end() const
676 {
677  const Table& table = m_updater.get();
678  return const_iterator (table, true);
679 }
680 
681 
682 /**
683  * @brief Erase the table and change the capacity.
684  * @param capacity The new table capacity.
685  * @param ctx Execution context.
686  *
687  * Returns an iterator pointing at the start of the old table.
688  */
689 T_CHMIMPL
690 typename CHMIMPL::const_iterator
691 CHMIMPL::clear (size_t capacity,
692  const typename Updater_t::Context_t& ctx)
693 {
694  capacity = round_up (capacity);
695  Lock_t lock (m_mutex);
696  std::unique_ptr<Table> new_table (new (capacity) Table (capacity,
697  m_hasher,
698  m_matcher));
699 
700  // Save an iterator to the old table.
701  const_iterator it = begin();
702 
703  // Publish the new table.
704  m_size = 0;
705  m_erased = 0;
706  m_table = new_table.get();
707  m_updater.update (std::move (new_table), ctx);
708  return it;
709 }
710 
711 
712 /**
713  * @brief Erase the table (don't change the capacity).
714  * @param ctx Execution context.
715  *
716  * Returns an iterator pointing at the start of the old table.
717  */
718 T_CHMIMPL
719 typename CHMIMPL::const_iterator
720 CHMIMPL::clear (const typename Updater_t::Context_t& ctx)
721 {
722  const Table& table = m_updater.get();
723  // Possible race here in that the container capacity could increase
724  // before we take out the lock in clear(). In practice, this should
725  // not actually be a problem.
726  return clear (table.capacity(), ctx);
727 }
728 
729 
730 /**
731  * @brief Erase the table by filling it with nulls.
732  *
733  * This method is not safe to use concurrently --- no other threads
734  * may be accessing the container at the same time, either for read
735  * or write.
736  */
737 T_CHMIMPL
738 void CHMIMPL::forceClear()
739 {
740  Lock_t lock (m_mutex);
741  size_t cap = m_table->capacity();
742  for (size_t i = 0; i < cap; i++) {
743  m_table->entry(i).m_key.store (nullval, std::memory_order_relaxed);
744  }
745  m_size = 0;
746  m_erased = 0;
747  std::atomic_thread_fence (std::memory_order_seq_cst);
748 }
749 
750 
751 /**
752  * @brief Increase the table capacity.
753  * @param capacity The new table capacity.
754  * @param ctx Execution context.
755  *
756  * No action will be taken if @c capacity is smaller
757  * than the current capacity.
758  */
759 T_CHMIMPL
760 void CHMIMPL::reserve (size_t capacity,
761  const typename Updater_t::Context_t& ctx)
762 {
763  Lock_t lock (m_mutex);
764  if (capacity < m_table->capacity()) return;
765  grow (lock, round_up (capacity), ctx);
766 }
767 
768 
769 /**
770  * @brief Called when this thread is no longer referencing anything
771  * from this container.
772  * @param ctx Execution context.
773  */
774 T_CHMIMPL
775 void CHMIMPL::quiescent (const typename Updater_t::Context_t& ctx)
776 {
777  m_updater.quiescent (ctx);
778 }
779 
780 
781 /**
782  * @brief Swap this container with another.
783  * @param other The container with which to swap.
784  *
785  * This will also call swap on the Updater object; hence, the Updater
786  * object must also support swap. The Hasher and Matcher instances
787  * are NOT swapped.
788  *
789  * This operation is NOT thread-safe. No other threads may be accessing
790  * either container during this operation.
791  */
792 T_CHMIMPL
793 void CHMIMPL::swap (ConcurrentHashmapImpl& other)
794 {
795  // Shouldn't be needed since we specified that no other threads can be
796  // accessing this...
797  Lock_t lock (m_mutex);
798  Lock_t lock_other (other.m_mutex);
799 
800  m_updater.swap (other.m_updater);
801  std::swap (m_table, other.m_table);
802 
803  auto swap_atomic = [] (std::atomic<size_t>& a, std::atomic<size_t>& b)
804  {
805  size_t tmp = a.load (std::memory_order_relaxed);
806  a.store (b.load (std::memory_order_relaxed),
807  std::memory_order_relaxed);
808  b.store (tmp, std::memory_order_relaxed);
809  };
810 
811  swap_atomic (m_size, other.m_size);
812  swap_atomic (m_erased, other.m_erased);
813 }
814 
815 
816 /**
817  * @brief Access the Updater instance.
818  */
819 T_CHMIMPL
820 inline
821 typename CHMIMPL::Updater_t& CHMIMPL::updater()
822 {
823  return m_updater;
824 }
825 
826 
827 /**
828  * @brief Make the table larger.
829  * @param ctx Execution context.
830  *
831  * Must be holding a lock on the mutex to call this.
832  */
833 T_CHMIMPL
834 bool CHMIMPL::grow (const Lock_t& lock, const typename Updater_t::Context_t& ctx)
835 {
836  // Allocate a new table with twice the capacity, unless there
837  // have been many erasures.
838  size_t new_capacity = m_erased >= m_table->capacity()/2 ?
839  m_table->capacity() : 2*m_table->capacity();
840  return grow (lock, new_capacity, ctx);
841 }
842 
843 
844 /**
845  * @brief Make the table larger.
846  * @param new_capacity The new table capacity (must be a power of 2).
847  * @param ctx Execution context.
848  *
849  * Must be holding a lock on the mutex to call this.
850  */
851 T_CHMIMPL
852 bool CHMIMPL::grow (const Lock_t& /*lock*/,
853  size_t new_capacity,
854  const typename Updater_t::Context_t& ctx)
855 {
856  // The current table.
857  const Table& table = *m_table;
858 
859  std::unique_ptr<Table> new_table (new (new_capacity) Table (new_capacity,
860  m_hasher,
861  m_matcher));
862 
863  // Copy data from the existing table to the new one.
864  size_t capacity = table.capacity();
865  for (size_t i = 0; i < capacity; i++) {
866  const entry_t& ent = table.entry(i);
867  if (ent.m_key != nullval) {
868  size_t hash = m_hasher (ent.m_key);
869  bool insert;
870  size_t offset = new_table->probeWrite (ent.m_key, hash, insert);
871  if (offset == INVALID) {
872  std::abort();
873  }
874  entry_t& new_entry = new_table->entry (offset);
875  new_entry.m_key = static_cast<val_t> (ent.m_key);
876  new_entry.m_val = static_cast<val_t> (ent.m_val);
877  }
878  }
879 
880  m_erased = 0;
881 
882  // Publish the new table.
883  m_table = new_table.get();
884  m_updater.update (std::move (new_table), ctx);
885 
886  return true;
887 }
888 
889 
890 /**
891  * @brief Round up to a power of 2.
892  */
893 T_CHMIMPL
894 uint64_t CHMIMPL::round_up (uint64_t x)
895 {
896  if (x <= 64) return 64;
897  return std::bit_ceil (x);
898 }
899 
900 
901 #undef CHMIMPL
902 #undef T_CHMIMPL
903 
904 
905 
906 } // namespace detail
907 } // namespace CxxUtils