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
41 with that key; it is a simple struct holding two variables. A \p dataNode in the multi-level array could be marked.
42 A \p markedDataNode refers to a pointer to a \p dataNode that has been bitmarked at the least significant bit (LSB)
43 of the pointer to the node. This signifies that this \p dataNode is contended. An expansion must occur at this node;
44 any thread that sees this \p markedDataNode will try to replace it with an \p arrayNode; which is a position that holds
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
46 on the second-least significant bit.
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
49 called \p head. The length of the \p head memory array is unique, whereas every other \p arrayNode has a uniform length;
50 a normal \p arrayNode has a fixed power-of-two length equal to the binary logarithm of a variable called \p arrayLength.
51 The maximum depth of the tree, \p maxDepth, is the maximum number of pointers that must be followed to reach any node.
52 We define \p currentDepth as the number of memory arrays that we need to traverse to reach the \p arrayNode on which
53 we need to operate; this is initially one, because of \p head.
55 That approach to the structure of the hash set uses an extensible hashing scheme; <b> the hash value is treated as a bit
56 string</b> and rehash incrementally.
58 @note Two important things you should keep in mind when you're using \p %MultiLevelHashSet:
59 - all keys must be fixed-size. It means that you cannot use \p std::string as a key for \p %MultiLevelHashSet.
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>,
61 <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash</a>, <a href="https://en.wikipedia.org/wiki/CityHash">CityHash</a>
62 or its successor <a href="https://code.google.com/p/farmhash/">FarmHash</a> and so on, which
63 converts variable-length strings to fixed-length bit-strings, and use that hash as a key in \p %MultiLevelHashSet.
64 - \p %MultiLevelHashSet uses a perfect hashing. It means that if two different keys, for example, of type \p std::string,
65 have identical hash then you cannot insert both that keys in the set. \p %MultiLevelHashSet does not maintain the key,
66 it maintains its fixed-size hash value.
68 The set supports @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional thread-safe iterators".
71 - \p GC - safe memory reclamation schema. Can be \p gc::HP, \p gc::DHP or one of \ref cds_urcu_type "RCU type"
72 - \p T - a value type to be stored in the set
73 - \p Traits - type traits, the structure based on \p multilevel_hashset::traits or result of \p multilevel_hashset::make_traits metafunction.
74 \p Traits is the mandatory argument because it has one mandatory type - an @ref multilevel_hashset::traits::hash_accessor "accessor"
75 to hash value of \p T. The set algorithm does not calculate that hash value.
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 = 2;
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 )
177 friend class MultiLevelHashSet;
180 array_node * m_pNode; ///< current array node
181 size_t m_idx; ///< current position in m_pNode
182 typename gc::Guard m_guard; ///< HP guard
183 MultiLevelHashSet const* m_set; ///< Hash set
186 iterator_base() CDS_NOEXCEPT
192 iterator_base( iterator_base const& rhs ) CDS_NOEXCEPT
193 : m_pNode( rhs.m_pNode )
197 m_guard.copy( rhs.m_guard );
200 iterator_base& operator=(iterator_base const& rhs) CDS_NOEXCEPT
202 m_pNode = rhs.m_pNode;
205 m_guard.copy( rhs.m_guard );
209 iterator_base& operator++()
215 iterator_base& operator--()
226 bool operator ==(iterator_base const& rhs) const CDS_NOEXCEPT
228 return m_pNode == rhs.m_pNode && m_idx == rhs.m_idx && m_set == rhs.m_set;
231 bool operator !=(iterator_base const& rhs) const CDS_NOEXCEPT
233 return !( *this == rhs );
237 iterator_base( MultiLevelHashSet const& set, array_node * pNode, size_t idx, bool )
243 iterator_base( MultiLevelHashSet const& set, array_node * pNode, size_t idx )
251 value_type * pointer() const CDS_NOEXCEPT
253 return m_guard.template get<value_type>();
258 assert( m_set != nullptr );
259 assert( m_pNode != nullptr );
261 size_t const arrayNodeSize = m_set->array_node_size();
262 size_t const headSize = m_set->head_size();
263 array_node * pNode = m_pNode;
264 size_t idx = m_idx + 1;
265 size_t nodeSize = m_pNode->pParent? arrayNodeSize : headSize;
268 if ( idx < nodeSize ) {
269 node_ptr slot = pNode->nodes[idx].load( memory_model::memory_order_acquire );
270 if ( slot.bits() == flag_array_node ) {
271 // array node, go down the tree
272 assert( slot.ptr() != nullptr );
273 pNode = to_array( slot.ptr() );
275 nodeSize = arrayNodeSize;
277 else if ( slot.bits() == flag_array_converting ) {
278 // the slot is converting to array node right now - skip the node
284 if ( m_guard.protect( pNode->nodes[idx], [](node_ptr p) -> value_type * { return p.ptr(); }) == slot ) {
295 if ( pNode->pParent ) {
296 idx = pNode->idxParent + 1;
297 pNode = pNode->pParent;
298 nodeSize = pNode->pParent ? arrayNodeSize : headSize;
302 assert( pNode == m_set->m_Head );
303 assert( idx == headSize );
314 assert( m_set != nullptr );
315 assert( m_pNode != nullptr );
317 size_t const arrayNodeSize = m_set->array_node_size();
318 size_t const headSize = m_set->head_size();
319 size_t const endIdx = size_t(0) - 1;
321 array_node * pNode = m_pNode;
322 size_t idx = m_idx - 1;
323 size_t nodeSize = m_pNode->pParent? arrayNodeSize : headSize;
326 if ( idx != endIdx ) {
327 node_ptr slot = pNode->nodes[idx].load( memory_model::memory_order_acquire );
328 if ( slot.bits() == flag_array_node ) {
329 // array node, go down the tree
330 assert( slot.ptr() != nullptr );
331 pNode = to_array( slot.ptr() );
332 nodeSize = arrayNodeSize;
335 else if ( slot.bits() == flag_array_converting ) {
336 // the slot is converting to array node right now - skip the node
342 if ( m_guard.protect( pNode->nodes[idx], [](node_ptr p) -> value_type * { return p.ptr(); }) == slot ) {
353 if ( pNode->pParent ) {
354 idx = pNode->idxParent - 1;
355 pNode = pNode->pParent;
356 nodeSize = pNode->pParent ? arrayNodeSize : headSize;
360 assert( pNode == m_set->m_Head );
361 assert( idx == endIdx );
371 template <class Iterator>
372 Iterator init_begin() const
374 return Iterator( *this, m_Head, size_t(0) - 1 );
377 template <class Iterator>
378 Iterator init_end() const
380 return Iterator( *this, m_Head, head_size(), false );
383 template <class Iterator>
384 Iterator init_rbegin() const
386 return Iterator( *this, m_Head, head_size() );
389 template <class Iterator>
390 Iterator init_rend() const
392 return Iterator( *this, m_Head, size_t(0) - 1, false );
395 /// Bidirectional iterator class
396 template <bool IsConst>
397 class bidirectional_iterator: protected iterator_base
399 friend class MultiLevelHashSet;
402 static CDS_CONSTEXPR bool const c_bConstantIterator = IsConst;
405 typedef typename std::conditional< IsConst, value_type const*, value_type*>::type value_ptr; ///< Value pointer
406 typedef typename std::conditional< IsConst, value_type const&, value_type&>::type value_ref; ///< Value reference
409 bidirectional_iterator() CDS_NOEXCEPT
412 bidirectional_iterator( bidirectional_iterator const& rhs ) CDS_NOEXCEPT
413 : iterator_base( rhs )
416 bidirectional_iterator& operator=(bidirectional_iterator const& rhs) CDS_NOEXCEPT
418 iterator_base::operator=( rhs );
422 bidirectional_iterator& operator++()
424 iterator_base::operator++();
428 bidirectional_iterator& operator--()
430 iterator_base::operator--();
434 value_ptr operator ->() const CDS_NOEXCEPT
436 return iterator_base::pointer();
439 value_ref operator *() const CDS_NOEXCEPT
441 value_ptr p = iterator_base::pointer();
448 iterator_base::release();
451 template <bool IsConst2>
452 bool operator ==(bidirectional_iterator<IsConst2> const& rhs) const CDS_NOEXCEPT
454 return iterator_base::operator==( rhs );
457 template <bool IsConst2>
458 bool operator !=(bidirectional_iterator<IsConst2> const& rhs) const CDS_NOEXCEPT
460 return !( *this == rhs );
464 bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx, bool )
465 : iterator_base( set, pNode, idx, false )
468 bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx )
469 : iterator_base( set, pNode, idx )
473 /// Reverse bidirectional iterator
474 template <bool IsConst>
475 class reverse_bidirectional_iterator : public iterator_base
477 friend class MultiLevelHashSet;
480 typedef typename std::conditional< IsConst, value_type const*, value_type*>::type value_ptr; ///< Value pointer
481 typedef typename std::conditional< IsConst, value_type const&, value_type&>::type value_ref; ///< Value reference
484 reverse_bidirectional_iterator() CDS_NOEXCEPT
488 reverse_bidirectional_iterator( reverse_bidirectional_iterator const& rhs ) CDS_NOEXCEPT
489 : iterator_base( rhs )
492 reverse_bidirectional_iterator& operator=( reverse_bidirectional_iterator const& rhs) CDS_NOEXCEPT
494 iterator_base::operator=( rhs );
498 reverse_bidirectional_iterator& operator++()
500 iterator_base::operator--();
504 reverse_bidirectional_iterator& operator--()
506 iterator_base::operator++();
510 value_ptr operator ->() const CDS_NOEXCEPT
512 return iterator_base::pointer();
515 value_ref operator *() const CDS_NOEXCEPT
517 value_ptr p = iterator_base::pointer();
524 iterator_base::release();
527 template <bool IsConst2>
528 bool operator ==(reverse_bidirectional_iterator<IsConst2> const& rhs) const
530 return iterator_base::operator==( rhs );
533 template <bool IsConst2>
534 bool operator !=(reverse_bidirectional_iterator<IsConst2> const& rhs)
536 return !( *this == rhs );
540 reverse_bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx, bool )
541 : iterator_base( set, pNode, idx, false )
544 reverse_bidirectional_iterator( MultiLevelHashSet& set, array_node * pNode, size_t idx )
545 : iterator_base( set, pNode, idx, false )
547 iterator_base::backward();
553 #ifdef CDS_DOXYGEN_INVOKED
554 typedef implementation_defined iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional iterator" type
555 typedef implementation_defined const_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional const iterator" type
556 typedef implementation_defined reverse_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional reverse iterator" type
557 typedef implementation_defined const_reverse_iterator; ///< @ref cds_intrusive_MultilevelHashSet_iterators "bidirectional reverse const iterator" type
559 typedef bidirectional_iterator<false> iterator;
560 typedef bidirectional_iterator<true> const_iterator;
561 typedef reverse_bidirectional_iterator<false> reverse_iterator;
562 typedef reverse_bidirectional_iterator<true> const_reverse_iterator;
567 metrics const m_Metrics; ///< Metrics
568 array_node * m_Head; ///< Head array
569 item_counter m_ItemCounter; ///< Item counter
570 stat m_Stat; ///< Internal statistics
574 /// Creates empty set
576 @param head_bits: 2<sup>head_bits</sup> specifies the size of head array, minimum is 4.
577 @param array_bits: 2<sup>array_bits</sup> specifies the size of array node, minimum is 2.
579 Equation for \p head_bits and \p array_bits:
581 sizeof(hash_type) * 8 == head_bits + N * array_bits
583 where \p N is multi-level array depth.
585 MultiLevelHashSet( size_t head_bits = 8, size_t array_bits = 4 )
586 : m_Metrics(make_metrics( head_bits, array_bits ))
587 , m_Head( alloc_head_node())
590 /// Destructs the set and frees all data
594 free_array_node( m_Head );
599 The function inserts \p val in the set if it does not contain
600 an item with that hash.
602 Returns \p true if \p val is placed into the set, \p false otherwise.
604 bool insert( value_type& val )
606 return insert( val, [](value_type&) {} );
611 This function is intended for derived non-intrusive containers.
613 The function allows to split creating of new item into two part:
614 - create item with key only
615 - insert new item into the set
616 - if inserting is success, calls \p f functor to initialize \p val.
618 The functor signature is:
620 void func( value_type& val );
622 where \p val is the item inserted.
624 The user-defined functor is called only if the inserting is success.
626 @warning See \ref cds_intrusive_item_creating "insert item troubleshooting".
628 template <typename Func>
629 bool insert( value_type& val, Func f )
631 hash_type const& hash = hash_accessor()( val );
632 hash_splitter splitter( hash );
634 typename gc::Guard guard;
637 size_t nOffset = m_Metrics.head_node_size_log;
638 array_node * pArr = m_Head;
639 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
640 assert( nSlot < m_Metrics.head_node_size );
643 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
644 if ( slot.bits() == flag_array_node ) {
645 // array node, go down the tree
646 assert( slot.ptr() != nullptr );
647 nSlot = splitter.cut( m_Metrics.array_node_size_log );
648 assert( nSlot < m_Metrics.array_node_size );
649 pArr = to_array( slot.ptr() );
650 nOffset += m_Metrics.array_node_size_log;
652 else if ( slot.bits() == flag_array_converting ) {
653 // the slot is converting to array node right now
655 m_Stat.onSlotConverting();
659 assert(slot.bits() == 0 );
661 // protect data node by hazard pointer
662 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
663 // slot value has been changed - retry
664 m_Stat.onSlotChanged();
666 else if ( slot.ptr() ) {
667 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
668 // the item with that hash value already exists
669 m_Stat.onInsertFailed();
673 // the slot must be expanded
674 expand_slot( pArr, nSlot, slot, nOffset );
677 // the slot is empty, try to insert data node
679 if ( pArr->nodes[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
681 // the new data node has been inserted
684 m_Stat.onInsertSuccess();
688 // insert failed - slot has been changed by another thread
690 m_Stat.onInsertRetry();
698 Performs inserting or updating the item with hash value equal to \p val.
699 - If hash value is found then existing item is replaced with \p val, old item is disposed
700 with \p Traits::disposer. Note that the disposer is called by \p GC asynchronously.
701 The function returns <tt> std::pair<true, false> </tt>
702 - If hash value is not found and \p bInsert is \p true then \p val is inserted,
703 the function returns <tt> std::pair<true, true> </tt>
704 - If hash value is not found and \p bInsert is \p false then the set is unchanged,
705 the function returns <tt> std::pair<false, false> </tt>
707 Returns <tt> std::pair<bool, bool> </tt> where \p first is \p true if operation is successfull
708 (i.e. the item has been inserted or updated),
709 \p second is \p true if new item has been added or \p false if the set contains that hash.
711 std::pair<bool, bool> update( value_type& val, bool bInsert = true )
713 return do_update(val, [](value_type&, value_type *) {}, bInsert );
716 /// Unlinks the item \p val from the set
718 The function searches the item \p val in the set and unlink it
719 if it is found and its address is equal to <tt>&val</tt>.
721 The function returns \p true if success and \p false otherwise.
723 bool unlink( value_type const& val )
725 typename gc::Guard guard;
726 auto pred = [&val](value_type const& item) -> bool { return &item == &val; };
727 value_type * p = do_erase( hash_accessor()( val ), guard, std::ref( pred ));
731 /// Deletes the item from the set
733 The function searches \p hash in the set,
734 unlinks the item found, and returns \p true.
735 If that item is not found the function returns \p false.
737 The \ref disposer specified in \p Traits is called by garbage collector \p GC asynchronously.
739 bool erase( hash_type const& hash )
741 return erase(hash, [](value_type const&) {} );
744 /// Deletes the item from the set
746 The function searches \p hash in the set,
747 call \p f functor with item found, and unlinks it from the set.
748 The \ref disposer specified in \p Traits is called
749 by garbage collector \p GC asynchronously.
751 The \p Func interface is
754 void operator()( value_type& item );
758 If \p hash is not found the function returns \p false.
760 template <typename Func>
761 bool erase( hash_type const& hash, Func f )
763 typename gc::Guard guard;
764 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
766 // p is guarded by HP
774 /// Deletes the item pointed by iterator \p iter
776 Returns \p true if the operation is successful, \p false otherwise.
778 The function does not invalidate the iterator, it remains valid and can be used for further traversing.
780 bool erase_at( iterator const& iter )
782 return do_erase_at( iter );
785 bool erase_at( reverse_iterator const& iter )
787 return do_erase_at( iter );
791 /// Extracts the item with specified \p hash
793 The function searches \p hash in the set,
794 unlinks it from the set, and returns an guarded pointer to the item extracted.
795 If \p hash is not found the function returns an empty guarded pointer.
797 The \p disposer specified in \p Traits class' template parameter is called automatically
798 by garbage collector \p GC when returned \ref guarded_ptr object to be destroyed or released.
799 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
803 typedef cds::intrusive::MultiLevelHashSet< your_template_args > my_set;
807 my_set::guarded_ptr gp( theSet.extract( 5 ));
812 // Destructor of gp releases internal HP guard
816 guarded_ptr extract( hash_type const& hash )
820 typename gc::Guard guard;
821 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true;} );
823 // p is guarded by HP
830 /// Finds an item by it's \p hash
832 The function searches the item by \p hash and calls the functor \p f for item found.
833 The interface of \p Func functor is:
836 void operator()( value_type& item );
839 where \p item is the item found.
841 The functor may change non-key fields of \p item. Note that the functor is only guarantee
842 that \p item cannot be disposed during the functor is executing.
843 The functor does not serialize simultaneous access to the set's \p item. If such access is
844 possible you must provide your own synchronization schema on item level to prevent unsafe item modifications.
846 The function returns \p true if \p hash is found, \p false otherwise.
848 template <typename Func>
849 bool find( hash_type const& hash, Func f )
851 typename gc::Guard guard;
852 value_type * p = search( hash, guard );
854 // p is guarded by HP
862 /// Checks whether the set contains \p hash
864 The function searches the item by its \p hash
865 and returns \p true if it is found, or \p false otherwise.
867 bool contains( hash_type const& hash )
869 return find( hash, [](value_type&) {} );
872 /// Finds an item by it's \p hash and returns the item found
874 The function searches the item by its \p hash
875 and returns the guarded pointer to the item found.
876 If \p hash is not found the function returns an empty \p guarded_ptr.
878 @note Each \p guarded_ptr object uses one GC's guard which can be limited resource.
882 typedef cds::intrusive::MultiLevelHashSet< your_template_params > my_set;
886 my_set::guarded_ptr gp( theSet.get( 5 ));
887 if ( theSet.get( 5 )) {
891 // Destructor of guarded_ptr releases internal HP guard
895 guarded_ptr get( hash_type const& hash )
899 typename gc::Guard guard;
900 gp.reset( search( hash, guard ));
905 /// Clears the set (non-atomic)
907 The function unlink all data node from the set.
908 The function is not atomic but is thread-safe.
909 After \p %clear() the set may not be empty because another threads may insert items.
911 For each item the \p disposer is called after unlinking.
915 clear_array( m_Head, head_size() );
918 /// Checks if the set is empty
920 Emptiness is checked by item counting: if item count is zero then the set is empty.
921 Thus, the correct item counting feature is an important part of the set implementation.
928 /// Returns item count in the set
931 return m_ItemCounter;
934 /// Returns const reference to internal statistics
935 stat const& statistics() const
940 /// Returns the size of head node
941 size_t head_size() const
943 return m_Metrics.head_node_size;
946 /// Returns the size of the array node
947 size_t array_node_size() const
949 return m_Metrics.array_node_size;
953 ///@name Thread-safe iterators
954 /** @anchor cds_intrusive_MultilevelHashSet_iterators
955 The set supports thread-safe iterators: you may iterate over the set in multi-threaded environment.
956 It is guaranteed that the iterators will remain valid even if another thread deletes the node the iterator points to:
957 Hazard Pointer embedded into the iterator object protects the node from physical reclamation.
959 @note Since the iterator object contains hazard pointer that is a thread-local resource,
960 the iterator should not be passed to another thread.
962 Each iterator object supports the common interface:
963 - dereference operators:
965 value_type [const] * operator ->() noexcept
966 value_type [const] & operator *() noexcept
968 - pre-increment and pre-decrement. Post-operators is not supported
969 - equality operators <tt>==</tt> and <tt>!=</tt>.
970 Iterators are equal iff they point to the same cell of the same array node.
971 Note that for two iterators \p it1 and \p it2, the conditon <tt> it1 == it2 </tt>
972 does not entail <tt> &(*it1) == &(*it2) </tt>: welcome to concurrent containers
973 - helper member function \p release() that clears internal hazard pointer.
974 After \p release() the iterator points to \p nullptr but it still remain valid: further iterating is possible.
978 /// Returns an iterator to the beginning of the set
981 return iterator( *this, m_Head, size_t(0) - 1 );
984 /// Returns an const iterator to the beginning of the set
985 const_iterator begin() const
987 return const_iterator( *this, m_Head, size_t(0) - 1 );
990 /// Returns an const iterator to the beginning of the set
991 const_iterator cbegin()
993 return const_iterator( *this, m_Head, size_t(0) - 1 );
996 /// 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.
999 return iterator( *this, m_Head, head_size(), false );
1002 /// 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.
1003 const_iterator end() const
1005 return const_iterator( *this, m_Head, head_size(), false );
1008 /// 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.
1009 const_iterator cend()
1011 return const_iterator( *this, m_Head, head_size(), false );
1014 /// Returns a reverse iterator to the first element of the reversed set
1015 reverse_iterator rbegin()
1017 return reverse_iterator( *this, m_Head, head_size() );
1020 /// Returns a const reverse iterator to the first element of the reversed set
1021 const_reverse_iterator rbegin() const
1023 return const_reverse_iterator( *this, m_Head, head_size() );
1026 /// Returns a const reverse iterator to the first element of the reversed set
1027 const_reverse_iterator crbegin()
1029 return const_reverse_iterator( *this, m_Head, head_size() );
1032 /// Returns a reverse iterator to the element following the last element of the reversed set
1034 It corresponds to the element preceding the first element of the non-reversed container.
1035 This element acts as a placeholder, attempting to access it results in undefined behavior.
1037 reverse_iterator rend()
1039 return reverse_iterator( *this, m_Head, size_t(0) - 1, false );
1042 /// Returns a const reverse iterator to the element following the last element of the reversed set
1044 It corresponds to the element preceding the first element of the non-reversed container.
1045 This element acts as a placeholder, attempting to access it results in undefined behavior.
1047 const_reverse_iterator rend() const
1049 return const_reverse_iterator( *this, m_Head, size_t(0) - 1, false );
1052 /// Returns a const reverse iterator to the element following the last element of the reversed set
1054 It corresponds to the element preceding the first element of the non-reversed container.
1055 This element acts as a placeholder, attempting to access it results in undefined behavior.
1057 const_reverse_iterator crend()
1059 return const_reverse_iterator( *this, m_Head, size_t(0) - 1, false );
1065 static metrics make_metrics( size_t head_bits, size_t array_bits )
1067 size_t const hash_bits = sizeof( hash_type ) * 8;
1069 if ( array_bits < 2 )
1071 if ( head_bits < 4 )
1073 if ( head_bits > hash_bits )
1074 head_bits = hash_bits;
1075 if ( (hash_bits - head_bits) % array_bits != 0 )
1076 head_bits += (hash_bits - head_bits) % array_bits;
1078 assert( (hash_bits - head_bits) % array_bits == 0 );
1081 m.head_node_size_log = head_bits;
1082 m.head_node_size = size_t(1) << head_bits;
1083 m.array_node_size_log = array_bits;
1084 m.array_node_size = size_t(1) << array_bits;
1088 array_node * alloc_head_node() const
1090 return alloc_array_node( head_size(), nullptr, 0 );
1093 array_node * alloc_array_node( array_node * pParent, size_t idxParent ) const
1095 return alloc_array_node( array_node_size(), pParent, idxParent );
1098 static array_node * alloc_array_node( size_t nSize, array_node * pParent, size_t idxParent )
1100 array_node * pNode = cxx_array_node_allocator().NewBlock( sizeof(array_node) + sizeof(atomic_node_ptr) * (nSize - 1), pParent, idxParent );
1101 for ( atomic_node_ptr * p = pNode->nodes, *pEnd = pNode->nodes + nSize; p < pEnd; ++p )
1102 p->store( node_ptr(), memory_model::memory_order_release );
1106 static void free_array_node( array_node * parr )
1108 cxx_array_node_allocator().Delete( parr );
1113 // The function is not thread-safe. For use in dtor only
1117 // Destroy all array nodes
1118 destroy_array_nodes( m_Head, head_size());
1121 void destroy_array_nodes( array_node * pArr, size_t nSize )
1123 for ( atomic_node_ptr * p = pArr->nodes, *pLast = pArr->nodes + nSize; p != pLast; ++p ) {
1124 node_ptr slot = p->load( memory_model::memory_order_acquire );
1125 if ( slot.bits() == flag_array_node ) {
1126 destroy_array_nodes(to_array(slot.ptr()), array_node_size());
1127 free_array_node( to_array(slot.ptr()));
1128 p->store(node_ptr(), memory_model::memory_order_relaxed );
1133 void clear_array( array_node * pArrNode, size_t nSize )
1138 for ( atomic_node_ptr * pArr = pArrNode->nodes, *pLast = pArr + nSize; pArr != pLast; ++pArr ) {
1140 node_ptr slot = pArr->load( memory_model::memory_order_acquire );
1141 if ( slot.bits() == flag_array_node ) {
1142 // array node, go down the tree
1143 assert( slot.ptr() != nullptr );
1144 clear_array( to_array( slot.ptr()), array_node_size() );
1147 else if ( slot.bits() == flag_array_converting ) {
1148 // the slot is converting to array node right now
1149 while ( (slot = pArr->load(memory_model::memory_order_acquire)).bits() == flag_array_converting ) {
1151 m_Stat.onSlotConverting();
1155 assert( slot.ptr() != nullptr );
1156 assert(slot.bits() == flag_array_node );
1157 clear_array( to_array( slot.ptr()), array_node_size() );
1162 if ( pArr->compare_exchange_strong( slot, node_ptr(), memory_model::memory_order_acquire, atomics::memory_order_relaxed )) {
1164 gc::template retire<disposer>( slot.ptr() );
1166 m_Stat.onEraseSuccess();
1179 converter( value_type * p )
1183 converter( array_node * p )
1188 static array_node * to_array( value_type * p )
1190 return converter( p ).pArr;
1192 static value_type * to_node( array_node * p )
1194 return converter( p ).pData;
1197 bool expand_slot( array_node * pParent, size_t idxParent, node_ptr current, size_t nOffset )
1199 assert( current.bits() == 0 );
1200 assert( current.ptr() );
1202 size_t idx = hash_splitter(hash_accessor()(*current.ptr()), nOffset).cut( m_Metrics.array_node_size_log );
1203 array_node * pArr = alloc_array_node( pParent, idxParent );
1205 node_ptr cur(current.ptr());
1206 atomic_node_ptr& slot = pParent->nodes[idxParent];
1207 if ( !slot.compare_exchange_strong( cur, cur | flag_array_converting, memory_model::memory_order_release, atomics::memory_order_relaxed ))
1209 m_Stat.onExpandNodeFailed();
1210 free_array_node( pArr );
1214 pArr->nodes[idx].store( current, memory_model::memory_order_release );
1216 cur = cur | flag_array_converting;
1218 slot.compare_exchange_strong( cur, node_ptr( to_node( pArr ), flag_array_node ), memory_model::memory_order_release, atomics::memory_order_relaxed )
1221 m_Stat.onArrayNodeCreated();
1228 value_type * search( hash_type const& hash, typename gc::Guard& guard )
1230 hash_splitter splitter( hash );
1231 hash_comparator cmp;
1234 array_node * pArr = m_Head;
1235 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1236 assert( nSlot < m_Metrics.head_node_size );
1239 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1240 if ( slot.bits() == flag_array_node ) {
1241 // array node, go down the tree
1242 assert( slot.ptr() != nullptr );
1243 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1244 assert( nSlot < m_Metrics.array_node_size );
1245 pArr = to_array( slot.ptr() );
1247 else if ( slot.bits() == flag_array_converting ) {
1248 // the slot is converting to array node right now
1250 m_Stat.onSlotConverting();
1254 assert(slot.bits() == 0 );
1256 // protect data node by hazard pointer
1257 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1258 // slot value has been changed - retry
1259 m_Stat.onSlotChanged();
1261 else if ( slot.ptr() && cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
1263 m_Stat.onFindSuccess();
1266 m_Stat.onFindFailed();
1272 template <typename Predicate>
1273 value_type * do_erase( hash_type const& hash, typename gc::Guard& guard, Predicate pred )
1275 hash_splitter splitter( hash );
1276 hash_comparator cmp;
1279 array_node * pArr = m_Head;
1280 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1281 assert( nSlot < m_Metrics.head_node_size );
1284 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1285 if ( slot.bits() == flag_array_node ) {
1286 // array node, go down the tree
1287 assert( slot.ptr() != nullptr );
1288 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1289 assert( nSlot < m_Metrics.array_node_size );
1290 pArr = to_array( slot.ptr() );
1292 else if ( slot.bits() == flag_array_converting ) {
1293 // the slot is converting to array node right now
1295 m_Stat.onSlotConverting();
1299 assert(slot.bits() == 0 );
1301 // protect data node by hazard pointer
1302 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1303 // slot value has been changed - retry
1304 m_Stat.onSlotChanged();
1306 else if ( slot.ptr() ) {
1307 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 && pred( *slot.ptr() )) {
1308 // item found - replace it with nullptr
1309 if ( pArr->nodes[nSlot].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed) ) {
1310 // slot is guarded by HP
1311 gc::template retire<disposer>( slot.ptr() );
1313 m_Stat.onEraseSuccess();
1317 m_Stat.onEraseRetry();
1320 m_Stat.onEraseFailed();
1324 // the slot is empty
1325 m_Stat.onEraseFailed();
1332 bool do_erase_at( iterator_base const& iter )
1334 if ( iter.m_set != this )
1336 if ( iter.m_pNode == m_Head && iter.m_idx >= head_size())
1338 if ( iter.m_idx >= array_node_size() )
1342 node_ptr slot = iter.m_pNode->nodes[iter.m_idx].load( memory_model::memory_order_acquire );
1343 if ( slot.bits() == 0 && slot.ptr() == iter.pointer() ) {
1344 if ( iter.m_pNode->nodes[iter.m_idx].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed) ) {
1345 // the item is guarded by iterator, so we may retire it safely
1346 gc::template retire<disposer>( slot.ptr() );
1348 m_Stat.onEraseSuccess();
1357 template <typename Func>
1358 std::pair<bool, bool> do_update( value_type& val, Func f, bool bInsert = true )
1360 hash_type const& hash = hash_accessor()( val );
1361 hash_splitter splitter( hash );
1362 hash_comparator cmp;
1363 typename gc::template GuardArray<2> guards;
1366 array_node * pArr = m_Head;
1367 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1368 assert( nSlot < m_Metrics.head_node_size );
1369 size_t nOffset = m_Metrics.head_node_size_log;
1371 guards.assign( 1, &val );
1374 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1375 if ( slot.bits() == flag_array_node ) {
1376 // array node, go down the tree
1377 assert( slot.ptr() != nullptr );
1378 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1379 assert( nSlot < m_Metrics.array_node_size );
1380 pArr = to_array( slot.ptr() );
1381 nOffset += m_Metrics.array_node_size_log;
1383 else if ( slot.bits() == flag_array_converting ) {
1384 // the slot is converting to array node right now
1386 m_Stat.onSlotConverting();
1390 assert(slot.bits() == 0 );
1392 // protect data node by hazard pointer
1393 if ( guards.protect( 0, pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1394 // slot value has been changed - retry
1395 m_Stat.onSlotChanged();
1397 else if ( slot.ptr() ) {
1398 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
1399 // the item with that hash value already exists
1400 // Replace it with val
1401 if ( slot.ptr() == &val ) {
1402 m_Stat.onUpdateExisting();
1403 return std::make_pair( true, false );
1406 if ( pArr->nodes[nSlot].compare_exchange_strong( slot, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed )) {
1407 // slot can be disposed
1408 f( val, slot.ptr() );
1409 gc::template retire<disposer>( slot.ptr() );
1410 m_Stat.onUpdateExisting();
1411 return std::make_pair( true, false );
1414 m_Stat.onUpdateRetry();
1418 // the slot must be expanded
1419 expand_slot( pArr, nSlot, slot, nOffset );
1422 // the slot is empty, try to insert data node
1425 if ( pArr->nodes[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
1427 // the new data node has been inserted
1430 m_Stat.onUpdateNew();
1431 return std::make_pair( true, true );
1435 m_Stat.onUpdateFailed();
1436 return std::make_pair( false, false );
1439 // insert failed - slot has been changed by another thread
1441 m_Stat.onUpdateRetry();
1448 }} // namespace cds::intrusive
1453 template <class GC, typename T, typename Traits>
1454 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator >
1456 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator iterator_class;
1458 // difference_type is not applicable for that iterator
1459 // typedef ??? difference_type
1460 typedef T value_type;
1461 typedef typename iterator_class::value_ptr pointer;
1462 typedef typename iterator_class::value_ref reference;
1463 typedef bidirectional_iterator_tag iterator_category;
1466 template <class GC, typename T, typename Traits>
1467 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator >
1469 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator iterator_class;
1471 // difference_type is not applicable for that iterator
1472 // typedef ??? difference_type
1473 typedef T value_type;
1474 typedef typename iterator_class::value_ptr pointer;
1475 typedef typename iterator_class::value_ref reference;
1476 typedef bidirectional_iterator_tag iterator_category;
1482 #endif // #ifndef CDSLIB_INTRUSIVE_IMPL_MULTILEVEL_HASHSET_H