ATLAS Offline Software
Loading...
Searching...
No Matches
ConcurrentHashmapImpl.h
Go to the documentation of this file.
1// This file's extension implies that it's C, but it's really -*- C++ -*-.
2/*
3 * Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration.
4 */
11
12
13
14#ifndef CXXUTILS_CONCURRENTHASHMAPIMPL_H
15#define CXXUTILS_CONCURRENTHASHMAPIMPL_H
16
17
19#include "CxxUtils/concepts.h"
20#include <functional>
21#include <cstdint>
22#include <cstdlib>
23#include <atomic>
24#include <mutex>
25#include <memory>
26#include <new>
27
28
29class ConcurrentHashmapImplTest;
30
31
32namespace CxxUtils {
33namespace detail {
34
35
40using ConcurrentHashmapVal_t = uintptr_t;
41
42
48template <class T>
49concept IsConcurrentHashmapPayload = std::is_standard_layout_v<T> &&
50 std::is_trivial_v<T> &&
51 sizeof (T) <= sizeof (ConcurrentHashmapVal_t);
52
53
58 : public std::unique_lock<std::mutex>
59{
60public:
61 using std::unique_lock<std::mutex>::unique_lock;
62};
63
64
83template <unsigned ENTRIES_PER_CACHELINE>
85{
87 static constexpr size_t ENTRIES_PER_CACHELINE_MASK = ENTRIES_PER_CACHELINE-1;
88
96 CHMTableIterator (size_t hash, size_t mask, size_t maskBits, size_t probeLimit);
97
98
102 size_t offset() const;
103
104
108 size_t nprobes() const;
109
110
116 bool next();
117
118
119private:
122 const size_t m_mask;
124 const size_t m_boffs;
126 const size_t m_stride;
128 const size_t m_probeLimit;
130 size_t m_bucket;
132 size_t m_nprobes;
133};
134
135
194template <template <class> class UPDATER_,
195 typename HASHER_ = std::hash<uintptr_t>,
196 typename MATCHER_ = std::equal_to<uintptr_t>,
197 uintptr_t NULLVAL_ = 0,
198 uintptr_t TOMBSTONE_ = NULLVAL_>
200{
201public:
203 using val_t = ConcurrentHashmapVal_t;
205 using Hasher_t = HASHER_;
207 using Matcher_t = MATCHER_;
209 static constexpr uintptr_t nullval = NULLVAL_;
211 static constexpr uintptr_t tombstone = TOMBSTONE_;
213 static constexpr size_t INVALID = static_cast<size_t>(-1);
214
216 using Lock_t = HashmapLock;
217
218
219private:
221 struct entry_t {
222 std::atomic<val_t> m_key;
223 std::atomic<val_t> m_val;
224 };
225
227 static constexpr size_t CACHELINE = 64;
228
229 // Ensure that entries will evenly pack a cache line.
230 // If CACHELINE is a power of two, this implies that sizeof(entry_t)
231 // is as well.
232 static_assert (CACHELINE >= sizeof (entry_t) &&
233 CACHELINE % sizeof(entry_t) == 0);
234
236 static constexpr size_t ENTRIES_PER_CACHELINE = CACHELINE / sizeof(entry_t);
238 static constexpr size_t ENTRIES_PER_CACHELINE_MASK = (ENTRIES_PER_CACHELINE-1);
239
241 friend class ::ConcurrentHashmapImplTest;
242
243
253 class alignas(CACHELINE) Table
254 {
255 public:
257
258
266 const Hasher_t& hasher = Hasher_t(),
267 const Matcher_t& matcher = Matcher_t());
268
269
277 static void* operator new (size_t, size_t capacity);
278
279
283 void operator delete (void* p);
284
285
293 size_t probeRead (val_t key, size_t hash) const;
294
295
307 size_t probeWrite (val_t key, size_t hash, bool& insert);
308
309
313 size_t capacity() const;
314
315
320 const entry_t& entry (size_t offset) const;
321
322
327 entry_t& entry (size_t offset);
328
329
330 private:
332 const size_t m_capacity;
334 const size_t m_maxProbe;
336 const size_t m_mask;
338 const size_t m_maskBits;
340 const Hasher_t& m_hasher;
342 const Matcher_t& m_matcher;
344 std::atomic<size_t> m_longestProbe;
346 alignas(CACHELINE) entry_t m_entries[1];
347 };
348
349
350public:
352 using Updater_t = UPDATER_<Table>;
354 using Context_t = typename Updater_t::Context_t;
355
356
357
368 size_t capacity_in,
369 const Hasher_t& hasher,
370 const Matcher_t& matcher,
371 const typename Updater_t::Context_t& ctx);
372
373
374 // Don't implement copying.
375 // This should be done by derived classes, if desired.
378
379
384 size_t size() const;
385
386
390 size_t capacity() const;
391
392
396 size_t erased() const;
397
398
402 const Hasher_t& hasher() const;
403
404
408 const Matcher_t& matcher() const;
409
410
418 {
419 public:
426 const_iterator (const Table& table, bool end);
427
428
435 const_iterator (const Table& table, size_t offset);
436
437
441 void next();
442
443
447 void prev();
448
449
455 val_t key() const;
456
457
461 val_t value() const;
462
463
467 bool operator!= (const const_iterator& other) const;
468
469
473 bool valid() const;
474
475
476 private:
481 size_t m_offset;
482 };
483
484
493 Lock_t lock();
494
495
509 std::pair<const_iterator, bool>
510 put (val_t key, size_t hash, val_t val,
511 bool overwrite,
512 const typename Updater_t::Context_t& ctx);
513
514
529 std::pair<const_iterator, bool>
530 put (const Lock_t& lock,
531 val_t key, size_t hash, val_t val,
532 bool overwrite,
533 const typename Updater_t::Context_t& ctx);
534
535
543 const_iterator get (val_t key, size_t hash) const;
544
545
561 bool erase (val_t key, size_t hash);
562
563
580 bool erase (const Lock_t& lock, val_t key, size_t hash);
581
582
584 using const_iterator_range = std::pair<const_iterator, const_iterator>;
585
586
591
592
597
598
603
604
613 const typename Updater_t::Context_t& ctx);
614
615
622 const_iterator clear (const typename Updater_t::Context_t& ctx);
623
624
633
634
643 void reserve (size_t capacity,
644 const typename Updater_t::Context_t& ctx);
645
646
652 void quiescent (const typename Updater_t::Context_t& ctx);
653
654
667
668
673
674
675private:
682 bool grow (const Lock_t& lock, const typename Updater_t::Context_t& ctx);
683
684
692 bool grow (const Lock_t& lock, size_t new_capacity, const typename Updater_t::Context_t& ctx);
693
694
695 static uint64_t round_up (uint64_t);
696
697
701 const Hasher_t m_hasher;
703 const Matcher_t m_matcher;
707 std::atomic<size_t> m_size;
709 std::atomic<size_t> m_erased;
711 std::mutex m_mutex;
712};
713
714
715} // namespace detail
716
717
718} // namespace CxxUtils
719
720
722
723
724#endif // not CXXUTILS_CONCURRENTHASHMAPIMPL_H
bool operator!=(const DataVector< T > &a, const DataVector< T > &b)
Based on operator==.
void swap(DataVector< T > &a, DataVector< T > &b)
See DataVector<T, BASE>::swap().
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
size_t size() const
Number of registered mappings.
void clear()
Empty the pool.
Atomic min/max functions.
Helper to allow for external locking with put().
Hash table allowing concurrent, lockless reads.
*Longest probe needed so far std::atomic< size_t > m_longestProbe
Table(size_t capacity, const Hasher_t &hasher=Hasher_t(), const Matcher_t &matcher=Matcher_t())
Constructor.
*The key match object const Matcher_t & m_matcher
entry_t & entry(size_t offset)
Return the entry for an offset (non-const).
*The hash object const Hasher_t & m_hasher
*Maximum number of probes allowed before resizing the table const size_t m_maxProbe
*Number of entries in the table Must be a power of const size_t m_capacity
size_t probeRead(val_t key, size_t hash) const
Find a table entry for reading.
*The actual table entries entry_t m_entries[1]
const entry_t & entry(size_t offset) const
Return the entry for an offset.
size_t capacity() const
The number of entries in the table.
*Number of bits in the mask const size_t m_maskBits
size_t probeWrite(val_t key, size_t hash, bool &insert)
Find a table entry for writing.
CHMTableIterator< ENTRIES_PER_CACHELINE > TableIterator
Bidirectional iterator over occupied table entries.
val_t value() const
Return the value for this iterator.
val_t key() const
Return the key for this iterator.
*The current position in the table *Set to for an end iterator size_t m_offset
void next()
Advance the iterator to the next occupied entry.
*The table over which we re iterating const Table & m_table
bool valid() const
Check that the iterator is valid (not pointing at the end).
void prev()
Move the iterator back to the previous occupied entry.
const_iterator(const Table &table, size_t offset)
Constructor.
const_iterator(const Table &table, bool end)
Constructor.
Concept for a value that can be saved in a concurrent hash map.
A couple standard-library related concepts.
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
Definition hcg.cxx:130
UPDATER_< Table > Updater_t
*Mutex to serialize changes to the map std::mutex m_mutex
*Number of entries in the map std::atomic< size_t > m_size
static uint64_t round_up(uint64_t)
void reserve(size_t capacity, const typename Updater_t::Context_t &ctx)
Increase the table capacity.
std::pair< const_iterator, const_iterator > const_iterator_range
const_iterator begin() const
A begin iterator for the container.
*The hash object const Hasher_t m_hasher
*Number of entries that have been erased std::atomic< size_t > m_erased
const Hasher_t & hasher() const
Return the hasher object.
void quiescent(const typename Updater_t::Context_t &ctx)
Called when this thread is no longer referencing anything from this container.
std::pair< const_iterator, bool > put(val_t key, size_t hash, val_t val, bool overwrite, const typename Updater_t::Context_t &ctx)
Add an entry to the table.
typename Updater_t::Context_t Context_t
size_t capacity() const
Return the current table size.
*The key match object const Matcher_t m_matcher
const_iterator_range range() const
Return a range that can be used to iterate over the container.
uintptr_t ConcurrentHashmapVal_t
Type used for keys and values — an unsigned big enough to hold a pointer.
bool grow(const Lock_t &lock, const typename Updater_t::Context_t &ctx)
Make the table larger.
const Matcher_t & matcher() const
Return the matcher object.
Updater_t & updater()
Access the Updater instance.
Lock_t lock()
Take a lock on the container.
const_iterator end() const
An end iterator for the container.
*The current table instance Must be holding the mutex to access this Table * m_table
*Updater object managing memory See above Updater_t m_updater
size_t erased() const
The number of erased elements in the current table.
void forceClear()
Erase the table by filling it with nulls.
ConcurrentHashmapImpl(Updater_t &&updater, size_t capacity_in, const Hasher_t &hasher, const Matcher_t &matcher, const typename Updater_t::Context_t &ctx)
Constructor.
bool erase(val_t key, size_t hash)
Erase an entry from the table.
ConcurrentBitset & insert(bit_t bit, bit_t new_nbits=0)
Set a bit to 1.
Helper to generate hash probes.
const size_t m_stride
Base increment between probes.
size_t offset() const
Offset of the element currently being probed.
const size_t m_boffs
Offset of the first entry we probe within its cacheline.
size_t nprobes() const
Return the number of probes performed so far.
const size_t m_mask
Mask for the table; i.e., capacity-1, but with the low bits corresponding to ENTRIES_PER_CACHELINE al...
static constexpr size_t ENTRIES_PER_CACHELINE_MASK
Mask for the within-cacheline part of indices.
CHMTableIterator(size_t hash, size_t mask, size_t maskBits, size_t probeLimit)
Constructor.
size_t m_bucket
Index of the start of the cacheline currently being probed.
size_t m_nprobes
Number of probes tried so far.
const size_t m_probeLimit
Maximum number of probes to try.
bool next()
Move to the next probe.