2  * Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration.
 
    5  * @file CxxUtils/ConcurrentHashmapImpl.h
 
    6  * @author scott snyder <snyder@bnl.gov>
 
    8  * @brief Hash table allowing concurrent, lockless reads.
 
   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.
 
   27 template <unsigned ENTRIES_PER_CACHELINE>
 
   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),
 
   48  * @brief Offset of the element currently being probed.
 
   50 template <unsigned ENTRIES_PER_CACHELINE>
 
   52 size_t CHMTableIterator<ENTRIES_PER_CACHELINE>::offset() const
 
   54   // m_bucket the starting index of the current cache line.
 
   55   // Count within the cache line in a circular manner starting
 
   57   return m_bucket + ((m_nprobes + m_boffs)&ENTRIES_PER_CACHELINE_MASK);
 
   62  * @brief Return the number of probes performed so far.
 
   64 template <unsigned ENTRIES_PER_CACHELINE>
 
   66 size_t CHMTableIterator<ENTRIES_PER_CACHELINE>::nprobes() const
 
   73  * @brief Move to the next probe.
 
   74  * Returns true if we should continue, or false if we've hit the maximum
 
   77 template <unsigned ENTRIES_PER_CACHELINE>
 
   79 bool CHMTableIterator<ENTRIES_PER_CACHELINE>::next()
 
   81   // Increment number of probes and stop if we've hit the maximum.
 
   82   if (++m_nprobes >= m_probeLimit) {
 
   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;
 
   97 //*****************************************************************************
 
  101   template <template <class> class UPDATER_,     \
 
  104             uintptr_t NULLVAL_,                  \
 
  105             uintptr_t TOMBSTONE_>
 
  107 #define CHMIMPL ConcurrentHashmapImpl<UPDATER_, HASHER_, MATCHER_, NULLVAL_, TOMBSTONE_>
 
  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.
 
  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),
 
  123     m_maskBits (std::countr_zero (capacity)),
 
  128   // Clear all the keys.
 
  129   for (size_t i = 0; i < capacity; i++) {
 
  130     m_entries[i].m_key = nullval;
 
  136  * @brief Allocator for table objects.
 
  137  * @param capacity Size of the table (must be a power of 2).
 
  139  * Allocate with enough space for the table of entries.
 
  140  * Also align on a cache line.
 
  143 void* CHMIMPL::Table::operator new (size_t, size_t capacity)
 
  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();
 
  156  * @brief Deallocator for table objects.
 
  159 void CHMIMPL::Table::operator delete (void* p)
 
  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.
 
  170  * Returns the matching entry, or nullptr.
 
  173 size_t CHMIMPL::Table::probeRead (val_t key, size_t hash) const
 
  175   // Iterate over table probes.
 
  176   // We don't need to check more than the longest probe sequence
 
  178   TableIterator it (hash, m_mask, m_maskBits, m_longestProbe);
 
  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.
 
  186     if (m_matcher (ent->m_key, key)) {
 
  187       // Found a matching key.
 
  191   // Too many probes --- return failure.
 
  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.
 
  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.
 
  208 size_t CHMIMPL::Table::probeWrite (val_t key, size_t hash, bool& insert)
 
  210   // Iterate over table probes.
 
  211   TableIterator it (hash, m_mask, m_maskBits, m_maxProbe);
 
  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);
 
  222     if (m_matcher (ent->m_key, key)) {
 
  223       // Found a matching key.
 
  228   // Too many probes --- return failure.
 
  234  * @brief The number of entries in the table.
 
  238 size_t CHMIMPL::Table::capacity() const
 
  245  * @brief Return the entry for an offset.
 
  246  * @param offset The index of the desired entry.
 
  250 const typename CHMIMPL::entry_t& CHMIMPL::Table::entry (size_t offset) const
 
  252   return m_entries[offset];
 
  257  * @brief Return the entry for an offset (non-const).
 
  258  * @param offset The index of the desired entry.
 
  262 typename CHMIMPL::entry_t& CHMIMPL::Table::entry (size_t offset)
 
  264   return m_entries[offset];
 
  268 //*****************************************************************************
 
  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.
 
  281 CHMIMPL::ConcurrentHashmapImpl (Updater_t&& updater,
 
  283                                 const Hasher_t& hasher,
 
  284                                 const Matcher_t& matcher,
 
  285                                 const typename Updater_t::Context_t& ctx)
 
  286   : m_updater (std::move (updater)),
 
  292   // Round up capacity to a power of 2.
 
  293   size_t capacity = round_up (capacity_in);
 
  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);
 
  302  * @brief Take a lock on the container.
 
  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.
 
  311 typename CHMIMPL::Lock_t CHMIMPL::lock()
 
  313   return Lock_t (m_mutex);
 
  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.
 
  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.
 
  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)
 
  337   assert (key != nullval && key != tombstone);
 
  341     size_t offset = m_table->probeWrite (key, hash, insert);
 
  342     if (offset != INVALID) {
 
  343       entry_t& ent = m_table->entry (offset);
 
  345         // Found a place to put it.
 
  346         // Be sure not to set the key until the value is in place.
 
  352         // Found --- update the entry if wanted.
 
  354           if (val != ent.m_val) {
 
  359       return std::make_pair (const_iterator (*m_table, offset), insert);
 
  362     // Need to grow the table.
 
  363   } while (grow (lock, ctx));
 
  366   return std::make_pair (end(), false);
 
  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.
 
  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.
 
  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)
 
  390   return put (l, key, hash, val, overwrite, ctx);
 
  395  * @brief Look up an entry in the table.
 
  396  * @param key The key to find.
 
  397  * @param hash The hash of the key.
 
  399  * Returns an iterator pointing at the found entry, or end().
 
  402 typename CHMIMPL::const_iterator CHMIMPL::get (val_t key, size_t hash) const
 
  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);
 
  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.
 
  417  * Mark the corresponding entry as deleted.
 
  418  * Return true on success, false on failure (key not found).
 
  420  * The tombstone value must be different from the null value.
 
  422  * Take care if the key or value types require memory allocation.
 
  424  * This may cause the key type returned by an iterator to change
 
  425  * asynchronously to the tombstone value.
 
  429 CHMIMPL::erase (const Lock_t& /*lock*/, val_t key, size_t hash)
 
  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);
 
  438     ent.m_key = tombstone;
 
  446  * @brief Erase an entry from the table.
 
  447  * @param key The key to find.
 
  448  * @param hash The hash of the key.
 
  450  * Mark the corresponding entry as deleted.
 
  451  * Return true on success, false on failure (key not found).
 
  453  * The tombstone value must be different from the null value.
 
  455  * Take care if the key or value types require memory allocation.
 
  457  * This may cause the key type returned by an iterator to change
 
  458  * asynchronously to the tombstone value.
 
  463 CHMIMPL::erase (val_t key, size_t hash)
 
  465   return erase (lock(), key, hash);
 
  470  * @brief Return the number of items currently stored.
 
  473 size_t CHMIMPL::size() const
 
  480  * @brief Return the current table size.
 
  483 size_t CHMIMPL::capacity() const
 
  485   const Table& table = m_updater.get();
 
  486   return table.capacity();
 
  491  * @brief The number of erased elements in the current table.
 
  494 size_t CHMIMPL::erased() const
 
  501  * @brief Return the hasher object.
 
  505 const typename CHMIMPL::Hasher_t& CHMIMPL::hasher() const
 
  512  * @brief Return the matcher object.
 
  516 const typename CHMIMPL::Matcher_t& CHMIMPL::matcher() const
 
  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.
 
  530 CHMIMPL::const_iterator::const_iterator (const Table& table, bool end)
 
  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.
 
  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.)
 
  549 CHMIMPL::const_iterator::const_iterator (const Table& table, size_t offset)
 
  553   assert (offset == INVALID || m_table.entry (offset).m_key != nullval);
 
  558  * @brief Advance the iterator to the next occupied entry.
 
  562 void CHMIMPL::const_iterator::next()
 
  567     if (m_offset >= m_table.capacity()) {
 
  571     key = m_table.entry (m_offset).m_key;
 
  572   } while (key == nullval || key == tombstone);
 
  577  * @brief Move the iterator back to the previous occupied entry.
 
  581 void CHMIMPL::const_iterator::prev()
 
  583   if (m_offset == INVALID) {
 
  584     m_offset = m_table.capacity();
 
  589     if (m_offset >= m_table.capacity()) {
 
  593     key = m_table.entry (m_offset).m_key;
 
  594   } while (key == nullval || key == tombstone);
 
  599  * @brief Return the key for this iterator.
 
  600  *        If deletions are allowed, then the key may change asynchronously
 
  601  *        to the tombstone value.
 
  605 typename CHMIMPL::val_t CHMIMPL::const_iterator::key() const
 
  607   return m_table.entry (m_offset).m_key;
 
  612  * @brief Return the value for this iterator.
 
  616 typename CHMIMPL::val_t CHMIMPL::const_iterator::value() const
 
  618   return m_table.entry (m_offset).m_val;
 
  623  * @brief Compare two iterators.
 
  627 bool CHMIMPL::const_iterator::operator!= (const const_iterator& other) const
 
  629   if (m_offset != other.m_offset) return true;
 
  630   if (m_offset == INVALID) return false;
 
  631   return &m_table != &other.m_table;
 
  636  * @brief Check that the iterator is valid (not pointing at the end).
 
  640 bool CHMIMPL::const_iterator::valid() const
 
  642   return m_offset != INVALID;
 
  647  * @brief Return a range that can be used to iterate over the container.
 
  651 typename CHMIMPL::const_iterator_range CHMIMPL::range() const
 
  653   const Table& table = m_updater.get();
 
  654   return const_iterator_range (const_iterator (table, false),
 
  655                                const_iterator (table, true));
 
  660  * @brief A begin iterator for the container.
 
  664 typename CHMIMPL::const_iterator CHMIMPL::begin() const
 
  666   const Table& table = m_updater.get();
 
  667   return const_iterator (table, false);
 
  672  * @brief An end iterator for the container.
 
  676 typename CHMIMPL::const_iterator CHMIMPL::end() const
 
  678   const Table& table = m_updater.get();
 
  679   return const_iterator (table, true);
 
  684  * @brief Erase the table and change the capacity.
 
  685  * @param capacity The new table capacity.
 
  686  * @param ctx Execution context.
 
  688  * Returns an iterator pointing at the start of the old table.
 
  691 typename CHMIMPL::const_iterator
 
  692 CHMIMPL::clear (size_t capacity,
 
  693                 const typename Updater_t::Context_t& ctx)
 
  695   capacity = round_up (capacity);
 
  696   Lock_t lock (m_mutex);
 
  697   std::unique_ptr<Table> new_table (new (capacity) Table (capacity,
 
  701   // Save an iterator to the old table.
 
  702   const_iterator it = begin();
 
  704   // Publish the new table.
 
  707   // cppcheck-suppress danglingLifetime -- ok
 
  708   m_table = new_table.get();
 
  709   m_updater.update (std::move (new_table), ctx);
 
  715  * @brief Erase the table (don't change the capacity).
 
  716  * @param ctx Execution context.
 
  718  * Returns an iterator pointing at the start of the old table.
 
  721 typename CHMIMPL::const_iterator
 
  722 CHMIMPL::clear (const typename Updater_t::Context_t& ctx)
 
  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);
 
  733  * @brief Erase the table by filling it with nulls.
 
  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
 
  740 void CHMIMPL::forceClear()
 
  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);
 
  749   std::atomic_thread_fence (std::memory_order_seq_cst);
 
  754  * @brief Increase the table capacity.
 
  755  * @param capacity The new table capacity.
 
  756  * @param ctx Execution context.
 
  758  * No action will be taken if @c capacity is smaller
 
  759  * than the current capacity.
 
  762 void CHMIMPL::reserve (size_t capacity,
 
  763                        const typename Updater_t::Context_t& ctx)
 
  765   Lock_t lock (m_mutex);
 
  766   if (capacity < m_table->capacity()) return;
 
  767   grow (lock, round_up (capacity), ctx);
 
  772  * @brief Called when this thread is no longer referencing anything
 
  773  *        from this container.
 
  774  * @param ctx Execution context.
 
  777 void CHMIMPL::quiescent (const typename Updater_t::Context_t& ctx)
 
  779   m_updater.quiescent (ctx);
 
  784  * @brief Swap this container with another.
 
  785  * @param other The container with which to swap.
 
  787  * This will also call swap on the Updater object; hence, the Updater
 
  788  * object must also support swap.  The Hasher and Matcher instances
 
  791  * This operation is NOT thread-safe.  No other threads may be accessing
 
  792  * either container during this operation.
 
  795 void CHMIMPL::swap (ConcurrentHashmapImpl& other)
 
  797   // Shouldn't be needed since we specified that no other threads can be
 
  799   Lock_t lock (m_mutex);
 
  800   Lock_t lock_other (other.m_mutex);
 
  802   m_updater.swap (other.m_updater);
 
  803   std::swap (m_table, other.m_table);
 
  805   auto swap_atomic = [] (std::atomic<size_t>& a, std::atomic<size_t>& b)
 
  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);
 
  813   swap_atomic (m_size, other.m_size);
 
  814   swap_atomic (m_erased, other.m_erased);
 
  819  * @brief Access the Updater instance.
 
  823 typename CHMIMPL::Updater_t& CHMIMPL::updater()
 
  830  * @brief Make the table larger.
 
  831  * @param ctx Execution context.
 
  833  * Must be holding a lock on the mutex to call this.
 
  836 bool CHMIMPL::grow (const Lock_t& lock, const typename Updater_t::Context_t& ctx)
 
  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);
 
  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.
 
  851  * Must be holding a lock on the mutex to call this.
 
  854 bool CHMIMPL::grow (const Lock_t& /*lock*/,
 
  856                     const typename Updater_t::Context_t& ctx)
 
  858   // The current table.
 
  859   const Table& table = *m_table;
 
  861   std::unique_ptr<Table> new_table (new (new_capacity) Table (new_capacity,
 
  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);
 
  872       size_t offset = new_table->probeWrite (ent.m_key, hash, insert);
 
  873       if (offset == INVALID) {
 
  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);
 
  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);
 
  894  * @brief Round up to a power of 2.
 
  897 uint64_t CHMIMPL::round_up (uint64_t x)
 
  899   if (x <= 64) return 64;
 
  900   return std::bit_ceil (x);
 
  909 } // namespace detail
 
  910 } // namespace CxxUtils