3 #ifndef CDSLIB_INTRUSIVE_IMPL_MULTILEVEL_HASHSET_H
4 #define CDSLIB_INTRUSIVE_IMPL_MULTILEVEL_HASHSET_H
6 #include <functional> // std::ref
7 #include <iterator> // std::iterator_traits
9 #include <cds/intrusive/details/multilevel_hashset_base.h>
10 #include <cds/details/allocator.h>
12 namespace cds { namespace intrusive {
13 /// Intrusive hash set based on multi-level array
14 /** @ingroup cds_intrusive_map
15 @anchor cds_intrusive_MultilevelHashSet_hp
18 - [2013] Steven Feldman, Pierre LaBorde, Damian Dechev "Concurrent Multi-level Arrays:
19 Wait-free Extensible Hash Maps"
21 [From the paper] The hardest problem encountered while developing a parallel hash map is how to perform
22 a global resize, the process of redistributing the elements in a hash map that occurs when adding new
23 buckets. The negative impact of blocking synchronization is multiplied during a global resize, because all
24 threads will be forced to wait on the thread that is performing the involved process of resizing the hash map
25 and redistributing the elements. \p %MultilevelHashSet implementation avoids global resizes through new array
26 allocation. By allowing concurrent expansion this structure is free from the overhead of an explicit resize,
27 which facilitates concurrent operations.
29 The presented design includes dynamic hashing, the use of sub-arrays within the hash map data structure;
30 which, in combination with <b>perfect hashing</b>, means that each element has a unique final, as well as current, position.
31 It is important to note that the perfect hash function required by our hash map is trivial to realize as
32 any hash function that permutes the bits of the key is suitable. This is possible because of our approach
33 to the hash function; we require that it produces hash values that are equal in size to that of the key.
34 We know that if we expand the hash map a fixed number of times there can be no collision as duplicate keys
35 are not provided for in the standard semantics of a hash map.
37 \p %MultiLevelHashSet is a multi-level array which has a structure similar to a tree:
38 @image html multilevel_hashset.png
39 The multi-level array differs from a tree in that each position on the tree could hold an array of nodes or a single node.
40 A position that holds a single node is a \p dataNode which holds the hash value of a key and the value that is associated
\r
41 with that key; it is a simple struct holding two variables. A \p dataNode in the multi-level array could be marked.
\r
42 A \p markedDataNode refers to a pointer to a \p dataNode that has been bitmarked at the least significant bit (LSB)
\r
43 of the pointer to the node. This signifies that this \p dataNode is contended. An expansion must occur at this node;
\r
44 any thread that sees this \p markedDataNode will try to replace it with an \p arrayNode; which is a position that holds
\r
45 an array of nodes. The pointer to an \p arrayNode is differentiated from that of a pointer to a \p dataNode by a bitmark
\r
46 on the second-least significant bit.
\r
48 \p %MultiLevelHashSet multi-level array is similar to a tree in that we keep a pointer to the root, which is a memory array
\r
49 called \p head. The length of the \p head memory array is unique, whereas every other \p arrayNode has a uniform length;
\r
50 a normal \p arrayNode has a fixed power-of-two length equal to the binary logarithm of a variable called \p arrayLength.
\r
51 The maximum depth of the tree, \p maxDepth, is the maximum number of pointers that must be followed to reach any node.
\r
52 We define \p currentDepth as the number of memory arrays that we need to traverse to reach the \p arrayNode on which
\r
53 we need to operate; this is initially one, because of \p head.
\r
55 That approach to the structure of the hash set uses an extensible hashing scheme; <b> the hash value is treated as a bit
\r
56 string</b> and rehash incrementally.
\r
58 @note Two important things you should keep in mind when you're using \p %MultiLevelHashSet:
\r
59 - all keys must be fixed-size. It means that you cannot use \p std::string as a key for \p %MultiLevelHashSet.
\r
60 Instead, for the strings you should use well-known hashing algorithms like <a href="https://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA1, SHA2</a>,
\r
61 <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>, <a href="https://en.wikipedia.org/wiki/CityHash">CityHash</a>
\r
62 or its successor <a href="https://code.google.com/p/farmhash/">FarmHash</a> and so on, which
\r
63 converts variable-length strings to fixed-length bit-strings, and use that hash as a key in \p %MultiLevelHashSet.
\r
64 - \p %MultiLevelHashSet uses a perfect hashing. It means that if two different keys, for example, of type \p std::string,
\r
65 have identical hash then you cannot insert both that keys in the set. \p %MultiLevelHashSet does not maintain the key,
\r
66 it maintains its fixed-size hash value.
\r
68 The set supports @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional thread-safe iterators".
\r
70 Template parameters:
\r
71 - \p GC - safe memory reclamation schema. Can be \p gc::HP, \p gc::DHP or one of \ref cds_urcu_type "RCU type"
\r
72 - \p T - a value type to be stored in the set
\r
73 - \p Traits - type traits, the structure based on \p multilevel_hashset::traits or result of \p multilevel_hashset::make_traits metafunction.
\r
74 \p Traits is the mandatory argument because it has one mandatory type - an @ref multilevel_hashset::traits::hash_accessor "accessor"
\r
75 to hash value of \p T. The set algorithm does not calculate that hash value.
\r
77 There are several specializations of \p %MultiLevelHashSet for each \p GC. You should include:
78 - <tt><cds/intrusive/multilevel_hashset_hp.h></tt> for \p gc::HP garbage collector
79 - <tt><cds/intrusive/multilevel_hashset_dhp.h></tt> for \p gc::DHP garbage collector
80 - <tt><cds/intrusive/multilevel_hashset_rcu.h></tt> for \ref cds_intrusive_MultiLevelHashSet_rcu "RCU type". RCU specialization
81 has a slightly different interface.
86 #ifdef CDS_DOXYGEN_INVOKED
87 ,typename Traits = multilevel_hashset::traits
92 class MultiLevelHashSet
95 typedef GC gc; ///< Garbage collector
96 typedef T value_type; ///< type of value stored in the set
97 typedef Traits traits; ///< Traits template parameter, see \p multilevel_hashset::traits
99 typedef typename traits::hash_accessor hash_accessor; ///< Hash accessor functor
100 static_assert(!std::is_same< hash_accessor, cds::opt::none >::value, "hash_accessor functor must be specified" );
102 /// Hash type deduced from \p hash_accessor return type
103 typedef typename std::decay<
104 typename std::remove_reference<
105 decltype( hash_accessor()( std::declval<T>()) )
108 //typedef typename std::result_of< hash_accessor( std::declval<T>()) >::type hash_type;
109 static_assert( !std::is_pointer<hash_type>::value, "hash_accessor should return a reference to hash value" );
111 typedef typename traits::disposer disposer; ///< data node disposer
113 # ifdef CDS_DOXYGEN_INVOKED
114 typedef implementation_defined hash_comparator ; ///< hash compare functor based on opt::compare and opt::less option setter
116 typedef typename cds::opt::details::make_comparator_from<
119 multilevel_hashset::bitwise_compare< hash_type >
120 >::type hash_comparator;
123 typedef typename traits::item_counter item_counter; ///< Item counter type
124 typedef typename traits::node_allocator node_allocator; ///< Array node allocator
125 typedef typename traits::memory_model memory_model; ///< Memory model
126 typedef typename traits::back_off back_off; ///< Backoff strategy
127 typedef typename traits::stat stat; ///< Internal statistics type
129 typedef typename gc::template guarded_ptr< value_type > guarded_ptr; ///< Guarded pointer
131 /// Count of hazard pointers required
132 static CDS_CONSTEXPR size_t const c_nHazardPtrCount = 1;
134 /// Node marked poiter
135 typedef cds::details::marked_ptr< value_type, 3 > node_ptr;
136 /// Array node element
137 typedef atomics::atomic< node_ptr > atomic_node_ptr;
142 flag_array_converting = 1, ///< the cell is converting from data node to an array node
143 flag_array_node = 2 ///< the cell is a pointer to an array node
147 array_node * const pParent; ///< parent array node
148 size_t const idxParent; ///< index in parent array node
149 atomic_node_ptr nodes[1]; ///< node array
151 array_node( array_node * parent, size_t idx )
156 array_node() = delete;
157 array_node( array_node const&) = delete;
158 array_node( array_node&& ) = delete;
161 typedef cds::details::Allocator< array_node, node_allocator > cxx_array_node_allocator;
163 typedef multilevel_hashset::details::hash_splitter< hash_type > hash_splitter;
166 size_t head_node_size; // power-of-two
167 size_t head_node_size_log; // log2( head_node_size )
168 size_t array_node_size; // power-of-two
169 size_t array_node_size_log;// log2( array_node_size )
175 /// Bidirectional iterator class
176 template <bool IsConst>
177 class bidirectional_iterator
179 friend class MultiLevelHashSet;
181 array_node * m_pNode; ///< current array node
182 size_t m_idx; ///< current position in m_pNode
183 typename gc::Guard m_guard; ///< HP guard
184 MultiLevelHashSet const* m_set; ///< Hash set
187 typedef typename std::conditional< IsConst, value_type const*, value_type*>::type value_ptr; ///< Value pointer
188 typedef typename std::conditional< IsConst, value_type const&, value_type&>::type value_ref; ///< Value reference
191 bidirectional_iterator() CDS_NOEXCEPT
197 bidirectional_iterator( bidirectional_iterator const& rhs ) CDS_NOEXCEPT
198 : m_pNode( rhs.m_pNode )
202 m_guard.copy( rhs.m_guard );
205 bidirectional_iterator& operator=(bidirectional_iterator const& rhs) CDS_NOEXCEPT
207 m_pNode = rhs.m_pNode;
210 m_guard.copy( rhs.m_guard );
214 bidirectional_iterator& operator++()
220 bidirectional_iterator& operator--()
226 value_ptr operator ->() const CDS_NOEXCEPT
231 value_ref operator *() const CDS_NOEXCEPT
233 value_ptr p = pointer();
243 template <bool IsConst2>
244 bool operator ==(bidirectional_iterator<IsConst2> const& rhs) const
246 return m_pNode == rhs.m_pNode && m_idx == rhs.m_idx && m_set == rhs.m_set;
249 template <bool IsConst2>
250 bool operator !=(bidirectional_iterator<IsConst2> const& rhs) const
252 return !( *this == rhs );
256 bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx, bool )
262 bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx )
270 value_ptr pointer() const CDS_NOEXCEPT
272 return m_guard.template get<value_type>();
277 assert( m_set != nullptr );
278 assert( m_pNode != nullptr );
280 size_t const arrayNodeSize = m_set->array_node_size();
281 size_t const headSize = m_set->head_size();
282 array_node * pNode = m_pNode;
283 size_t idx = m_idx + 1;
284 size_t nodeSize = m_pNode->pParent? arrayNodeSize : headSize;
287 if ( idx < nodeSize ) {
288 node_ptr slot = pNode->nodes[idx].load( memory_model::memory_order_acquire );
289 if ( slot.bits() == flag_array_node ) {
290 // array node, go down the tree
291 assert( slot.ptr() != nullptr );
292 pNode = to_array( slot.ptr() );
294 nodeSize = arrayNodeSize;
296 else if ( slot.bits() == flag_array_converting ) {
297 // the slot is converting to array node right now - skip the node
303 if ( m_guard.protect( pNode->nodes[idx], [](node_ptr p) -> value_type * { return p.ptr(); }) == slot ) {
314 if ( pNode->pParent ) {
315 idx = pNode->idxParent + 1;
316 pNode = pNode->pParent;
317 nodeSize = pNode->pParent ? arrayNodeSize : headSize;
321 assert( pNode == m_set->m_Head );
322 assert( idx == headSize );
333 assert( m_set != nullptr );
334 assert( m_pNode != nullptr );
336 size_t const arrayNodeSize = m_set->array_node_size();
337 size_t const headSize = m_set->head_size();
338 size_t const endIdx = size_t(0) - 1;
340 array_node * pNode = m_pNode;
341 size_t idx = m_idx - 1;
342 size_t nodeSize = m_pNode->pParent? arrayNodeSize : headSize;
345 if ( idx != endIdx ) {
346 node_ptr slot = pNode->nodes[idx].load( memory_model::memory_order_acquire );
347 if ( slot.bits() == flag_array_node ) {
348 // array node, go down the tree
349 assert( slot.ptr() != nullptr );
350 pNode = to_array( slot.ptr() );
351 nodeSize = arrayNodeSize;
354 else if ( slot.bits() == flag_array_converting ) {
355 // the slot is converting to array node right now - skip the node
361 if ( m_guard.protect( pNode->nodes[idx], [](node_ptr p) -> value_type * { return p.ptr(); }) == slot ) {
372 if ( pNode->pParent ) {
373 idx = pNode->idxParent - 1;
374 pNode = pNode->pParent;
375 nodeSize = pNode->pParent ? arrayNodeSize : headSize;
379 assert( pNode == m_set->m_Head );
380 assert( idx == endIdx );
390 /// Reverse bidirectional iterator
391 template <bool IsConst>
392 class reverse_bidirectional_iterator : protected bidirectional_iterator<IsConst>
394 typedef bidirectional_iterator<IsConst> base_class;
395 friend class MultiLevelHashSet;
398 typedef typename base_class::value_ptr value_ptr;
399 typedef typename base_class::value_ref value_ref;
402 reverse_bidirectional_iterator() CDS_NOEXCEPT
406 reverse_bidirectional_iterator( reverse_bidirectional_iterator const& rhs ) CDS_NOEXCEPT
410 reverse_bidirectional_iterator& operator=( reverse_bidirectional_iterator const& rhs) CDS_NOEXCEPT
412 base_class::operator=( rhs );
416 reverse_bidirectional_iterator& operator++()
418 base_class::backward();
422 reverse_bidirectional_iterator& operator--()
424 base_class::forward();
428 value_ptr operator ->() const CDS_NOEXCEPT
430 return base_class::operator->();
433 value_ref operator *() const CDS_NOEXCEPT
435 return base_class::operator*();
440 base_class::release();
443 template <bool IsConst2>
444 bool operator ==(reverse_bidirectional_iterator<IsConst2> const& rhs) const
446 return base_class::operator==( rhs );
449 template <bool IsConst2>
450 bool operator !=(reverse_bidirectional_iterator<IsConst2> const& rhs)
452 return !( *this == rhs );
456 reverse_bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx )
457 : base_class( set, pNode, idx, false )
459 base_class::backward();
465 typedef bidirectional_iterator<false> iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional iterator" type
466 typedef bidirectional_iterator<true> const_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional const iterator" type
467 typedef reverse_bidirectional_iterator<false> reverse_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional reverse iterator" type
468 typedef reverse_bidirectional_iterator<true> const_reverse_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional reverse const iterator" type
472 metrics const m_Metrics; ///< Metrics
473 array_node * m_Head; ///< Head array
474 item_counter m_ItemCounter; ///< Item counter
475 stat m_Stat; ///< Internal statistics
479 /// Creates empty set
481 @param head_bits: 2<sup>head_bits</sup> specifies the size of head array, minimum is 4.
482 @param array_bits: 2<sup>array_bits</sup> specifies the size of array node, minimum is 2.
484 Equation for \p head_bits and \p array_bits:
486 sizeof(hash_type) * 8 == head_bits + N * array_bits
488 where \p N is multi-level array depth.
490 MultiLevelHashSet( size_t head_bits = 8, size_t array_bits = 4 )
491 : m_Metrics(make_metrics( head_bits, array_bits ))
492 , m_Head( alloc_head_node())
495 /// Destructs the set and frees all data
499 free_array_node( m_Head );
504 The function inserts \p val in the set if it does not contain
505 an item with that hash.
507 Returns \p true if \p val is placed into the set, \p false otherwise.
509 bool insert( value_type& val )
511 return insert( val, [](value_type&) {} );
516 This function is intended for derived non-intrusive containers.
518 The function allows to split creating of new item into two part:
519 - create item with key only
520 - insert new item into the set
521 - if inserting is success, calls \p f functor to initialize \p val.
523 The functor signature is:
525 void func( value_type& val );
527 where \p val is the item inserted.
529 The user-defined functor is called only if the inserting is success.
531 @warning See \ref cds_intrusive_item_creating "insert item troubleshooting".
533 template <typename Func>
534 bool insert( value_type& val, Func f )
536 hash_type const& hash = hash_accessor()( val );
537 hash_splitter splitter( hash );
539 typename gc::Guard guard;
542 size_t nOffset = m_Metrics.head_node_size_log;
543 array_node * pArr = m_Head;
544 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
545 assert( nSlot < m_Metrics.head_node_size );
548 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
549 if ( slot.bits() == flag_array_node ) {
550 // array node, go down the tree
551 assert( slot.ptr() != nullptr );
552 nSlot = splitter.cut( m_Metrics.array_node_size_log );
553 assert( nSlot < m_Metrics.array_node_size );
554 pArr = to_array( slot.ptr() );
555 nOffset += m_Metrics.array_node_size_log;
557 else if ( slot.bits() == flag_array_converting ) {
558 // the slot is converting to array node right now
560 m_Stat.onSlotConverting();
564 assert(slot.bits() == 0 );
566 // protect data node by hazard pointer
567 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
568 // slot value has been changed - retry
569 m_Stat.onSlotChanged();
571 else if ( slot.ptr() ) {
572 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
573 // the item with that hash value already exists
574 m_Stat.onInsertFailed();
578 // the slot must be expanded
579 expand_slot( pArr, nSlot, slot, nOffset );
582 // the slot is empty, try to insert data node
584 if ( pArr->nodes[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
586 // the new data node has been inserted
589 m_Stat.onInsertSuccess();
593 // insert failed - slot has been changed by another thread
595 m_Stat.onInsertRetry();
603 Performs inserting or updating the item with hash value equal to \p val.
604 - If hash value is found then existing item is replaced with \p val, old item is disposed
605 with \p Traits::disposer. Note that the disposer is called by \p GC asynchronously.
606 The function returns <tt> std::pair<true, false> </tt>
607 - If hash value is not found and \p bInsert is \p true then \p val is inserted,
608 the function returns <tt> std::pair<true, true> </tt>
609 - If hash value is not found and \p bInsert is \p false then the set is unchanged,
610 the function returns <tt> std::pair<false, false> </tt>
612 Returns <tt> std::pair<bool, bool> </tt> where \p first is \p true if operation is successfull
613 (i.e. the item has been inserted or updated),
614 \p second is \p true if new item has been added or \p false if the set contains that hash.
616 std::pair<bool, bool> update( value_type& val, bool bInsert = true )
618 return do_update(val, [](bool, value_type&) {}, bInsert );
621 /// Unlinks the item \p val from the set
623 The function searches the item \p val in the set and unlink it
624 if it is found and its address is equal to <tt>&val</tt>.
626 The function returns \p true if success and \p false otherwise.
628 bool unlink( value_type const& val )
630 typename gc::Guard guard;
631 auto pred = [&val](value_type const& item) -> bool { return &item == &val; };
632 value_type * p = do_erase( hash_accessor()( val ), guard, std::ref( pred ));
634 // p is guarded by HP
636 gc::template retire<disposer>( p );
638 m_Stat.onEraseSuccess();
644 /// Deletes the item from the set
646 The function searches \p hash in the set,
647 unlinks the item found, and returns \p true.
648 If that item is not found the function returns \p false.
650 The \ref disposer specified in \p Traits is called by garbage collector \p GC asynchronously.
652 bool erase( hash_type const& hash )
654 return erase(hash, [](value_type const&) {} );
657 /// Deletes the item from the set
659 The function searches \p hash in the set,
660 call \p f functor with item found, and unlinks it from the set.
661 The \ref disposer specified in \p Traits is called
662 by garbage collector \p GC asynchronously.
664 The \p Func interface is
667 void operator()( value_type& item );
671 If \p hash is not found the function returns \p false.
673 template <typename Func>
674 bool erase( hash_type const& hash, Func f )
676 typename gc::Guard guard;
677 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
679 // p is guarded by HP
681 gc::template retire<disposer>( p );
684 m_Stat.onEraseSuccess();
690 /// Deletes the item pointed by iterator \p iter
692 Returns \p true if the operation is successful, \p false otherwise.
694 The function does not invalidate the iterator, it remains valid and can be used for further traversing.
696 bool erase_at( iterator const& iter )
698 if ( iter.m_set != this )
700 if ( iter.m_pNode == m_Head && iter.m_idx >= head_size())
702 if ( iter.m_idx >= array_node_size() )
706 node_ptr slot = iter.m_pNode->nodes[iter.m_idx].load( memory_model::memory_order_acquire );
707 if ( slot.bits() == 0 && slot.ptr() == iter.pointer() ) {
708 if ( iter.m_pNode->nodes[iter.m_idx].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed) ) {
709 // the item is guarded by iterator, so we may retire it safely
710 gc::template retire<disposer>( slot.ptr() );
712 m_Stat.onEraseSuccess();
721 /// Extracts the item with specified \p hash
723 The function searches \p hash in the set,
724 unlinks it from the set, and returns an guarded pointer to the item extracted.
725 If \p hash is not found the function returns an empty guarded pointer.
727 The \p disposer specified in \p Traits class' template parameter is called automatically
728 by garbage collector \p GC when returned \ref guarded_ptr object to be destroyed or released.
729 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
733 typedef cds::intrusive::MultiLevelHashSet< your_template_args > my_set;
737 my_set::guarded_ptr gp( theSet.extract( 5 ));
742 // Destructor of gp releases internal HP guard
746 guarded_ptr extract( hash_type const& hash )
750 typename gc::Guard guard;
751 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
753 // p is guarded by HP
755 gc::template retire<disposer>( p );
757 m_Stat.onEraseSuccess();
764 /// Finds an item by it's \p hash
766 The function searches the item by \p hash and calls the functor \p f for item found.
767 The interface of \p Func functor is:
770 void operator()( value_type& item );
773 where \p item is the item found.
775 The functor may change non-key fields of \p item. Note that the functor is only guarantee
776 that \p item cannot be disposed during the functor is executing.
777 The functor does not serialize simultaneous access to the set's \p item. If such access is
778 possible you must provide your own synchronization schema on item level to prevent unsafe item modifications.
780 The function returns \p true if \p hash is found, \p false otherwise.
782 template <typename Func>
783 bool find( hash_type const& hash, Func f )
785 typename gc::Guard guard;
786 value_type * p = search( hash, guard );
788 // p is guarded by HP
796 /// Checks whether the set contains \p hash
798 The function searches the item by its \p hash
799 and returns \p true if it is found, or \p false otherwise.
801 bool contains( hash_type const& hash )
803 return find( hash, [](value_type&) {} );
806 /// Finds an item by it's \p hash and returns the item found
808 The function searches the item by its \p hash
809 and returns the guarded pointer to the item found.
810 If \p hash is not found the function returns an empty \p guarded_ptr.
812 @note Each \p guarded_ptr object uses one GC's guard which can be limited resource.
816 typedef cds::intrusive::MultiLevelHashSet< your_template_params > my_set;
820 my_set::guarded_ptr gp( theSet.get( 5 ));
821 if ( theSet.get( 5 )) {
825 // Destructor of guarded_ptr releases internal HP guard
829 guarded_ptr get( hash_type const& hash )
833 typename gc::Guard guard;
834 gp.reset( search( hash, guard ));
839 /// Clears the set (non-atomic)
841 The function unlink all data node from the set.
842 The function is not atomic but is thread-safe.
843 After \p %clear() the set may not be empty because another threads may insert items.
845 For each item the \p disposer is called after unlinking.
849 clear_array( m_Head, head_size() );
852 /// Checks if the set is empty
854 Emptiness is checked by item counting: if item count is zero then the set is empty.
855 Thus, the correct item counting feature is an important part of the set implementation.
862 /// Returns item count in the set
865 return m_ItemCounter;
868 /// Returns const reference to internal statistics
869 stat const& statistics() const
874 /// Returns the size of head node
875 size_t head_size() const
877 return m_Metrics.head_node_size;
880 /// Returns the size of the array node
881 size_t array_node_size() const
883 return m_Metrics.array_node_size;
887 ///@name Thread-safe iterators
888 /** @anchor cds_intrusive_MultilevelHashSet_iterators
889 The set supports thread-safe iterators: you may iterate over the set in multi-threaded environment.
\r
890 It is guaranteed that the iterators will remain valid even if another thread deletes the node the iterator points to:
\r
891 Hazard Pointer embedded into the iterator object protects the node from physical reclamation.
\r
893 @note Since the iterator object contains hazard pointer that is a thread-local resource,
\r
894 the iterator should not be passed to another thread.
\r
896 Each iterator object supports the common interface:
\r
897 - dereference operators:
\r
899 value_type [const] * operator ->() noexcept
\r
900 value_type [const] & operator *() noexcept
\r
902 - pre-increment and pre-decrement. Post-operators is not supported
\r
903 - equality operators <tt>==</tt> and <tt>!=</tt>.
\r
904 Iterators are equal iff they point to the same cell of the same array node.
905 Note that for two iterators \p it1 and \p it2, the conditon <tt> it1 == it2 </tt>
906 does not entail <tt> &(*it1) == &(*it2) </tt>: welcome to concurrent containers
907 - helper member function \p release() that clears internal hazard pointer.
\r
908 After \p release() call the iterator points to \p nullptr but it still remain valid: further iterating is possible.
912 /// Returns an iterator to the beginning of the set
915 return iterator( *this, m_Head, size_t(0) - 1 );
918 /// Returns an const iterator to the beginning of the set
919 const_iterator begin() const
921 return const_iterator( *this, m_Head, size_t(0) - 1 );
924 /// Returns an const iterator to the beginning of the set
925 const_iterator cbegin()
927 return const_iterator( *this, m_Head, size_t(0) - 1 );
930 /// Returns an iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
933 return iterator( *this, m_Head, head_size() - 1 );
936 /// Returns a const iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
937 const_iterator end() const
939 return const_iterator( *this, m_Head, head_size() - 1 );
942 /// Returns a const iterator to the element following the last element of the set. This element acts as a placeholder; attempting to access it results in undefined behavior.
943 const_iterator cend()
945 return const_iterator( *this, m_Head, head_size() - 1 );
948 /// Returns a reverse iterator to the first element of the reversed set
949 reverse_iterator rbegin()
951 return reverse_iterator( *this, m_Head, head_size() );
954 /// Returns a const reverse iterator to the first element of the reversed set
955 const_reverse_iterator rbegin() const
957 return const_reverse_iterator( *this, m_Head, head_size() );
960 /// Returns a const reverse iterator to the first element of the reversed set
961 const_reverse_iterator crbegin()
963 return const_reverse_iterator( *this, m_Head, head_size() );
966 /// Returns a reverse iterator to the element following the last element of the reversed set
968 It corresponds to the element preceding the first element of the non-reversed container.
969 This element acts as a placeholder, attempting to access it results in undefined behavior.
971 reverse_iterator rend()
973 return reverse_iterator( *this, m_Head, 0 );
976 /// Returns a const reverse iterator to the element following the last element of the reversed set
978 It corresponds to the element preceding the first element of the non-reversed container.
979 This element acts as a placeholder, attempting to access it results in undefined behavior.
981 const_reverse_iterator rend() const
983 return const_reverse_iterator( *this, m_Head, 0 );
986 /// Returns a const reverse iterator to the element following the last element of the reversed set
988 It corresponds to the element preceding the first element of the non-reversed container.
989 This element acts as a placeholder, attempting to access it results in undefined behavior.
991 const_reverse_iterator crend()
993 return const_reverse_iterator( *this, m_Head, 0 );
999 static metrics make_metrics( size_t head_bits, size_t array_bits )
1001 size_t const hash_bits = sizeof( hash_type ) * 8;
1003 if ( array_bits < 2 )
1005 if ( head_bits < 4 )
1007 if ( head_bits > hash_bits )
1008 head_bits = hash_bits;
1009 if ( (hash_bits - head_bits) % array_bits != 0 )
1010 head_bits += (hash_bits - head_bits) % array_bits;
1012 assert( (hash_bits - head_bits) % array_bits == 0 );
1015 m.head_node_size_log = head_bits;
1016 m.head_node_size = size_t(1) << head_bits;
1017 m.array_node_size_log = array_bits;
1018 m.array_node_size = size_t(1) << array_bits;
1022 array_node * alloc_head_node() const
1024 return alloc_array_node( head_size(), nullptr, 0 );
1027 array_node * alloc_array_node( array_node * pParent, size_t idxParent ) const
1029 return alloc_array_node( array_node_size(), pParent, idxParent );
1032 static array_node * alloc_array_node( size_t nSize, array_node * pParent, size_t idxParent )
1034 array_node * pNode = cxx_array_node_allocator().NewBlock( sizeof(array_node) + sizeof(atomic_node_ptr) * (nSize - 1), pParent, idxParent );
1035 for ( atomic_node_ptr * p = pNode->nodes, *pEnd = pNode->nodes + nSize; p < pEnd; ++p )
1036 p->store( node_ptr(), memory_model::memory_order_release );
1040 static void free_array_node( array_node * parr )
1042 cxx_array_node_allocator().Delete( parr );
1047 // The function is not thread-safe. For use in dtor only
1051 // Destroy all array nodes
1052 destroy_array_nodes( m_Head, head_size());
1055 void destroy_array_nodes( array_node * pArr, size_t nSize )
1057 for ( atomic_node_ptr * p = pArr->nodes, *pLast = pArr->nodes + nSize; p != pLast; ++p ) {
1058 node_ptr slot = p->load( memory_model::memory_order_acquire );
1059 if ( slot.bits() == flag_array_node ) {
1060 destroy_array_nodes(to_array(slot.ptr()), array_node_size());
1061 free_array_node( to_array(slot.ptr()));
1062 p->store(node_ptr(), memory_model::memory_order_relaxed );
1067 void clear_array( array_node * pArrNode, size_t nSize )
1072 for ( atomic_node_ptr * pArr = pArrNode->nodes, *pLast = pArr + nSize; pArr != pLast; ++pArr ) {
1074 node_ptr slot = pArr->load( memory_model::memory_order_acquire );
1075 if ( slot.bits() == flag_array_node ) {
1076 // array node, go down the tree
1077 assert( slot.ptr() != nullptr );
1078 clear_array( to_array( slot.ptr()), array_node_size() );
1081 else if ( slot.bits() == flag_array_converting ) {
1082 // the slot is converting to array node right now
1083 while ( (slot = pArr->load(memory_model::memory_order_acquire)).bits() == flag_array_converting ) {
1085 m_Stat.onSlotConverting();
1089 assert( slot.ptr() != nullptr );
1090 assert(slot.bits() == flag_array_node );
1091 clear_array( to_array( slot.ptr()), array_node_size() );
1096 if ( pArr->compare_exchange_strong( slot, node_ptr(), memory_model::memory_order_acquire, atomics::memory_order_relaxed )) {
1098 gc::template retire<disposer>( slot.ptr() );
1100 m_Stat.onEraseSuccess();
1113 converter( value_type * p )
1117 converter( array_node * p )
1122 static array_node * to_array( value_type * p )
1124 return converter( p ).pArr;
1126 static value_type * to_node( array_node * p )
1128 return converter( p ).pData;
1131 bool expand_slot( array_node * pParent, size_t idxParent, node_ptr current, size_t nOffset )
1133 assert( current.bits() == 0 );
1134 assert( current.ptr() );
1136 size_t idx = hash_splitter(hash_accessor()(*current.ptr()), nOffset).cut( m_Metrics.array_node_size_log );
1137 array_node * pArr = alloc_array_node( pParent, idxParent );
1139 node_ptr cur(current.ptr());
1140 atomic_node_ptr& slot = pParent->nodes[idxParent];
1141 if ( !slot.compare_exchange_strong( cur, cur | flag_array_converting, memory_model::memory_order_release, atomics::memory_order_relaxed ))
1143 m_Stat.onExpandNodeFailed();
1144 free_array_node( pArr );
1148 pArr->nodes[idx].store( current, memory_model::memory_order_release );
1150 cur = cur | flag_array_converting;
1152 slot.compare_exchange_strong( cur, node_ptr( to_node( pArr ), flag_array_node ), memory_model::memory_order_release, atomics::memory_order_relaxed )
1155 m_Stat.onArrayNodeCreated();
1162 value_type * search( hash_type const& hash, typename gc::Guard& guard )
1164 hash_splitter splitter( hash );
1165 hash_comparator cmp;
1168 array_node * pArr = m_Head;
1169 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1170 assert( nSlot < m_Metrics.head_node_size );
1173 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1174 if ( slot.bits() == flag_array_node ) {
1175 // array node, go down the tree
1176 assert( slot.ptr() != nullptr );
1177 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1178 assert( nSlot < m_Metrics.array_node_size );
1179 pArr = to_array( slot.ptr() );
1181 else if ( slot.bits() == flag_array_converting ) {
1182 // the slot is converting to array node right now
1184 m_Stat.onSlotConverting();
1188 assert(slot.bits() == 0 );
1190 // protect data node by hazard pointer
1191 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1192 // slot value has been changed - retry
1193 m_Stat.onSlotChanged();
1195 else if ( slot.ptr() && cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
1197 m_Stat.onFindSuccess();
1200 m_Stat.onFindFailed();
1206 template <typename Predicate>
1207 value_type * do_erase( hash_type const& hash, typename gc::Guard& guard, Predicate pred )
1209 hash_splitter splitter( hash );
1210 hash_comparator cmp;
1213 array_node * pArr = m_Head;
1214 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1215 assert( nSlot < m_Metrics.head_node_size );
1218 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1219 if ( slot.bits() == flag_array_node ) {
1220 // array node, go down the tree
1221 assert( slot.ptr() != nullptr );
1222 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1223 assert( nSlot < m_Metrics.array_node_size );
1224 pArr = to_array( slot.ptr() );
1226 else if ( slot.bits() == flag_array_converting ) {
1227 // the slot is converting to array node right now
1229 m_Stat.onSlotConverting();
1233 assert(slot.bits() == 0 );
1235 // protect data node by hazard pointer
1236 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1237 // slot value has been changed - retry
1238 m_Stat.onSlotChanged();
1240 else if ( slot.ptr() ) {
1241 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 && pred( *slot.ptr() )) {
1242 // item found - replace it with nullptr
1243 if ( pArr->nodes[nSlot].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed))
1245 m_Stat.onEraseRetry();
1248 m_Stat.onEraseFailed();
1252 // the slot is empty
1253 m_Stat.onEraseFailed();
1260 template <typename Func>
1261 std::pair<bool, bool> do_update( value_type& val, Func f, bool bInsert = true )
1263 hash_type const& hash = hash_accessor()( val );
1264 hash_splitter splitter( hash );
1265 hash_comparator cmp;
1266 typename gc::Guard guard;
1269 array_node * pArr = m_Head;
1270 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1271 assert( nSlot < m_Metrics.head_node_size );
1272 size_t nOffset = m_Metrics.head_node_size_log;
1275 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1276 if ( slot.bits() == flag_array_node ) {
1277 // array node, go down the tree
1278 assert( slot.ptr() != nullptr );
1279 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1280 assert( nSlot < m_Metrics.array_node_size );
1281 pArr = to_array( slot.ptr() );
1282 nOffset += m_Metrics.array_node_size_log;
1284 else if ( slot.bits() == flag_array_converting ) {
1285 // the slot is converting to array node right now
1287 m_Stat.onSlotConverting();
1291 assert(slot.bits() == 0 );
1293 // protect data node by hazard pointer
1294 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1295 // slot value has been changed - retry
1296 m_Stat.onSlotChanged();
1298 else if ( slot.ptr() ) {
1299 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
1300 // the item with that hash value already exists
1301 // Replace it with val
1302 if ( slot.ptr() == &val ) {
1303 m_Stat.onUpdateExisting();
1304 return std::make_pair( true, false );
1307 if ( pArr->nodes[nSlot].compare_exchange_strong( slot, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed )) {
1308 // slot can be disposed
1310 gc::template retire<disposer>( slot.ptr() );
1311 m_Stat.onUpdateExisting();
1312 return std::make_pair( true, false );
1315 m_Stat.onUpdateRetry();
1319 // the slot must be expanded
1320 expand_slot( pArr, nSlot, slot, nOffset );
1323 // the slot is empty, try to insert data node
1326 if ( pArr->nodes[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
1328 // the new data node has been inserted
1331 m_Stat.onUpdateNew();
1332 return std::make_pair( true, true );
1336 m_Stat.onUpdateFailed();
1337 return std::make_pair( false, false );
1340 // insert failed - slot has been changed by another thread
1342 m_Stat.onUpdateRetry();
1349 }} // namespace cds::intrusive
1354 template <class GC, typename T, typename Traits>
1355 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator >
1357 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator iterator_class;
1359 // difference_type is not applicable for that iterator
1360 // typedef ??? difference_type
1361 typedef T value_type;
1362 typedef typename iterator_class::value_ptr pointer;
1363 typedef typename iterator_class::value_ref reference;
1364 typedef bidirectional_iterator_tag iterator_category;
1367 template <class GC, typename T, typename Traits>
1368 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator >
1370 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator iterator_class;
1372 // difference_type is not applicable for that iterator
1373 // typedef ??? difference_type
1374 typedef T value_type;
1375 typedef typename iterator_class::value_ptr pointer;
1376 typedef typename iterator_class::value_ref reference;
1377 typedef bidirectional_iterator_tag iterator_category;
1383 #endif // #ifndef CDSLIB_INTRUSIVE_IMPL_MULTILEVEL_HASHSET_H