intrusive MultiLevelHashSet:
authorkhizmax <libcds.dev@gmail.com>
Sun, 9 Aug 2015 21:01:35 +0000 (00:01 +0300)
committerkhizmax <libcds.dev@gmail.com>
Sun, 9 Aug 2015 21:01:35 +0000 (00:01 +0300)
- added single-threaded interface sufficiency test
- fixed found bugs

cds/algo/split_bitstring.h
cds/gc/details/hp.h
cds/gc/impl/hp_decl.h
cds/intrusive/details/multilevel_hashset_base.h
cds/intrusive/impl/multilevel_hashset.h
projects/Win/vc12/cds.sln
tests/test-hdr/set/hdr_intrusive_multilevel_hashset.h
tests/test-hdr/set/hdr_intrusive_multilevel_hashset_hp.cpp
tests/unit/print_multilevel_hashset_stat.h [new file with mode: 0644]

index 19c183fdfd053d2ede65cdefd82db413af054e6d..432c40382a98d35b944cdfaacee0c974a50e06c3 100644 (file)
@@ -9,7 +9,7 @@ namespace cds { namespace algo {
 
     /// Cuts a bit sequence from fixed-size bit-string
     /**
-        The splitter an be used as iterator over bit-string.
+        The splitter can be used as iterator over bit-string.
         Each call of \p cut() or \p safe_cut() cuts the bit count specified
         and keeps the position inside bit-string for the next call.
 
@@ -31,7 +31,7 @@ namespace cds { namespace algo {
         //@endcond
 
     public:
-        /// Initializises the splitter with reference to \p h
+        /// Initializises the splitter with reference to \p h and zero start bit offset
         explicit split_bitstring( bitstring const& h )
             : m_ptr(reinterpret_cast<uint_type const*>( &h ))
             , m_pos(0)
@@ -41,6 +41,17 @@ namespace cds { namespace algo {
 #   endif
         {}
 
+        /// Initializises the splitter with reference to \p h and start bit offset \p nBitOffset
+        split_bitstring( bitstring const& h, size_t nBitOffset )
+            : m_ptr( reinterpret_cast<uint_type const*>( &h ) + nBitOffset / c_nBitPerInt )
+            , m_pos( nBitOffset % c_nBitPerInt )
+            , m_first( m_ptr )
+#   ifdef _DEBUG
+            , m_last( m_ptr + c_nHashSize )
+#   endif
+        {}
+
+
         /// Returns \p true if end-of-string is not reached yet
         explicit operator bool() const
         {
index 8170b8f1aa5ad8da481029db2bf29871a0bd269b..b0a3b5e1c6648645e95ce0e90e28dcc295a1173e 100644 (file)
@@ -635,7 +635,7 @@ namespace cds {
 
         /// Auto hp_guard.
         /**
-            This class encapsulates Hazard Pointer guard to protect a pointer against deletion .
+            This class encapsulates Hazard Pointer guard to protect a pointer against deletion.
             It allocates one HP from thread's HP array in constructor and free the hazard pointer allocated
             in destructor.
         */
index 4625cc06efe11dbfe5b2a5ef158ad76db268fcd7..f2e42084f41619ea806cd3f29c9913eeb82170e9 100644 (file)
@@ -435,15 +435,16 @@ namespace cds { namespace gc {
             /// Creates empty guarded pointer
             guarded_ptr() CDS_NOEXCEPT
                 : m_pGuard(nullptr)
-            {}
+            {
+                alloc_guard();
+            }
 
             //@cond
             /// Initializes guarded pointer with \p p
             explicit guarded_ptr( guarded_type * p ) CDS_NOEXCEPT
+                : m_pGuard( nullptr )
             {
-                alloc_guard();
-                assert( m_pGuard );
-                m_pGuard->set(p);
+                reset(p);
             }
             explicit guarded_ptr( std::nullptr_t ) CDS_NOEXCEPT
                 : m_pGuard( nullptr )
@@ -539,6 +540,13 @@ namespace cds { namespace gc {
                 assert( m_pGuard );
                 return *m_pGuard;
             }
+
+            void reset(guarded_type * p) CDS_NOEXCEPT
+            {
+                alloc_guard();
+                assert( m_pGuard );
+                m_pGuard->set(p);
+            }
             //@endcond
 
         private:
index 63d57bb4847c31fc8aeedb3f951a912dfcb8c767..c47ea81793c7d6efce163576ae7c152c4c88fbc2 100644 (file)
@@ -85,9 +85,11 @@ namespace cds { namespace intrusive {
             event_counter   m_nSlotChanged;     ///< Number of array node slot changing by other thread during an operation
             event_counter   m_nSlotConverting;  ///< Number of events when we encounter a slot while it is converting to array node
 
+            event_counter   m_nArrayNodeCount;  ///< Number of array nodes
+
             //@cond
             void onInsertSuccess()              { ++m_nInsertSuccess;       }
-            void onInserFailed()                { ++m_nInsertFailed;        }
+            void onInsertFailed()               { ++m_nInsertFailed;        }
             void onInsertRetry()                { ++m_nInsertRetry;         }
             void onUpdateNew()                  { ++m_nUpdateNew;           }
             void onUpdateExisting()             { ++m_nUpdateExisting;      }
@@ -103,6 +105,7 @@ namespace cds { namespace intrusive {
             void onExpandNodeFailed()           { ++m_nExpandNodeFailed;    }
             void onSlotChanged()                { ++m_nSlotChanged;         }
             void onSlotConverting()             { ++m_nSlotConverting;      }
+            void onArrayNodeCreated()           { ++m_nArrayNodeCount;      }
             //@endcond
         };
 
@@ -126,6 +129,7 @@ namespace cds { namespace intrusive {
             void onExpandNodeFailed()           const {}
             void onSlotChanged()                const {}
             void onSlotConverting()             const {}
+            void onArrayNodeCreated()           const {}
             //@endcond
         };
 
index 9f0db90a216ec23697efda925ee28c6106b79cb6..4924713d6755330966b8e9f779d07e7a69527bd8 100644 (file)
@@ -163,7 +163,7 @@ namespace cds { namespace intrusive {
     public:
         /// Creates empty set
         /**
-            @param head_bits: 2<sup>head_bits</sup> specifies the size of head array, minimum is 8.
+            @param head_bits: 2<sup>head_bits</sup> specifies the size of head array, minimum is 4.
             @param array_bits: 2<sup>array_bits</sup> specifies the size of array node, minimum is 2.
 
             Equation for \p head_bits and \p array_bits:
@@ -224,6 +224,7 @@ namespace cds { namespace intrusive {
             typename gc::Guard guard;
             back_off bkoff;
 
+            size_t nOffset = m_Metrics.head_node_size_log;
             atomic_node_ptr * pArr = m_Head;
             size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
             assert( nSlot < m_Metrics.head_node_size );
@@ -236,6 +237,7 @@ namespace cds { namespace intrusive {
                     nSlot = splitter.cut( m_Metrics.array_node_size_log );
                     assert( nSlot < m_Metrics.array_node_size );
                     pArr = to_array( slot.ptr() );
+                    nOffset += m_Metrics.array_node_size_log;
                 }
                 else if ( slot.bits() == array_converting ) {
                     // the slot is converting to array node right now
@@ -259,13 +261,7 @@ namespace cds { namespace intrusive {
                         }
 
                         // the slot must be expanded
-                        atomic_node_ptr * pNewArr;
-                        size_t nNewSlot;
-                        std::tie( pNewArr, nNewSlot ) = expand_slot( pArr[ nSlot ], slot, splitter );
-                        if ( pNewArr ) {
-                            pArr = pNewArr;
-                            nSlot = nNewSlot;
-                        }
+                        expand_slot( pArr[ nSlot ], slot, nOffset );
                     }
                     else {
                         // the slot is empty, try to insert data node
@@ -302,7 +298,6 @@ namespace cds { namespace intrusive {
             (i.e. the item has been inserted or updated),
             \p second is \p true if new item has been added or \p false if the set contains that hash.
         */
-        template <typename Func>
         std::pair<bool, bool> update( value_type& val, bool bInsert = true )
         {
             hash_type const& hash = hash_accessor()( val );
@@ -314,6 +309,7 @@ namespace cds { namespace intrusive {
             atomic_node_ptr * pArr = m_Head;
             size_t nSlot = splitter.cut( m_Metrics.head_node_size_log );
             assert( nSlot < m_Metrics.head_node_size );
+            size_t nOffset = m_Metrics.head_node_size_log;
 
             while ( true ) {
                 node_ptr slot = pArr[nSlot].load( memory_model::memory_order_acquire );
@@ -323,6 +319,7 @@ namespace cds { namespace intrusive {
                     nSlot = splitter.cut( m_Metrics.array_node_size_log );
                     assert( nSlot < m_Metrics.array_node_size );
                     pArr = to_array( slot.ptr() );
+                    nOffset += m_Metrics.array_node_size_log;
                 }
                 else if ( slot.bits() == array_converting ) {
                     // the slot is converting to array node right now
@@ -342,7 +339,12 @@ namespace cds { namespace intrusive {
                         if ( cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
                             // the item with that hash value already exists
                             // Replace it with val
-                            if ( pArr[nSlot].compare_exchange_strong( slot, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ) ) {
+                            if ( slot.ptr() == &val ) {
+                                m_Stat.onUpdateExisting();
+                                return std::make_pair( true, false );
+                            }
+
+                            if ( pArr[nSlot].compare_exchange_strong( slot, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed )) {
                                 // slot can be disposed
                                 gc::template retire<disposer>( slot.ptr() );
                                 m_Stat.onUpdateExisting();
@@ -354,13 +356,7 @@ namespace cds { namespace intrusive {
                         }
 
                         // the slot must be expanded
-                        atomic_node_ptr * pNewArr;
-                        size_t nNewSlot;
-                        std::tie( pNewArr, nNewSlot ) = expand_slot( pArr[ nSlot ], slot, splitter );
-                        if ( pNewArr ) {
-                            pArr = pNewArr;
-                            nSlot = nNewSlot;
-                        }
+                        expand_slot( pArr[ nSlot ], slot, nOffset );
                     }
                     else {
                         // the slot is empty, try to insert data node
@@ -369,7 +365,6 @@ namespace cds { namespace intrusive {
                             if ( pArr[nSlot].compare_exchange_strong( pNull, node_ptr( &val ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
                             {
                                 // the new data node has been inserted
-                                f( val );
                                 ++m_ItemCounter;
                                 m_Stat.onUpdateNew();
                                 return std::make_pair( true, true );
@@ -395,7 +390,7 @@ namespace cds { namespace intrusive {
 
             The function returns \p true if success and \p false otherwise.
         */
-        bool unlink( value_type& val )
+        bool unlink( value_type const& val )
         {
             typename gc::Guard guard;
             auto pred = [&val](value_type const& item) -> bool { return &item == &val; };
@@ -485,17 +480,20 @@ namespace cds { namespace intrusive {
         */
         guarded_ptr extract( hash_type const& hash )
         {
-            typename gc::Guard guard;
-            value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
-
-            // p is guarded by HP
-            if ( p ) {
-                gc::template retire<disposer>( p );
-                --m_ItemCounter;
-                m_Stat.onEraseSuccess();
-                return guarded_ptr(p);
+            guarded_ptr gp;
+            {
+                typename gc::Guard guard;
+                value_type * p = do_erase( hash, guard, []( value_type const&) -> bool {return true; } );
+
+                // p is guarded by HP
+                if ( p ) {
+                    gc::template retire<disposer>( p );
+                    --m_ItemCounter;
+                    m_Stat.onEraseSuccess();
+                    gp.reset( p );
+                }
             }
-            return guarded_ptr();
+            return gp;
         }
 
         /// Finds an item by it's \p hash
@@ -565,13 +563,12 @@ namespace cds { namespace intrusive {
         */
         guarded_ptr get( hash_type const& hash )
         {
-            typename gc::Guard guard;
-            value_type * p = search( hash, guard );
-
-            // p is guarded by HP
-            if ( p )
-                return guarded_ptr( p );
-            return guarded_ptr();
+            guarded_ptr gp;
+            {
+                typename gc::Guard guard;
+                gp.reset( search( hash, guard ));
+            }
+            return gp;
         }
 
         /// Clears the set (non-atomic)
@@ -629,8 +626,8 @@ namespace cds { namespace intrusive {
 
             if ( array_bits < 2 )
                 array_bits = 2;
-            if ( head_bits < 8 )
-                head_bits = 8;
+            if ( head_bits < 4 )
+                head_bits = 4;
             if ( head_bits > hash_bits )
                 head_bits = hash_bits;
             if ( (hash_bits - head_bits) % array_bits != 0 )
@@ -713,9 +710,11 @@ namespace cds { namespace intrusive {
                     else {
                         // data node
                         if ( pArr->compare_exchange_strong( slot, node_ptr(), memory_model::memory_order_acquire, atomics::memory_order_relaxed )) {
-                            gc::template retire<disposer>( slot.ptr() );
-                            --m_ItemCounter;
-                            m_Stat.onEraseSuccess();
+                            if ( slot.ptr() ) {
+                                gc::template retire<disposer>( slot.ptr() );
+                                --m_ItemCounter;
+                                m_Stat.onEraseSuccess();
+                            }
                             break;
                         }
                     }
@@ -740,21 +739,26 @@ namespace cds { namespace intrusive {
         {
             return converter( p ).pArr;
         }
-        static node_ptr * to_node( atomic_node_ptr * p )
+        static value_type * to_node( atomic_node_ptr * p )
         {
             return converter( p ).pData;
         }
 
-        std::pair< atomic_node_ptr *, size_t > expand_slot( atomic_node_ptr& slot, node_ptr current, hash_splitter& splitter )
+        bool expand_slot( atomic_node_ptr& slot, node_ptr current, size_t nOffset )
         {
+            assert( current.bits() == 0 );
+            assert( current.ptr() );
+
+            size_t idx = hash_splitter(hash_accessor()(*current.ptr()), nOffset).cut( m_Metrics.array_node_size_log );
+            atomic_node_ptr * pArr = alloc_array_node();
+
             node_ptr cur(current.ptr());
             if ( !slot.compare_exchange_strong( cur, cur | array_converting, memory_model::memory_order_release, atomics::memory_order_relaxed )) {
                 m_Stat.onExpandNodeFailed();
-                return std::make_pair(static_cast<atomic_node_ptr *>(nullptr), size_t(0));
+                free_array_node( pArr );
+                return false;
             }
 
-            atomic_node_ptr * pArr = alloc_array_node();
-            size_t idx = splitter.cut( m_Metrics.array_node_size_log );
             pArr[idx].store( current, memory_model::memory_order_release );
 
             cur = cur | array_converting;
@@ -762,7 +766,8 @@ namespace cds { namespace intrusive {
                 slot.compare_exchange_strong( cur, node_ptr( to_node( pArr ), array_node ), memory_model::memory_order_release, atomics::memory_order_relaxed )
             );
 
-            return std::make_pair( pArr, idx );
+            m_Stat.onArrayNodeCreated();
+            return true;
         }
 
         value_type * search( hash_type const& hash, typename gc::Guard& guard )
@@ -800,7 +805,7 @@ namespace cds { namespace intrusive {
                     }
                     else if ( slot.ptr() && cmp( hash, hash_accessor()( *slot.ptr() )) == 0 ) {
                         // item found
-                        m_Stat.onFindSucces();
+                        m_Stat.onFindSuccess();
                         return slot.ptr();
                     }
                     m_Stat.onFindFailed();
index 3f7bde00165d05a1a5c52f6fe4b56b4e0b84a0b2..c63ed1af438eab07bdb747b15f250a8de5da3469 100644 (file)
@@ -1,6 +1,6 @@
 Microsoft Visual Studio Solution File, Format Version 12.00\r
 # Visual Studio Express 2013 for Windows Desktop\r
-VisualStudioVersion = 12.0.31101.0\r
+VisualStudioVersion = 12.0.40629.0\r
 MinimumVisualStudioVersion = 10.0.40219.1\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cds", "cds.vcxproj", "{408FE9BC-44F0-4E6A-89FA-D6F952584239}"\r
 EndProject\r
@@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit-test", "unit-test", "{
                ..\..\..\tests\unit\print_cuckoo_stat.h = ..\..\..\tests\unit\print_cuckoo_stat.h\r
                ..\..\..\tests\unit\print_ellenbintree_stat.h = ..\..\..\tests\unit\print_ellenbintree_stat.h\r
                ..\..\..\tests\unit\print_mspriorityqueue_stat.h = ..\..\..\tests\unit\print_mspriorityqueue_stat.h\r
+               ..\..\..\tests\unit\print_multilevel_hashset_stat.h = ..\..\..\tests\unit\print_multilevel_hashset_stat.h\r
                ..\..\..\tests\unit\print_segmentedqueue_stat.h = ..\..\..\tests\unit\print_segmentedqueue_stat.h\r
                ..\..\..\tests\unit\print_skip_list_stat.h = ..\..\..\tests\unit\print_skip_list_stat.h\r
                ..\..\..\tests\unit\print_split_list_stat.h = ..\..\..\tests\unit\print_split_list_stat.h\r
index 8eb17e3736940aca01e24333ff654de5e7fc96b4..d882c6a2b9c6735a175c053fbbc270e24be1d55f 100644 (file)
@@ -22,6 +22,16 @@ namespace set {
         {
             unsigned int nDisposeCount  ;   // count of disposer calling
             Hash hash;
+            unsigned int nInsertCall;
+            unsigned int nFindCall;
+            unsigned int nEraseCall;
+
+            Item()
+                : nDisposeCount(0)
+                , nInsertCall(0)
+                , nFindCall(0)
+                , nEraseCall(0)
+            {}
         };
 
         template <typename Hash>
@@ -33,16 +43,194 @@ namespace set {
             }
         };
 
+        struct item_disposer {
+            template <typename Hash>
+            void operator()( Item<Hash> * p )
+            {
+                ++p->nDisposeCount;
+            }
+        };
+
         template <typename Set>
         void test_hp()
         {
-            Set s;
+            typedef typename Set::hash_type hash_type;
+            typedef typename Set::value_type value_type;
+
+            std::hash<hash_type> hasher;
+
+            size_t const arrCapacity = 1000;
+            std::vector< value_type > arrValue;
+            arrValue.reserve( arrCapacity );
+            for ( size_t i = 0; i < arrCapacity; ++i ) {
+                arrValue.emplace_back( value_type() );
+                arrValue.back().hash = hasher( i );
+            }
+            CPPUNIT_ASSERT( arrValue.size() == arrCapacity );
+
+            Set s( 4, 2 );
+            CPPUNIT_ASSERT(s.head_size() == 16 );
+            CPPUNIT_ASSERT(s.array_node_size() == 4 );
+
+            // insert() test
+            CPPUNIT_ASSERT(s.size() == 0 );
+            CPPUNIT_ASSERT(s.empty() );
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT( s.insert( el ));
+                CPPUNIT_ASSERT(s.contains( el.hash ));
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT(s.contains( el.hash ));
+                CPPUNIT_ASSERT( !s.insert( el ) );
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+            CPPUNIT_ASSERT( !s.empty() );
+
+            // update() exists test
+            for ( auto& el : arrValue ) {
+                bool bOp, bInsert;
+                std::tie(bOp, bInsert) = s.update( el, false );
+                CPPUNIT_ASSERT( bOp );
+                CPPUNIT_ASSERT( !bInsert );
+                CPPUNIT_ASSERT( el.nFindCall == 0 );
+                CPPUNIT_ASSERT(s.find(el.hash, [](value_type& v) { v.nFindCall++; } ));
+                CPPUNIT_ASSERT( el.nFindCall == 1 );
+            }
+
+            // unlink test
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+            for ( auto const& el : arrValue ) {
+                CPPUNIT_ASSERT(s.unlink( el ));
+                CPPUNIT_ASSERT(!s.contains( el.hash ));
+            }
+            CPPUNIT_ASSERT(s.size() == 0 );
+            Set::gc::force_dispose();
+            for ( auto const& el : arrValue ) {
+                CPPUNIT_ASSERT( el.nDisposeCount == 1 );
+            }
+
+            // new hash values
+            for ( auto& el : arrValue )
+                el.hash = hasher( el.hash );
+
+            // insert( func )
+            CPPUNIT_ASSERT(s.size() == 0 );
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT( s.insert( el, []( value_type& v ) { ++v.nInsertCall; } ));
+                CPPUNIT_ASSERT(s.contains( el.hash ));
+                CPPUNIT_ASSERT( el.nInsertCall == 1 );
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT(s.contains( el.hash ));
+                CPPUNIT_ASSERT( !s.insert( el ) );
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+            CPPUNIT_ASSERT( !s.empty() );
+
+            for ( auto& el : arrValue )
+                el.nDisposeCount = 0;
+
+            s.clear();
+            CPPUNIT_ASSERT(s.size() == 0 );
+            Set::gc::force_dispose();
+            for ( auto const& el : arrValue ) {
+                CPPUNIT_ASSERT( el.nDisposeCount == 1 );
+            }
+
+            // new hash values
+            for ( auto& el : arrValue )
+                el.hash = hasher( el.hash );
+
+            // update test
+            for ( auto& el : arrValue ) {
+                bool bOp, bInsert;
+                std::tie(bOp, bInsert) = s.update( el, false );
+                CPPUNIT_ASSERT( !bOp );
+                CPPUNIT_ASSERT( !bInsert );
+                CPPUNIT_ASSERT( !s.contains( el.hash ));
+
+                std::tie(bOp, bInsert) = s.update( el, true );
+                CPPUNIT_ASSERT( bOp );
+                CPPUNIT_ASSERT( bInsert );
+                CPPUNIT_ASSERT( s.contains( el.hash ));
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+
+            // erase test
+            for ( auto& el : arrValue ) {
+                el.nDisposeCount = 0;
+                CPPUNIT_ASSERT( s.contains( el.hash ));
+                CPPUNIT_ASSERT(s.erase( el.hash ));
+                CPPUNIT_ASSERT( !s.contains( el.hash ));
+                CPPUNIT_ASSERT( !s.erase( el.hash ));
+            }
+            CPPUNIT_ASSERT(s.size() == 0 );
+            Set::gc::force_dispose();
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT( el.nDisposeCount == 1 );
+                CPPUNIT_ASSERT(s.insert( el ));
+            }
+
+            // erase with functor, get() test
+            for ( auto& el : arrValue ) {
+                el.nDisposeCount = 0;
+                CPPUNIT_ASSERT( s.contains( el.hash ) );
+                {
+                    typename Set::guarded_ptr gp{ s.get( el.hash ) };
+                    CPPUNIT_ASSERT( gp );
+                    CPPUNIT_ASSERT( gp->nEraseCall == 0);
+                    CPPUNIT_ASSERT(s.erase( gp->hash, []( value_type& i ) { ++i.nEraseCall; } ));
+                    CPPUNIT_ASSERT( gp->nEraseCall == 1);
+                    Set::gc::force_dispose();
+                    CPPUNIT_ASSERT( gp->nDisposeCount == 0 );
+                }
+                CPPUNIT_ASSERT( !s.contains( el.hash ));
+                CPPUNIT_ASSERT( !s.erase( el.hash ));
+                CPPUNIT_ASSERT( el.nEraseCall == 1 );
+                Set::gc::force_dispose();
+                CPPUNIT_ASSERT( el.nDisposeCount == 1 );
+            }
+            CPPUNIT_ASSERT(s.size() == 0 );
+
+            // new hash values
+            for ( auto& el : arrValue ) {
+                el.hash = hasher( el.hash );
+                el.nDisposeCount = 0;
+                bool bOp, bInsert;
+                std::tie(bOp, bInsert) = s.update( el );
+                CPPUNIT_ASSERT( bOp );
+                CPPUNIT_ASSERT( bInsert );
+            }
+            CPPUNIT_ASSERT(s.size() == arrCapacity );
+
+            // extract test
+            for ( auto& el : arrValue ) {
+                CPPUNIT_ASSERT( s.contains( el.hash ) );
+                typename Set::guarded_ptr gp = s.extract( el.hash );
+                CPPUNIT_ASSERT( gp );
+                Set::gc::force_dispose();
+                CPPUNIT_ASSERT( el.nDisposeCount == 0 );
+                CPPUNIT_ASSERT( gp->nDisposeCount == 0 );
+                gp = s.get( el.hash );
+                CPPUNIT_ASSERT( !gp );
+                Set::gc::force_dispose();
+                CPPUNIT_ASSERT( el.nDisposeCount == 1 );
+                CPPUNIT_ASSERT( !s.contains( el.hash ) );
+            }
+            CPPUNIT_ASSERT(s.size() == 0 );
+            CPPUNIT_ASSERT(s.empty() );
+
+            CPPUNIT_MSG( s.statistics() );
         }
 
         void hp_stdhash();
+        void hp_stdhash_stat();
 
         CPPUNIT_TEST_SUITE(IntrusiveMultiLevelHashSetHdrTest)
             CPPUNIT_TEST(hp_stdhash)
+            CPPUNIT_TEST(hp_stdhash_stat)
         CPPUNIT_TEST_SUITE_END()
     };
 } // namespace set
index fb80ee161e97173c0fedfe9ed1159f683976063b..83f2fd3ab3b37b1bec4be02112daa2cc06009c59 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "set/hdr_intrusive_multilevel_hashset.h"
 #include <cds/intrusive/multilevel_hashset_hp.h>
+#include "unit/print_multilevel_hashset_stat.h"
 
 namespace set {
     namespace {
@@ -15,6 +16,7 @@ namespace set {
         struct traits: public ci::multilevel_hashset::traits
         {
             typedef get_hash<hash_type> hash_accessor;
+            typedef item_disposer disposer;
         };
         typedef ci::MultiLevelHashSet< gc_type, Item<hash_type>, traits > set_type;
         test_hp<set_type>();
@@ -24,10 +26,37 @@ namespace set {
             Item<hash_type>, 
             typename ci::multilevel_hashset::make_traits<
                 ci::multilevel_hashset::hash_accessor< get_hash<hash_type>>
+                , ci::opt::disposer< item_disposer >
             >::type
         > set_type2;
         test_hp<set_type2>();
     }
+
+    void IntrusiveMultiLevelHashSetHdrTest::hp_stdhash_stat()
+    {
+        typedef size_t hash_type;
+
+        struct traits: public ci::multilevel_hashset::traits
+        {
+            typedef get_hash<hash_type> hash_accessor;
+            typedef item_disposer disposer;
+            typedef ci::multilevel_hashset::stat<> stat;
+        };
+        typedef ci::MultiLevelHashSet< gc_type, Item<hash_type>, traits > set_type;
+        test_hp<set_type>();
+
+        typedef ci::MultiLevelHashSet< 
+            gc_type, 
+            Item<hash_type>, 
+            typename ci::multilevel_hashset::make_traits<
+                ci::multilevel_hashset::hash_accessor< get_hash<hash_type>>
+                , ci::opt::disposer< item_disposer >
+                ,co::stat< ci::multilevel_hashset::stat<>>
+            >::type
+        > set_type2;
+        test_hp<set_type2>();
+    }
+
 } // namespace set
 
 CPPUNIT_TEST_SUITE_REGISTRATION(set::IntrusiveMultiLevelHashSetHdrTest);
diff --git a/tests/unit/print_multilevel_hashset_stat.h b/tests/unit/print_multilevel_hashset_stat.h
new file mode 100644 (file)
index 0000000..2183328
--- /dev/null
@@ -0,0 +1,41 @@
+//$$CDS-header$$
+
+#ifndef CDSUNIT_PRINT_MULTILEVEL_HASHSET_STAT_H
+#define CDSUNIT_PRINT_MULTILEVEL_HASHSET_STAT_H
+
+#include <cds/intrusive/details/multilevel_hashset_base.h>
+#include <ostream>
+
+namespace std {
+
+    static inline ostream& operator <<( ostream& o, cds::intrusive::multilevel_hashset::stat<> const& s )
+    {
+        return
+        o << "Stat [cds::intrusive::multilevel_hashset::stat]\n"
+            << "\t\t          m_nInsertSuccess: " << s.m_nInsertSuccess.get()           << "\n"
+            << "\t\t           m_nInsertFailed: " << s.m_nInsertFailed.get()            << "\n"
+            << "\t\t            m_nInsertRetry: " << s.m_nInsertRetry.get()             << "\n"
+            << "\t\t              m_nUpdateNew: " << s.m_nUpdateNew.get()               << "\n"
+            << "\t\t         m_nUpdateExisting: " << s.m_nUpdateExisting.get()          << "\n"
+            << "\t\t           m_nUpdateFailed: " << s.m_nUpdateFailed.get()            << "\n"
+            << "\t\t            m_nUpdateRetry: " << s.m_nUpdateRetry.get()             << "\n"
+            << "\t\t           m_nEraseSuccess: " << s.m_nEraseSuccess.get()            << "\n"
+            << "\t\t            m_nEraseFailed: " << s.m_nEraseFailed.get()             << "\n"
+            << "\t\t             m_nEraseRetry: " << s.m_nEraseRetry.get()              << "\n"
+            << "\t\t            m_nFindSuccess: " << s.m_nFindSuccess.get()             << "\n"
+            << "\t\t             m_nFindFailed: " << s.m_nFindFailed.get()              << "\n"
+            << "\t\t      m_nExpandNodeSuccess: " << s.m_nExpandNodeSuccess.get()       << "\n"
+            << "\t\t       m_nExpandNodeFailed: " << s.m_nExpandNodeFailed.get()        << "\n"
+            << "\t\t            m_nSlotChanged: " << s.m_nSlotChanged.get()             << "\n"
+            << "\t\t         m_nSlotConverting: " << s.m_nSlotConverting.get()          << "\n"
+            << "\t\t         m_nArrayNodeCount: " << s.m_nArrayNodeCount.get()          << "\n";
+    }
+
+    static inline ostream& operator <<( ostream& o, cds::intrusive::multilevel_hashset::empty_stat const& /*s*/ )
+    {
+        return o;
+    }
+
+} // namespace std
+
+#endif // #ifndef CDSUNIT_PRINT_MULTILEVEL_HASHSET_STAT_H