ATLAS Offline Software
Loading...
Searching...
No Matches
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
16namespace CxxUtils {
17namespace 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 */
27template <unsigned ENTRIES_PER_CACHELINE>
28inline
29CHMTableIterator<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 */
50template <unsigned ENTRIES_PER_CACHELINE>
51inline
52size_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 */
64template <unsigned ENTRIES_PER_CACHELINE>
65inline
66size_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 */
77template <unsigned ENTRIES_PER_CACHELINE>
78inline
79bool 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 */
116T_CHMIMPL
117CHMIMPL::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 */
142T_CHMIMPL
143void* 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 */
158T_CHMIMPL
159void 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 */
172T_CHMIMPL
173size_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 */
207T_CHMIMPL
208size_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 */
236T_CHMIMPL
237inline
238size_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 */
248T_CHMIMPL
249inline
250const 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 */
260T_CHMIMPL
261inline
262typename 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 */
280T_CHMIMPL
281CHMIMPL::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 */
309T_CHMIMPL
310inline
311typename 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 */
331T_CHMIMPL
332std::pair<typename CHMIMPL::const_iterator, bool>
333CHMIMPL::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 */
383T_CHMIMPL
384inline
385std::pair<typename CHMIMPL::const_iterator, bool>
386CHMIMPL::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 */
401T_CHMIMPL
402typename 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 **/
427T_CHMIMPL
428bool
429CHMIMPL::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 **/
460T_CHMIMPL
461inline
462bool
463CHMIMPL::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 */
472T_CHMIMPL
473size_t CHMIMPL::size() const
474{
475 return m_size;
476}
477
478
479/**
480 * @brief Return the current table size.
481 */
482T_CHMIMPL
483size_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 */
493T_CHMIMPL
494size_t CHMIMPL::erased() const
495{
496 return m_erased;
497}
498
499
500/**
501 * @brief Return the hasher object.
502 */
503T_CHMIMPL
504inline
505const typename CHMIMPL::Hasher_t& CHMIMPL::hasher() const
506{
507 return m_hasher;
508}
509
510
511/**
512 * @brief Return the matcher object.
513 */
514T_CHMIMPL
515inline
516const 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 */
528T_CHMIMPL
529inline
530CHMIMPL::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 */
548T_CHMIMPL
549CHMIMPL::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 */
560T_CHMIMPL
561inline
562void 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 */
579T_CHMIMPL
580inline
581void 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 */
603T_CHMIMPL
604inline
605typename 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 */
614T_CHMIMPL
615inline
616typename 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 */
625T_CHMIMPL
626inline
627bool 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 */
638T_CHMIMPL
639inline
640bool 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 */
649T_CHMIMPL
650inline
651typename 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 */
662T_CHMIMPL
663inline
664typename 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 */
674T_CHMIMPL
675inline
676typename 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 */
690T_CHMIMPL
691typename CHMIMPL::const_iterator
692CHMIMPL::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 */
720T_CHMIMPL
721typename CHMIMPL::const_iterator
722CHMIMPL::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 */
739T_CHMIMPL
740void 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 */
761T_CHMIMPL
762void 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 */
776T_CHMIMPL
777void 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 */
794T_CHMIMPL
795void 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 */
821T_CHMIMPL
822inline
823typename 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 */
835T_CHMIMPL
836bool 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 */
853T_CHMIMPL
854bool 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 */
896T_CHMIMPL
897uint64_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