Improved management of SkipList auxiliary nodes: now aux nodes are allocated from...
[libcds.git] / cds / intrusive / split_list_rcu.h
index e27c64d8a7dd5864ef6c59e3352f8e17a8a292e1..8ea2078d97e53085d4f3061f758f623ee66f98f9 100644 (file)
@@ -5,7 +5,7 @@
 
     Source code repo: http://github.com/khizmax/libcds/
     Download: http://sourceforge.net/projects/libcds/files/
-    
+
     Redistribution and use in source and binary forms, with or without
     modification, are permitted provided that the following conditions are met:
 
@@ -25,7 +25,7 @@
     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.     
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 #ifndef CDSLIB_INTRUSIVE_SPLIT_LIST_RCU_H
@@ -35,6 +35,7 @@
 
 #include <cds/intrusive/details/split_list_base.h>
 #include <cds/details/binary_functor_wrapper.h>
+#include <cds/details/type_padding.h>
 
 namespace cds { namespace intrusive {
 
@@ -123,10 +124,17 @@ namespace cds { namespace intrusive {
         typedef typename traits::memory_model memory_model; ///< Memory ordering. See cds::opt::memory_model option
         typedef typename traits::stat         stat;         ///< Internal statistics
 
+        // GC and OrderedList::gc must be the same
+        static_assert( std::is_same<gc, typename ordered_list::gc>::value, "GC and OrderedList::gc must be the same");
+
+        // atomicity::empty_item_counter is not allowed as a item counter
+        static_assert( !std::is_same<item_counter, cds::atomicity::empty_item_counter>::value,
+                        "cds::atomicity::empty_item_counter is not allowed as a item counter");
+
     protected:
         typedef typename ordered_list::node_type    list_node_type;  ///< Node type as declared in ordered list
         typedef split_list::node<list_node_type>    node_type;       ///< split-list node type
-        typedef node_type                           dummy_node_type; ///< dummy node type
+        typedef node_type                           aux_node_type; ///< dummy node type
 
         /// Split-list node traits
         /**
@@ -140,7 +148,7 @@ namespace cds { namespace intrusive {
         typedef typename split_list::details::bucket_table_selector<
             traits::dynamic_bucket_table
             , gc
-            , dummy_node_type
+            , aux_node_type
             , opt::allocator< typename traits::allocator >
             , opt::memory_model< memory_model >
         >::type bucket_table;
@@ -156,7 +164,7 @@ namespace cds { namespace intrusive {
             typedef typename base_class::auxiliary_head bucket_head_type;
 
         public:
-            bool insert_at( dummy_node_type * pHead, value_type& val )
+            bool insert_at( aux_node_type * pHead, value_type& val )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -164,7 +172,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Func>
-            bool insert_at( dummy_node_type * pHead, value_type& val, Func f )
+            bool insert_at( aux_node_type * pHead, value_type& val, Func f )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -172,14 +180,14 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Func>
-            std::pair<bool, bool> update_at( dummy_node_type * pHead, value_type& val, Func func, bool bAllowInsert )
+            std::pair<bool, bool> update_at( aux_node_type * pHead, value_type& val, Func func, bool bAllowInsert )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
                 return base_class::update_at( h, val, func, bAllowInsert );
             }
 
-            bool unlink_at( dummy_node_type * pHead, value_type& val )
+            bool unlink_at( aux_node_type * pHead, value_type& val )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -187,7 +195,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare, typename Func>
-            bool erase_at( dummy_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp, Func f )
+            bool erase_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp, Func f )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -195,7 +203,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare>
-            bool erase_at( dummy_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
+            bool erase_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -203,7 +211,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare>
-            value_type * extract_at( dummy_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp )
+            value_type * extract_at( aux_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -211,7 +219,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare, typename Func>
-            bool find_at( dummy_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp, Func f )
+            bool find_at( aux_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp, Func f )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -219,7 +227,7 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare>
-            bool find_at( dummy_node_type * pHead, split_list::details::search_value_type<Q> const & val, Compare cmp )
+            bool find_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const & val, Compare cmp )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
@@ -227,18 +235,18 @@ namespace cds { namespace intrusive {
             }
 
             template <typename Q, typename Compare>
-            raw_ptr get_at( dummy_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp )
+            raw_ptr get_at( aux_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp )
             {
                 assert( pHead != nullptr );
                 bucket_head_type h(pHead);
                 return base_class::get_at( h, val, cmp );
             }
 
-            bool insert_aux_node( dummy_node_type * pNode )
+            bool insert_aux_node( aux_node_type * pNode )
             {
                 return base_class::insert_aux_node( pNode );
             }
-            bool insert_aux_node( dummy_node_type * pHead, dummy_node_type * pNode )
+            bool insert_aux_node( aux_node_type * pHead, aux_node_type * pNode )
             {
                 bucket_head_type h(pHead);
                 return base_class::insert_aux_node( h, pNode );
@@ -264,244 +272,6 @@ namespace cds { namespace intrusive {
         };
         //@endcond
 
-    protected:
-        ordered_list_wrapper    m_List;             ///< Ordered list containing split-list items
-        bucket_table            m_Buckets;          ///< bucket table
-        atomics::atomic<size_t> m_nBucketCountLog2; ///< log2( current bucket count )
-        atomics::atomic<size_t> m_nMaxItemCount;    ///< number of items container can hold, before we have to resize
-        item_counter            m_ItemCounter;      ///< Item counter
-        hash                    m_HashFunctor;      ///< Hash functor
-        stat                    m_Stat;             ///< Internal statistics accumulator
-
-    protected:
-        //@cond
-        typedef cds::details::Allocator< dummy_node_type, typename traits::allocator >   dummy_node_allocator;
-
-        dummy_node_type * alloc_dummy_node( size_t nHash )
-        {
-            m_Stat.onHeadNodeAllocated();
-            return dummy_node_allocator().New( nHash );
-        }
-        void free_dummy_node( dummy_node_type * p )
-        {
-            dummy_node_allocator().Delete( p );
-            m_Stat.onHeadNodeFreed();
-        }
-
-        /// Calculates hash value of \p key
-        template <typename Q>
-        size_t hash_value( Q const& key ) const
-        {
-            return m_HashFunctor( key );
-        }
-
-        size_t bucket_no( size_t nHash ) const
-        {
-            return nHash & ( (1 << m_nBucketCountLog2.load(memory_model::memory_order_relaxed)) - 1 );
-        }
-
-        static size_t parent_bucket( size_t nBucket )
-        {
-            assert( nBucket > 0 );
-            return nBucket & ~( 1 << bitop::MSBnz( nBucket ) );
-        }
-
-        dummy_node_type * init_bucket( size_t nBucket )
-        {
-            assert( nBucket > 0 );
-            size_t nParent = parent_bucket( nBucket );
-
-            dummy_node_type * pParentBucket = m_Buckets.bucket( nParent );
-            if ( pParentBucket == nullptr ) {
-                pParentBucket = init_bucket( nParent );
-                m_Stat.onRecursiveInitBucket();
-            }
-
-            assert( pParentBucket != nullptr );
-
-            // Allocate a dummy node for new bucket
-            {
-                dummy_node_type * pBucket = alloc_dummy_node( split_list::dummy_hash( nBucket ) );
-                if ( m_List.insert_aux_node( pParentBucket, pBucket ) ) {
-                    m_Buckets.bucket( nBucket, pBucket );
-                    m_Stat.onNewBucket();
-                    return pBucket;
-                }
-                free_dummy_node( pBucket );
-            }
-
-            // Another thread set the bucket. Wait while it done
-
-            // In this point, we must wait while nBucket is empty.
-            // The compiler can decide that waiting loop can be "optimized" (stripped)
-            // To prevent this situation, we use waiting on volatile bucket_head_ptr pointer.
-            //
-            m_Stat.onBucketInitContenton();
-            back_off bkoff;
-            while ( true ) {
-                dummy_node_type volatile * p = m_Buckets.bucket( nBucket );
-                if ( p != nullptr )
-                    return const_cast<dummy_node_type *>( p );
-                bkoff();
-                m_Stat.onBusyWaitBucketInit();
-            }
-        }
-
-        dummy_node_type * get_bucket( size_t nHash )
-        {
-            size_t nBucket = bucket_no( nHash );
-
-            dummy_node_type * pHead = m_Buckets.bucket( nBucket );
-            if ( pHead == nullptr )
-                pHead = init_bucket( nBucket );
-
-            assert( pHead->is_dummy() );
-
-            return pHead;
-        }
-
-        void init()
-        {
-            // GC and OrderedList::gc must be the same
-            static_assert( std::is_same<gc, typename ordered_list::gc>::value, "GC and OrderedList::gc must be the same");
-
-            // atomicity::empty_item_counter is not allowed as a item counter
-            static_assert( !std::is_same<item_counter, cds::atomicity::empty_item_counter>::value,
-                           "cds::atomicity::empty_item_counter is not allowed as a item counter");
-
-            // Initialize bucket 0
-            dummy_node_type * pNode = alloc_dummy_node( 0 /*split_list::dummy_hash(0)*/ );
-
-            // insert_aux_node cannot return false for empty list
-            CDS_VERIFY( m_List.insert_aux_node( pNode ));
-
-            m_Buckets.bucket( 0, pNode );
-        }
-
-        static size_t max_item_count( size_t nBucketCount, size_t nLoadFactor )
-        {
-            return nBucketCount * nLoadFactor;
-        }
-
-        void inc_item_count()
-        {
-            size_t nMaxCount = m_nMaxItemCount.load(memory_model::memory_order_relaxed);
-            if ( ++m_ItemCounter <= nMaxCount )
-                return;
-
-            size_t sz = m_nBucketCountLog2.load(memory_model::memory_order_relaxed);
-            const size_t nBucketCount = static_cast<size_t>(1) << sz;
-            if ( nBucketCount < m_Buckets.capacity() ) {
-                // we may grow the bucket table
-                const size_t nLoadFactor = m_Buckets.load_factor();
-                if ( nMaxCount < max_item_count( nBucketCount, nLoadFactor ))
-                    return; // someone already have updated m_nBucketCountLog2, so stop here
-
-                m_nMaxItemCount.compare_exchange_strong( nMaxCount, max_item_count( nBucketCount << 1, nLoadFactor ),
-                                                         memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
-                m_nBucketCountLog2.compare_exchange_strong( sz, sz + 1, memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
-            }
-            else
-                m_nMaxItemCount.store( std::numeric_limits<size_t>::max(), memory_model::memory_order_relaxed );
-        }
-
-        template <typename Q, typename Compare, typename Func>
-        bool find_( Q& val, Compare cmp, Func f )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            return m_Stat.onFind( m_List.find_at( pHead, sv, cmp,
-                [&f](value_type& item, split_list::details::search_value_type<Q>& val){ f(item, val.val ); }));
-        }
-
-        template <typename Q, typename Compare>
-        bool find_value( Q const& val, Compare cmp )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            return m_Stat.onFind( m_List.find_at( pHead, sv, cmp ));
-        }
-
-        template <typename Q, typename Compare>
-        raw_ptr get_( Q const& val, Compare cmp )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            raw_ptr p = m_List.get_at( pHead, sv, cmp );
-            m_Stat.onFind( !!p );
-            return p;
-        }
-
-        template <typename Q, typename Compare>
-        value_type * extract_( Q const& val, Compare cmp )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            value_type * pNode = m_List.extract_at( pHead, sv, cmp );
-            if ( pNode ) {
-                --m_ItemCounter;
-                m_Stat.onExtractSuccess();
-            }
-            else
-                m_Stat.onExtractFailed();
-            return pNode;
-        }
-
-        template <typename Q, typename Less>
-        value_type * extract_with_( Q const& val, Less pred )
-        {
-            CDS_UNUSED( pred );
-            return extract_( val, typename wrapped_ordered_list::template make_compare_from_less<Less>());
-        }
-
-        template <typename Q, typename Compare>
-        bool erase_( const Q& val, Compare cmp )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            if ( m_List.erase_at( pHead, sv, cmp ) ) {
-                --m_ItemCounter;
-                m_Stat.onEraseSuccess();
-                return true;
-            }
-            m_Stat.onEraseFailed();
-            return false;
-        }
-
-        template <typename Q, typename Compare, typename Func>
-        bool erase_( Q const& val, Compare cmp, Func f )
-        {
-            size_t nHash = hash_value( val );
-            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
-            dummy_node_type * pHead = get_bucket( nHash );
-            assert( pHead != nullptr );
-
-            if ( m_List.erase_at( pHead, sv, cmp, f )) {
-                --m_ItemCounter;
-                m_Stat.onEraseSuccess();
-                return true;
-            }
-            m_Stat.onEraseFailed();
-            return false;
-        }
-
-        //@endcond
-
     public:
         /// Initialize split-ordered list of default capacity
         /**
@@ -541,7 +311,7 @@ namespace cds { namespace intrusive {
         bool insert( value_type& val )
         {
             size_t nHash = hash_value( val );
-            dummy_node_type * pHead = get_bucket( nHash );
+            aux_node_type * pHead = get_bucket( nHash );
             assert( pHead != nullptr );
 
             node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
@@ -580,7 +350,7 @@ namespace cds { namespace intrusive {
         bool insert( value_type& val, Func f )
         {
             size_t nHash = hash_value( val );
-            dummy_node_type * pHead = get_bucket( nHash );
+            aux_node_type * pHead = get_bucket( nHash );
             assert( pHead != nullptr );
 
             node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
@@ -616,7 +386,7 @@ namespace cds { namespace intrusive {
 
             The function applies RCU lock internally.
 
-            Returns std::pair<bool, bool> where \p first is \p true if operation is successfull,
+            Returns std::pair<bool, bool> where \p first is \p true if operation is successful,
             \p second is \p true if new item has been added or \p false if the item with \p key
             already is in the list.
 
@@ -628,7 +398,7 @@ namespace cds { namespace intrusive {
         std::pair<bool, bool> update( value_type& val, Func func, bool bAllowInsert = true )
         {
             size_t nHash = hash_value( val );
-            dummy_node_type * pHead = get_bucket( nHash );
+            aux_node_type * pHead = get_bucket( nHash );
             assert( pHead != nullptr );
 
             node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
@@ -668,7 +438,7 @@ namespace cds { namespace intrusive {
         bool unlink( value_type& val )
         {
             size_t nHash = hash_value( val );
-            dummy_node_type * pHead = get_bucket( nHash );
+            aux_node_type * pHead = get_bucket( nHash );
             assert( pHead != nullptr );
 
             if ( m_List.unlink_at( pHead, val ) ) {
@@ -1087,6 +857,244 @@ namespace cds { namespace intrusive {
             return const_iterator( m_List.cend(), m_List.cend() );
         }
     //@}
+
+    protected:
+        //@cond
+        aux_node_type * alloc_aux_node( size_t nHash )
+        {
+            m_Stat.onHeadNodeAllocated();
+            aux_node_type* p = m_Buckets.alloc_aux_node();
+            if ( p )
+                p->m_nHash = nHash;
+            return p;
+        }
+
+        void free_aux_node( aux_node_type * p )
+        {
+            m_Buckets.free_aux_node( p );
+            m_Stat.onHeadNodeFreed();
+        }
+
+        /// Calculates hash value of \p key
+        template <typename Q>
+        size_t hash_value( Q const& key ) const
+        {
+            return m_HashFunctor( key );
+        }
+
+        size_t bucket_no( size_t nHash ) const
+        {
+            return nHash & ( (1 << m_nBucketCountLog2.load(memory_model::memory_order_relaxed)) - 1 );
+        }
+
+        static size_t parent_bucket( size_t nBucket )
+        {
+            assert( nBucket > 0 );
+            return nBucket & ~( 1 << bitop::MSBnz( nBucket ) );
+        }
+
+        aux_node_type * init_bucket( size_t nBucket )
+        {
+            assert( nBucket > 0 );
+            size_t nParent = parent_bucket( nBucket );
+
+            aux_node_type * pParentBucket = m_Buckets.bucket( nParent );
+            if ( pParentBucket == nullptr ) {
+                pParentBucket = init_bucket( nParent );
+                m_Stat.onRecursiveInitBucket();
+            }
+
+            assert( pParentBucket != nullptr );
+
+            // Allocate a dummy node for new bucket
+            {
+                aux_node_type * pBucket = alloc_aux_node( split_list::dummy_hash( nBucket ) );
+                if ( m_List.insert_aux_node( pParentBucket, pBucket ) ) {
+                    m_Buckets.bucket( nBucket, pBucket );
+                    m_Stat.onNewBucket();
+                    return pBucket;
+                }
+                free_aux_node( pBucket );
+            }
+
+            // Another thread set the bucket. Wait while it done
+
+            // In this point, we must wait while nBucket is empty.
+            // The compiler can decide that waiting loop can be "optimized" (stripped)
+            // To prevent this situation, we use waiting on volatile bucket_head_ptr pointer.
+            //
+            m_Stat.onBucketInitContenton();
+            back_off bkoff;
+            while ( true ) {
+                aux_node_type volatile * p = m_Buckets.bucket( nBucket );
+                if ( p != nullptr )
+                    return const_cast<aux_node_type *>( p );
+                bkoff();
+                m_Stat.onBusyWaitBucketInit();
+            }
+        }
+
+        aux_node_type * get_bucket( size_t nHash )
+        {
+            size_t nBucket = bucket_no( nHash );
+
+            aux_node_type * pHead = m_Buckets.bucket( nBucket );
+            if ( pHead == nullptr )
+                pHead = init_bucket( nBucket );
+
+            assert( pHead->is_dummy() );
+
+            return pHead;
+        }
+
+        void init()
+        {
+            // Initialize bucket 0
+            aux_node_type * pNode = alloc_aux_node( 0 /*split_list::dummy_hash(0)*/ );
+
+            // insert_aux_node cannot return false for empty list
+            CDS_VERIFY( m_List.insert_aux_node( pNode ));
+
+            m_Buckets.bucket( 0, pNode );
+        }
+
+        static size_t max_item_count( size_t nBucketCount, size_t nLoadFactor )
+        {
+            return nBucketCount * nLoadFactor;
+        }
+
+        void inc_item_count()
+        {
+            size_t nMaxCount = m_nMaxItemCount.load(memory_model::memory_order_relaxed);
+            if ( ++m_ItemCounter <= nMaxCount )
+                return;
+
+            size_t sz = m_nBucketCountLog2.load(memory_model::memory_order_relaxed);
+            const size_t nBucketCount = static_cast<size_t>(1) << sz;
+            if ( nBucketCount < m_Buckets.capacity() ) {
+                // we may grow the bucket table
+                const size_t nLoadFactor = m_Buckets.load_factor();
+                if ( nMaxCount < max_item_count( nBucketCount, nLoadFactor ))
+                    return; // someone already have updated m_nBucketCountLog2, so stop here
+
+                m_nMaxItemCount.compare_exchange_strong( nMaxCount, max_item_count( nBucketCount << 1, nLoadFactor ),
+                                                         memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
+                m_nBucketCountLog2.compare_exchange_strong( sz, sz + 1, memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
+            }
+            else
+                m_nMaxItemCount.store( std::numeric_limits<size_t>::max(), memory_model::memory_order_relaxed );
+        }
+
+        template <typename Q, typename Compare, typename Func>
+        bool find_( Q& val, Compare cmp, Func f )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            return m_Stat.onFind( m_List.find_at( pHead, sv, cmp,
+                [&f](value_type& item, split_list::details::search_value_type<Q>& val){ f(item, val.val ); }));
+        }
+
+        template <typename Q, typename Compare>
+        bool find_value( Q const& val, Compare cmp )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            return m_Stat.onFind( m_List.find_at( pHead, sv, cmp ));
+        }
+
+        template <typename Q, typename Compare>
+        raw_ptr get_( Q const& val, Compare cmp )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            raw_ptr p = m_List.get_at( pHead, sv, cmp );
+            m_Stat.onFind( !!p );
+            return p;
+        }
+
+        template <typename Q, typename Compare>
+        value_type * extract_( Q const& val, Compare cmp )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            value_type * pNode = m_List.extract_at( pHead, sv, cmp );
+            if ( pNode ) {
+                --m_ItemCounter;
+                m_Stat.onExtractSuccess();
+            }
+            else
+                m_Stat.onExtractFailed();
+            return pNode;
+        }
+
+        template <typename Q, typename Less>
+        value_type * extract_with_( Q const& val, Less pred )
+        {
+            CDS_UNUSED( pred );
+            return extract_( val, typename wrapped_ordered_list::template make_compare_from_less<Less>());
+        }
+
+        template <typename Q, typename Compare>
+        bool erase_( const Q& val, Compare cmp )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            if ( m_List.erase_at( pHead, sv, cmp ) ) {
+                --m_ItemCounter;
+                m_Stat.onEraseSuccess();
+                return true;
+            }
+            m_Stat.onEraseFailed();
+            return false;
+        }
+
+        template <typename Q, typename Compare, typename Func>
+        bool erase_( Q const& val, Compare cmp, Func f )
+        {
+            size_t nHash = hash_value( val );
+            split_list::details::search_value_type<Q const>  sv( val, split_list::regular_hash( nHash ));
+            aux_node_type * pHead = get_bucket( nHash );
+            assert( pHead != nullptr );
+
+            if ( m_List.erase_at( pHead, sv, cmp, f )) {
+                --m_ItemCounter;
+                m_Stat.onEraseSuccess();
+                return true;
+            }
+            m_Stat.onEraseFailed();
+            return false;
+        }
+        //@endcond
+
+    protected:
+        //@cond
+        typedef typename cds::details::type_padding< bucket_table, traits::padding >::type padded_bucket_table;
+        padded_bucket_table     m_Buckets;          ///< bucket table
+
+        typedef typename cds::details::type_padding< ordered_list_wrapper, traits::padding>::type padded_ordered_list;
+        padded_ordered_list     m_List;             ///< Ordered list containing split-list items
+
+        atomics::atomic<size_t> m_nBucketCountLog2; ///< log2( current bucket count )
+        atomics::atomic<size_t> m_nMaxItemCount;    ///< number of items container can hold, before we have to resize
+        item_counter            m_ItemCounter;      ///< Item counter
+        hash                    m_HashFunctor;      ///< Hash functor
+        stat                    m_Stat;             ///< Internal statistics accumulator
+        //@endcond
     };
 
 }}  // namespace cds::intrusive