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 whitch 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 map 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 defined as \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 hash_type const& hash = hash_accessor()( val );
619 hash_splitter splitter( hash );
621 typename gc::Guard guard;
624 array_node * pArr = m_Head;
625 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
626 assert( nSlot < m_Metrics.head_node_size );
627 size_t nOffset = m_Metrics.head_node_size_log;
630 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
631 if ( slot.bits() == flag_array_node ) {
632 // array node, go down the tree
633 assert( slot.ptr() != nullptr );
634 nSlot = splitter.cut( m_Metrics.array_node_size_log );
635 assert( nSlot < m_Metrics.array_node_size );
636 pArr = to_array( slot.ptr() );
637 nOffset += m_Metrics.array_node_size_log;
639 else if ( slot.bits() == flag_array_converting ) {
640 // the slot is converting to array node right now
642 m_Stat.onSlotConverting();
646 assert(slot.bits() == 0 );
648 // protect data node by hazard pointer
649 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
650 // slot value has been changed - retry
651 m_Stat.onSlotChanged();
653 else if ( slot.ptr() ) {
654 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
655 // the item with that hash value already exists
656 // Replace it with val
657 if ( slot.ptr() == &val ) {
658 m_Stat.onUpdateExisting();
659 return std::make_pair( true, false );
662 if ( pArr->nodes[nSlot].compare_exchange_strong( slot, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed )) {
663 // slot can be disposed
664 gc::template retire<disposer>( slot.ptr() );
665 m_Stat.onUpdateExisting();
666 return std::make_pair( true, false );
669 m_Stat.onUpdateRetry();
673 // the slot must be expanded
674 expand_slot( pArr, nSlot, slot, nOffset );
677 // the slot is empty, try to insert data node
680 if ( pArr->nodes[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
682 // the new data node has been inserted
684 m_Stat.onUpdateNew();
685 return std::make_pair( true, true );
689 m_Stat.onUpdateFailed();
690 return std::make_pair( false, false );
693 // insert failed - slot has been changed by another thread
695 m_Stat.onUpdateRetry();
701 /// Unlinks the item \p val from the set
703 The function searches the item \p val in the set and unlink it
704 if it is found and its address is equal to <tt>&val</tt>.
706 The function returns \p true if success and \p false otherwise.
708 bool unlink( value_type const& val )
710 typename gc::Guard guard;
711 auto pred = [&val](value_type const& item) -> bool { return &item == &val; };
712 value_type * p = do_erase( hash_accessor()( val ), guard, std::ref( pred ));
714 // p is guarded by HP
716 gc::template retire<disposer>( p );
718 m_Stat.onEraseSuccess();
724 /// Deletes the item from the set
726 The function searches \p hash in the set,
727 unlinks the item found, and returns \p true.
728 If that item is not found the function returns \p false.
730 The \ref disposer specified in \p Traits is called by garbage collector \p GC asynchronously.
733 bool erase( hash_type const& hash )
735 return erase(hash, [](value_type const&) {} );
738 /// Deletes the item from the set
740 The function searches \p hash in the set,
741 call \p f functor with item found, and unlinks it from the set.
742 The \ref disposer specified in \p Traits is called
743 by garbage collector \p GC asynchronously.
745 The \p Func interface is
748 void operator()( value_type& item );
752 If \p hash is not found the function returns \p false.
754 template <typename Func>
755 bool erase( hash_type const& hash, Func f )
757 typename gc::Guard guard;
758 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
760 // p is guarded by HP
762 gc::template retire<disposer>( p );
765 m_Stat.onEraseSuccess();
771 /// Deletes the item pointed by iterator \p it
773 Returns \p true if the operation is successful, \p false otherwise.
775 The function does not invalidate the iterator, it remains valid and can be used for further traversing.
777 bool erase_at( iterator const& it )
779 if ( it.m_set != this )
781 if ( it.m_pNode == m_Head && it.m_idx >= head_size())
783 if ( it.m_idx >= array_node_size() )
787 node_ptr slot = it.m_pNode->nodes[it.m_idx].load( memory_model::memory_order_acquire );
788 if ( slot.bits() == 0 && slot.ptr() == it.pointer() ) {
789 if ( it.m_pNode->nodes[it.m_idx].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed) ) {
790 // the item is guarded by iterator, so we may retire it safely
791 gc::template retire<disposer>( slot.ptr() );
793 m_Stat.onEraseSuccess();
802 /// Extracts the item with specified \p hash
804 The function searches \p hash in the set,
805 unlinks it from the set, and returns an guarded pointer to the item extracted.
806 If \p hash is not found the function returns an empty guarded pointer.
808 The \p disposer specified in \p Traits class' template parameter is called automatically
809 by garbage collector \p GC when returned \ref guarded_ptr object to be destroyed or released.
810 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
814 typedef cds::intrusive::MultiLevelHashSet< your_template_args > my_set;
818 my_set::guarded_ptr gp( theSet.extract( 5 ));
823 // Destructor of gp releases internal HP guard
827 guarded_ptr extract( hash_type const& hash )
831 typename gc::Guard guard;
832 value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
834 // p is guarded by HP
836 gc::template retire<disposer>( p );
838 m_Stat.onEraseSuccess();
845 /// Finds an item by it's \p hash
847 The function searches the item by \p hash and calls the functor \p f for item found.
848 The interface of \p Func functor is:
851 void operator()( value_type& item );
854 where \p item is the item found.
856 The functor may change non-key fields of \p item. Note that the functor is only guarantee
857 that \p item cannot be disposed during the functor is executing.
858 The functor does not serialize simultaneous access to the set's \p item. If such access is
859 possible you must provide your own synchronization schema on item level to prevent unsafe item modifications.
861 The function returns \p true if \p hash is found, \p false otherwise.
863 template <typename Func>
864 bool find( hash_type const& hash, Func f )
866 typename gc::Guard guard;
867 value_type * p = search( hash, guard );
869 // p is guarded by HP
877 /// Checks whether the set contains \p hash
879 The function searches the item by its \p hash
880 and returns \p true if it is found, or \p false otherwise.
882 bool contains( hash_type const& hash )
884 return find( hash, [](value_type&) {} );
887 /// Finds an item by it's \p hash and returns the item found
889 The function searches the item by its \p hash
890 and returns the guarded pointer to the item found.
891 If \p hash is not found the function returns an empty \p guarded_ptr.
893 @note Each \p guarded_ptr object uses one GC's guard which can be limited resource.
897 typedef cds::intrusive::MultiLevelHashSet< your_template_params > my_set;
901 my_set::guarded_ptr gp( theSet.get( 5 ));
902 if ( theSet.get( 5 )) {
906 // Destructor of guarded_ptr releases internal HP guard
910 guarded_ptr get( hash_type const& hash )
914 typename gc::Guard guard;
915 gp.reset( search( hash, guard ));
920 /// Clears the set (non-atomic)
922 The function unlink all data node from the set.
923 The function is not atomic but is thread-safe.
924 After \p %clear() the set may not be empty because another threads may insert items.
926 For each item the \p disposer is called after unlinking.
930 clear_array( m_Head, head_size() );
933 /// Checks if the set is empty
935 Emptiness is checked by item counting: if item count is zero then the set is empty.
936 Thus, the correct item counting feature is an important part of the set implementation.
943 /// Returns item count in the set
946 return m_ItemCounter;
949 /// Returns const reference to internal statistics
950 stat const& statistics() const
955 /// Returns the size of head node
956 size_t head_size() const
958 return m_Metrics.head_node_size;
961 /// Returns the size of the array node
962 size_t array_node_size() const
964 return m_Metrics.array_node_size;
968 ///@name Thread-safe iterators
969 /** @anchor cds_intrusive_MultilevelHashSet_iterators
970 The set supports thread-safe iterators: you may iterate over the set in multi-threaded environment.
\r
971 It is guaranteed that the iterators will remain valid even if another thread deletes the node the iterator points to:
\r
972 Hazard Pointer embedded into the iterator object protects the node from physical reclamation.
\r
974 @note Since the iterator object contains hazard pointer that is a thread-local resource,
\r
975 the iterator should not be passed to another thread.
\r
977 Each iterator object supports the common interface:
\r
978 - dereference operators:
\r
980 value_type [const] * operator ->() noexcept
\r
981 value_type [const] & operator *() noexcept
\r
983 - pre-increment and pre-decrement. Post-operators is not supported
\r
984 - equality operators <tt>==</tt> and <tt>!=</tt>.
\r
985 Iterators are equal iff they point to the same cell of the same array node.
986 Note that for two iterators \p it1 and \p it2, the conditon <tt> it1 == it2 </tt>
987 does not entail <tt> &(*it1) == &(*it2) </tt>: welcome to concurrent containers
988 - helper member function \p release() that clears internal hazard pointer.
\r
989 After \p release() call the iterator points to \p nullptr but it still remain valid: further iterating is possible.
993 /// Returns an iterator to the beginning of the set
996 return iterator( *this, m_Head, size_t(0) - 1 );
999 /// Returns an const iterator to the beginning of the set
1000 const_iterator begin() const
1002 return const_iterator( *this, m_Head, size_t(0) - 1 );
1005 /// Returns an const iterator to the beginning of the set
1006 const_iterator cbegin()
1008 return const_iterator( *this, m_Head, size_t(0) - 1 );
1011 /// 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.
1014 return iterator( *this, m_Head, head_size() - 1 );
1017 /// 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.
1018 const_iterator end() const
1020 return const_iterator( *this, m_Head, head_size() - 1 );
1023 /// 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.
1024 const_iterator cend()
1026 return const_iterator( *this, m_Head, head_size() - 1 );
1029 /// Returns a reverse iterator to the first element of the reversed set
1030 reverse_iterator rbegin()
1032 return reverse_iterator( *this, m_Head, head_size() );
1035 /// Returns a const reverse iterator to the first element of the reversed set
1036 const_reverse_iterator rbegin() const
1038 return const_reverse_iterator( *this, m_Head, head_size() );
1041 /// Returns a const reverse iterator to the first element of the reversed set
1042 const_reverse_iterator crbegin()
1044 return const_reverse_iterator( *this, m_Head, head_size() );
1047 /// Returns a reverse iterator to the element following the last element of the reversed set
1049 It corresponds to the element preceding the first element of the non-reversed container.
1050 This element acts as a placeholder, attempting to access it results in undefined behavior.
1052 reverse_iterator rend()
1054 return reverse_iterator( *this, m_Head, 0 );
1057 /// Returns a const reverse iterator to the element following the last element of the reversed set
1059 It corresponds to the element preceding the first element of the non-reversed container.
1060 This element acts as a placeholder, attempting to access it results in undefined behavior.
1062 const_reverse_iterator rend() const
1064 return const_reverse_iterator( *this, m_Head, 0 );
1067 /// Returns a const reverse iterator to the element following the last element of the reversed set
1069 It corresponds to the element preceding the first element of the non-reversed container.
1070 This element acts as a placeholder, attempting to access it results in undefined behavior.
1072 const_reverse_iterator crend()
1074 return const_reverse_iterator( *this, m_Head, 0 );
1080 static metrics make_metrics( size_t head_bits, size_t array_bits )
1082 size_t const hash_bits = sizeof( hash_type ) * 8;
1084 if ( array_bits < 2 )
1086 if ( head_bits < 4 )
1088 if ( head_bits > hash_bits )
1089 head_bits = hash_bits;
1090 if ( (hash_bits - head_bits) % array_bits != 0 )
1091 head_bits += (hash_bits - head_bits) % array_bits;
1093 assert( (hash_bits - head_bits) % array_bits == 0 );
1096 m.head_node_size_log = head_bits;
1097 m.head_node_size = size_t(1) << head_bits;
1098 m.array_node_size_log = array_bits;
1099 m.array_node_size = size_t(1) << array_bits;
1103 array_node * alloc_head_node() const
1105 return alloc_array_node( head_size(), nullptr, 0 );
1108 array_node * alloc_array_node( array_node * pParent, size_t idxParent ) const
1110 return alloc_array_node( array_node_size(), pParent, idxParent );
1113 static array_node * alloc_array_node( size_t nSize, array_node * pParent, size_t idxParent )
1115 array_node * pNode = cxx_array_node_allocator().NewBlock( sizeof(array_node) + sizeof(atomic_node_ptr) * (nSize - 1), pParent, idxParent );
1116 for ( atomic_node_ptr * p = pNode->nodes, *pEnd = pNode->nodes + nSize; p < pEnd; ++p )
1117 p->store( node_ptr(), memory_model::memory_order_release );
1121 static void free_array_node( array_node * parr )
1123 cxx_array_node_allocator().Delete( parr );
1128 // The function is not thread-safe. For use in dtor only
1132 // Destroy all array nodes
1133 destroy_array_nodes( m_Head, head_size());
1136 void destroy_array_nodes( array_node * pArr, size_t nSize )
1138 for ( atomic_node_ptr * p = pArr->nodes, *pLast = pArr->nodes + nSize; p != pLast; ++p ) {
1139 node_ptr slot = p->load( memory_model::memory_order_acquire );
1140 if ( slot.bits() == flag_array_node ) {
1141 destroy_array_nodes(to_array(slot.ptr()), array_node_size());
1142 free_array_node( to_array(slot.ptr()));
1143 p->store(node_ptr(), memory_model::memory_order_relaxed );
1148 void clear_array( array_node * pArrNode, size_t nSize )
1153 for ( atomic_node_ptr * pArr = pArrNode->nodes, *pLast = pArr + nSize; pArr != pLast; ++pArr ) {
1155 node_ptr slot = pArr->load( memory_model::memory_order_acquire );
1156 if ( slot.bits() == flag_array_node ) {
1157 // array node, go down the tree
1158 assert( slot.ptr() != nullptr );
1159 clear_array( to_array( slot.ptr()), array_node_size() );
1162 else if ( slot.bits() == flag_array_converting ) {
1163 // the slot is converting to array node right now
1164 while ( (slot = pArr->load(memory_model::memory_order_acquire)).bits() == flag_array_converting ) {
1166 m_Stat.onSlotConverting();
1170 assert( slot.ptr() != nullptr );
1171 assert(slot.bits() == flag_array_node );
1172 clear_array( to_array( slot.ptr()), array_node_size() );
1177 if ( pArr->compare_exchange_strong( slot, node_ptr(), memory_model::memory_order_acquire, atomics::memory_order_relaxed )) {
1179 gc::template retire<disposer>( slot.ptr() );
1181 m_Stat.onEraseSuccess();
1194 converter( value_type * p )
1198 converter( array_node * p )
1203 static array_node * to_array( value_type * p )
1205 return converter( p ).pArr;
1207 static value_type * to_node( array_node * p )
1209 return converter( p ).pData;
1212 bool expand_slot( array_node * pParent, size_t idxParent, node_ptr current, size_t nOffset )
1214 assert( current.bits() == 0 );
1215 assert( current.ptr() );
1217 size_t idx = hash_splitter(hash_accessor()(*current.ptr()), nOffset).cut( m_Metrics.array_node_size_log );
1218 array_node * pArr = alloc_array_node( pParent, idxParent );
1220 node_ptr cur(current.ptr());
1221 atomic_node_ptr& slot = pParent->nodes[idxParent];
1222 if ( !slot.compare_exchange_strong( cur, cur | flag_array_converting, memory_model::memory_order_release, atomics::memory_order_relaxed ))
1224 m_Stat.onExpandNodeFailed();
1225 free_array_node( pArr );
1229 pArr->nodes[idx].store( current, memory_model::memory_order_release );
1231 cur = cur | flag_array_converting;
1233 slot.compare_exchange_strong( cur, node_ptr( to_node( pArr ), flag_array_node ), memory_model::memory_order_release, atomics::memory_order_relaxed )
1236 m_Stat.onArrayNodeCreated();
1240 value_type * search( hash_type const& hash, typename gc::Guard& guard )
1242 hash_splitter splitter( hash );
1243 hash_comparator cmp;
1246 array_node * pArr = m_Head;
1247 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1248 assert( nSlot < m_Metrics.head_node_size );
1251 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1252 if ( slot.bits() == flag_array_node ) {
1253 // array node, go down the tree
1254 assert( slot.ptr() != nullptr );
1255 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1256 assert( nSlot < m_Metrics.array_node_size );
1257 pArr = to_array( slot.ptr() );
1259 else if ( slot.bits() == flag_array_converting ) {
1260 // the slot is converting to array node right now
1262 m_Stat.onSlotConverting();
1266 assert(slot.bits() == 0 );
1268 // protect data node by hazard pointer
1269 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1270 // slot value has been changed - retry
1271 m_Stat.onSlotChanged();
1273 else if ( slot.ptr() && cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
1275 m_Stat.onFindSuccess();
1278 m_Stat.onFindFailed();
1284 template <typename Predicate>
1285 value_type * do_erase( hash_type const& hash, typename gc::Guard& guard, Predicate pred )
1287 hash_splitter splitter( hash );
1288 hash_comparator cmp;
1291 array_node * pArr = m_Head;
1292 size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
1293 assert( nSlot < m_Metrics.head_node_size );
1296 node_ptr slot = pArr->nodes[nSlot].load( memory_model::memory_order_acquire );
1297 if ( slot.bits() == flag_array_node ) {
1298 // array node, go down the tree
1299 assert( slot.ptr() != nullptr );
1300 nSlot = splitter.cut( m_Metrics.array_node_size_log );
1301 assert( nSlot < m_Metrics.array_node_size );
1302 pArr = to_array( slot.ptr() );
1304 else if ( slot.bits() == flag_array_converting ) {
1305 // the slot is converting to array node right now
1307 m_Stat.onSlotConverting();
1311 assert(slot.bits() == 0 );
1313 // protect data node by hazard pointer
1314 if ( guard.protect( pArr->nodes[nSlot], [](node_ptr p) -> value_type * { return p.ptr(); }) != slot ) {
1315 // slot value has been changed - retry
1316 m_Stat.onSlotChanged();
1318 else if ( slot.ptr() ) {
1319 if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 && pred( *slot.ptr() )) {
1320 // item found - replace it with nullptr
1321 if ( pArr->nodes[nSlot].compare_exchange_strong(slot, node_ptr(nullptr), memory_model::memory_order_acquire, atomics::memory_order_relaxed))
1323 m_Stat.onEraseRetry();
1326 m_Stat.onEraseFailed();
1330 // the slot is empty
1331 m_Stat.onEraseFailed();
1340 }} // namespace cds::intrusive
1346 template <class GC, typename T, typename Traits>
1347 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator >
1349 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::iterator iterator_class;
1351 // difference_type is not applicable for that iterator
1352 // typedef ??? difference_type
1353 typedef T value_type;
1354 typedef typename iterator_class::value_ptr pointer;
1355 typedef typename iterator_class::value_ref reference;
1356 typedef bidirectional_iterator_tag iterator_category;
1359 template <class GC, typename T, typename Traits>
1360 struct iterator_traits< typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator >
1362 typedef typename cds::intrusive::MultiLevelHashSet< GC, T, Traits >::const_iterator iterator_class;
1364 // difference_type is not applicable for that iterator
1365 // typedef ??? difference_type
1366 typedef T value_type;
1367 typedef typename iterator_class::value_ptr pointer;
1368 typedef typename iterator_class::value_ref reference;
1369 typedef bidirectional_iterator_tag iterator_category;
1376 #endif // #ifndef CDSLIB_INTRUSIVE_IMPL_MULTILEVEL_HASHSET_H