From cdadea07f8f8dcd822fd28bc28a0a70336e28eae Mon Sep 17 00:00:00 2001 From: Max Kanushin Date: Sat, 27 Jan 2018 18:08:49 +0300 Subject: [PATCH 1/2] lazy skip list set tests --- test/unit/set/CMakeLists.txt | 10 +++++ test/unit/set/lazyskiplist_dhp.cpp | 64 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 test/unit/set/lazyskiplist_dhp.cpp diff --git a/test/unit/set/CMakeLists.txt b/test/unit/set/CMakeLists.txt index 0487d6617..23e10cdd1 100644 --- a/test/unit/set/CMakeLists.txt +++ b/test/unit/set/CMakeLists.txt @@ -77,6 +77,16 @@ add_executable(${UNIT_SET_SKIP} ${UNIT_SET_SKIP_SOURCES}) target_link_libraries(${UNIT_SET_SKIP} ${CDS_TEST_LIBRARIES}) add_test(NAME ${UNIT_SET_SKIP} COMMAND ${UNIT_SET_SKIP} WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) +# LazySkipListSet +set(UNIT_SET_LAZY_SKIP unit-set-lazy-skip) +set(UNIT_SET_LAZY_SKIP_SOURCES + ../main.cpp + lazyskiplist_dhp.cpp +) +add_executable(${UNIT_SET_LAZY_SKIP} ${UNIT_SET_LAZY_SKIP_SOURCES}) +target_link_libraries(${UNIT_SET_LAZY_SKIP} ${CDS_TEST_LIBRARIES}) +add_test(NAME ${UNIT_SET_LAZY_SKIP} COMMAND ${UNIT_SET_LAZY_SKIP} WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + # SplitListSet set(UNIT_SET_SPLIT_MICHAEL unit-set-split-michael) set(UNIT_SET_SPLIT_MICHAEL_SOURCES diff --git a/test/unit/set/lazyskiplist_dhp.cpp b/test/unit/set/lazyskiplist_dhp.cpp new file mode 100644 index 000000000..2ef6ffb2a --- /dev/null +++ b/test/unit/set/lazyskiplist_dhp.cpp @@ -0,0 +1,64 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017 + + 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: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + 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. +*/ + +#include "test_ordered_set_hp.h" + +#include +#include + +namespace { + namespace cc = cds::container; + typedef cds::gc::DHP gc_type; + + class LazySkipListSet_DHP : public cds_test::container_ordered_set_hp + { + protected: + typedef cds_test::container_ordered_set_hp base_class; + + void SetUp() + { + typedef cc::LazySkipListSet< gc_type, int_item > set_type; + + cds::gc::DHP dhpGC; + // cds::gc::dhp::smr::construct( set_type::c_nHazardPtrCount ); + cds::threading::Manager::attachThread(); + } + + void TearDown() + { + cds::threading::Manager::detachThread(); + cds::gc::dhp::smr::destruct(); + } + }; + +# define CDSTEST_FIXTURE_NAME LazySkipListSet_DHP +# include "skiplist_hp_inl.h" + +} // namespace From f3ca5021f21242e69fdbb9a6b49498f9e3cc4851 Mon Sep 17 00:00:00 2001 From: Max Kanushin Date: Sun, 28 Jan 2018 12:46:47 +0300 Subject: [PATCH 2/2] unit test stub --- .../details/lazy_skip_list_set_base.h | 192 +++++++++++++++++- cds/container/lazy_skip_list_set_dhp.h | 43 +++- test/unit/set/CMakeLists.txt | 2 +- test/unit/set/lazy_skiplist.cpp | 151 ++++++++++++++ 4 files changed, 369 insertions(+), 19 deletions(-) create mode 100644 test/unit/set/lazy_skiplist.cpp diff --git a/cds/container/details/lazy_skip_list_set_base.h b/cds/container/details/lazy_skip_list_set_base.h index bcb0be225..4c75a1169 100644 --- a/cds/container/details/lazy_skip_list_set_base.h +++ b/cds/container/details/lazy_skip_list_set_base.h @@ -1,16 +1,17 @@ #ifndef CDSLIB_LAZY_SKIP_LIST_BASE_H #define CDSLIB_LAZY_SKIP_LIST_BASE_H -#include +#include +#include +#include +#include namespace cds { namespace container { - namespace lazy_skip_list_set { + namespace skip_list { static size_t const c_nMaxHeight = 32; - typedef cds::intrusive::skip_list::traits traits; - template < typename GC, typename T, @@ -19,7 +20,7 @@ namespace cds { namespace container { class node { public: - typedef GC gc; + typedef cds::gc::DHP gc; typedef T value_type; typedef Lock lock_type; @@ -119,7 +120,7 @@ namespace cds { namespace container { node_allocator alloc; node_ptr new_node = alloc.New(); new_node->key = key; - new_node->m_nHeight = cds::container::lazy_skip_list_set::c_nMaxHeight; + new_node->m_nHeight = cds::container::skip_list::c_nMaxHeight; new_node->allocate_tower(new_node->m_nHeight); new_node->fullyLinked = false; @@ -153,7 +154,184 @@ namespace cds { namespace container { } }; - } // namespace lazy_skip_list_set + + /// Turbo-pascal random level generator + /** + This uses a cheap pseudo-random function that was used in Turbo Pascal. + + The random generator should return numbers from range [0..31]. + + From Doug Lea's ConcurrentSkipListMap.java. + */ + template + class turbo + { + //@cond + atomics::atomic m_nSeed; + + static_assert( MaxHeight > 1, "MaxHeight" ); + static_assert( MaxHeight <= c_nMaxHeight, "MaxHeight is too large" ); + static unsigned int const c_nBitMask = (1u << ( MaxHeight - 1 )) - 1; + //@endcond + public: + /// The upper bound of generator's return value. The generator produces random number in range [0..c_nUpperBound) + static unsigned int const c_nUpperBound = MaxHeight; + + /// Initializes the generator instance + turbo() + { + m_nSeed.store( (unsigned int) cds::OS::Timer::random_seed(), atomics::memory_order_relaxed ); + } + + /// Main generator function + unsigned int operator()() + { + /* + private int randomLevel() { + int level = 0; + int r = randomSeed; + randomSeed = r * 134775813 + 1; + if (r < 0) { + while ((r <<= 1) > 0) + ++level; + } + return level; + } + */ + /* + The low bits are apparently not very random (the original used only + upper 16 bits) so we traverse from highest bit down (i.e., test + sign), thus hardly ever use lower bits. + */ + unsigned int x = m_nSeed.load( atomics::memory_order_relaxed ) * 134775813 + 1; + m_nSeed.store( x, atomics::memory_order_relaxed ); + unsigned int nLevel = ( x & 0x80000000 ) ? ( c_nUpperBound - 1 - cds::bitop::MSBnz( (x & c_nBitMask ) | 1 )) : 0; + + assert( nLevel < c_nUpperBound ); + return nLevel; + } + }; + /// Turbo-Pascal random level generator, max height 32 + typedef turbo turbo32; + + template + struct stat { + typedef EventCounter event_counter ; ///< Event counter type + + event_counter m_nNodeHeightAdd[c_nMaxHeight] ; ///< Count of added node of each height + event_counter m_nNodeHeightDel[c_nMaxHeight] ; ///< Count of deleted node of each height + event_counter m_nInsertSuccess ; ///< Count of success insertion + event_counter m_nInsertFailed ; ///< Count of failed insertion + event_counter m_nInsertRetries ; ///< Count of unsuccessful retries of insertion + event_counter m_nUpdateExist ; ///< Count of \p update() call for existed node + event_counter m_nUpdateNew ; ///< Count of \p update() call for new node + event_counter m_nUnlinkSuccess ; ///< Count of successful call of \p unlink + event_counter m_nUnlinkFailed ; ///< Count of failed call of \p unlink + event_counter m_nEraseSuccess ; ///< Count of successful call of \p erase + event_counter m_nEraseFailed ; ///< Count of failed call of \p erase + event_counter m_nEraseRetry ; ///< Count of retries while erasing node + event_counter m_nFindFastSuccess ; ///< Count of successful call of \p find and all derivatives (via fast-path) + event_counter m_nFindFastFailed ; ///< Count of failed call of \p find and all derivatives (via fast-path) + event_counter m_nFindSlowSuccess ; ///< Count of successful call of \p find and all derivatives (via slow-path) + event_counter m_nFindSlowFailed ; ///< Count of failed call of \p find and all derivatives (via slow-path) + event_counter m_nRenewInsertPosition ; ///< Count of renewing position events while inserting + event_counter m_nLogicDeleteWhileInsert; ///< Count of events "The node has been logically deleted while inserting" + event_counter m_nRemoveWhileInsert ; ///< Count of evnts "The node is removing while inserting" + event_counter m_nFastErase ; ///< Fast erase event counter + event_counter m_nFastExtract ; ///< Fast extract event counter + event_counter m_nSlowErase ; ///< Slow erase event counter + event_counter m_nSlowExtract ; ///< Slow extract event counter + event_counter m_nExtractSuccess ; ///< Count of successful call of \p extract + event_counter m_nExtractFailed ; ///< Count of failed call of \p extract + event_counter m_nExtractRetries ; ///< Count of retries of \p extract call + event_counter m_nExtractMinSuccess ; ///< Count of successful call of \p extract_min + event_counter m_nExtractMinFailed ; ///< Count of failed call of \p extract_min + event_counter m_nExtractMinRetries ; ///< Count of retries of \p extract_min call + event_counter m_nExtractMaxSuccess ; ///< Count of successful call of \p extract_max + event_counter m_nExtractMaxFailed ; ///< Count of failed call of \p extract_max + event_counter m_nExtractMaxRetries ; ///< Count of retries of \p extract_max call + event_counter m_nEraseWhileFind ; ///< Count of erased item while searching + event_counter m_nExtractWhileFind ; ///< Count of extracted item while searching (RCU only) + event_counter m_nMarkFailed ; ///< Count of failed node marking (logical deletion mark) + event_counter m_nEraseContention ; ///< Count of key erasing contention encountered + + //@cond + void onAddNode( unsigned int nHeight ) + { + assert( nHeight > 0 && nHeight <= sizeof(m_nNodeHeightAdd) / sizeof(m_nNodeHeightAdd[0])); + ++m_nNodeHeightAdd[nHeight - 1]; + } + void onRemoveNode( unsigned int nHeight ) + { + assert( nHeight > 0 && nHeight <= sizeof(m_nNodeHeightDel) / sizeof(m_nNodeHeightDel[0])); + ++m_nNodeHeightDel[nHeight - 1]; + } + + void onInsertSuccess() { ++m_nInsertSuccess ; } + void onInsertFailed() { ++m_nInsertFailed ; } + void onInsertRetry() { ++m_nInsertRetries ; } + void onUpdateExist() { ++m_nUpdateExist ; } + void onUpdateNew() { ++m_nUpdateNew ; } + void onUnlinkSuccess() { ++m_nUnlinkSuccess ; } + void onUnlinkFailed() { ++m_nUnlinkFailed ; } + void onEraseSuccess() { ++m_nEraseSuccess ; } + void onEraseFailed() { ++m_nEraseFailed ; } + void onEraseRetry() { ++m_nEraseRetry; } + void onFindFastSuccess() { ++m_nFindFastSuccess ; } + void onFindFastFailed() { ++m_nFindFastFailed ; } + void onFindSlowSuccess() { ++m_nFindSlowSuccess ; } + void onFindSlowFailed() { ++m_nFindSlowFailed ; } + void onEraseWhileFind() { ++m_nEraseWhileFind ; } + void onExtractWhileFind() { ++m_nExtractWhileFind ; } + void onRenewInsertPosition() { ++m_nRenewInsertPosition; } + void onLogicDeleteWhileInsert() { ++m_nLogicDeleteWhileInsert; } + void onRemoveWhileInsert() { ++m_nRemoveWhileInsert; } + void onFastErase() { ++m_nFastErase; } + void onFastExtract() { ++m_nFastExtract; } + void onSlowErase() { ++m_nSlowErase; } + void onSlowExtract() { ++m_nSlowExtract; } + void onExtractSuccess() { ++m_nExtractSuccess; } + void onExtractFailed() { ++m_nExtractFailed; } + void onExtractRetry() { ++m_nExtractRetries; } + void onExtractMinSuccess() { ++m_nExtractMinSuccess; } + void onExtractMinFailed() { ++m_nExtractMinFailed; } + void onExtractMinRetry() { ++m_nExtractMinRetries; } + void onExtractMaxSuccess() { ++m_nExtractMaxSuccess; } + void onExtractMaxFailed() { ++m_nExtractMaxFailed; } + void onExtractMaxRetry() { ++m_nExtractMaxRetries; } + void onMarkFailed() { ++m_nMarkFailed; } + void onEraseContention() { ++m_nEraseContention; } + //@endcond + }; + + // typedef cds::intrusive::skip_list::traits traits; + struct traits { + typedef opt::v::relaxed_ordering memory_model; + typedef turbo32 random_level_generator; + }; + + template + struct make_traits { +# ifdef CDS_DOXYGEN_INVOKED + typedef implementation_defined type ; ///< Metafunction result +# else + typedef typename cds::opt::make_options< + typename cds::opt::find_type_traits< traits, Options... >::type + ,Options... + >::type type; +# endif + }; + + namespace details { + template + class iterator { + typedef node node_type; + + node_type * m_pNode; + }; + } + + } // namespace skip_list }} diff --git a/cds/container/lazy_skip_list_set_dhp.h b/cds/container/lazy_skip_list_set_dhp.h index 27ebdb67f..7c3c1e6dc 100644 --- a/cds/container/lazy_skip_list_set_dhp.h +++ b/cds/container/lazy_skip_list_set_dhp.h @@ -10,23 +10,24 @@ namespace cds { namespace container { template < typename GC, typename T, - typename Traits = lazy_skip_list_set::traits + typename Traits = skip_list::traits > - class LazySkipListSet + class SkipListSet { public: - typedef GC gc; + typedef cds::gc::DHP gc; typedef T value_type; typedef Traits traits; - static size_t const c_nMaxHeight = cds::container::lazy_skip_list_set::c_nMaxHeight; - // static size_t const c_nHazardPtrCount = c_nMaxHeight * 2 + 3; + static size_t const c_nMaxHeight = cds::container::skip_list::c_nMaxHeight; + static size_t const c_nHazardPtrCount = 0; typedef typename traits::random_level_generator rand_height; protected: - typedef cds::container::lazy_skip_list_set::node node_type; + typedef cds::container::skip_list::node node_type; typedef typename node_type::key_type key_type; + typedef skip_list::details::iterator iterator; typedef cds::details::marked_ptr marked_ptr; @@ -34,7 +35,7 @@ namespace cds { namespace container { node_type *m_Tail; public: - LazySkipListSet() { + SkipListSet() { m_Head = node_type::min_key(); m_Tail = node_type::max_key(); @@ -42,10 +43,23 @@ namespace cds { namespace container { m_Head->next(layer).store(marked_ptr(m_Tail), traits::memory_model::memory_order_relaxed); } - ~LazySkipListSet() { + ~SkipListSet() { destroy(); } + iterator begin() { + return iterator(m_Head); + } + + iterator end() { + key_type key = m_Tail->node_key(); + node_type *preds[c_nMaxHeight]; + node_type *succs[c_nMaxHeight]; + int lFound = find(key, preds, succs); + + return iterator(preds[0]); + } + bool insert(value_type v) { key_type key = std::hash{}(v); unsigned int topLayer = randomLevel(); @@ -168,19 +182,22 @@ namespace cds { namespace container { } } - bool contains(value_type v) { + value_type contains(value_type &v) { key_type key = std::hash{}(v); node_type *preds[c_nMaxHeight]; node_type *succs[c_nMaxHeight]; int lFound = find(key, preds, succs); if (lFound == -1) - return false; + return nullptr; bool linked = succs[lFound]->fully_linked(); bool marked = succs[lFound]->marked(); - return (linked && !marked); + if (linked && !marked) + return v; + + return nullptr; } bool empty() { @@ -198,6 +215,10 @@ namespace cds { namespace container { } } + size_t size() { + return 0; + } + protected: void destroy() { node_type *p = m_Head; //->next(0).load(atomics::memory_order_relaxed).ptr(); diff --git a/test/unit/set/CMakeLists.txt b/test/unit/set/CMakeLists.txt index 23e10cdd1..b0e625257 100644 --- a/test/unit/set/CMakeLists.txt +++ b/test/unit/set/CMakeLists.txt @@ -81,7 +81,7 @@ add_test(NAME ${UNIT_SET_SKIP} COMMAND ${UNIT_SET_SKIP} WORKING_DIRECTORY ${EXEC set(UNIT_SET_LAZY_SKIP unit-set-lazy-skip) set(UNIT_SET_LAZY_SKIP_SOURCES ../main.cpp - lazyskiplist_dhp.cpp + lazy_skiplist.cpp ) add_executable(${UNIT_SET_LAZY_SKIP} ${UNIT_SET_LAZY_SKIP_SOURCES}) target_link_libraries(${UNIT_SET_LAZY_SKIP} ${CDS_TEST_LIBRARIES}) diff --git a/test/unit/set/lazy_skiplist.cpp b/test/unit/set/lazy_skiplist.cpp new file mode 100644 index 000000000..0369c0838 --- /dev/null +++ b/test/unit/set/lazy_skiplist.cpp @@ -0,0 +1,151 @@ +/* + This file is a part of libcds - Concurrent Data Structures library + + (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017 + + 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: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + 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. +*/ + +#include "test_set_nogc.h" + +#include +#include +#include + +namespace { + namespace cc = cds::container; + typedef cds::gc::nogc gc_type; + + class SkipListSet_NoGC : public cds_test::container_set_nogc + { + protected: + typedef cds_test::container_set_nogc base_class; + + //void SetUp() + //{} + + //void TearDown() + //{} + }; + + TEST_F( SkipListSet_NoGC, compare ) + { + typedef cc::SkipListSet< gc_type, int_item, + typename cc::skip_list::make_traits< + cds::opt::compare< cmp > + >::type + > set_type; + + set_type s; + test( s ); + } + + TEST_F( SkipListSet_NoGC, less ) + { + typedef cc::SkipListSet< gc_type, int_item, + typename cc::skip_list::make_traits< + cds::opt::less< base_class::less > + >::type + > set_type; + + set_type s; + test( s ); + } + + TEST_F( SkipListSet_NoGC, cmpmix ) + { + typedef cc::SkipListSet< gc_type, int_item, + typename cc::skip_list::make_traits< + cds::opt::less< base_class::less > + ,cds::opt::compare< cmp > + >::type + > set_type; + + set_type s; + test( s ); + } + + TEST_F( SkipListSet_NoGC, item_counting ) + { + struct set_traits: public cc::skip_list::traits + { + typedef cmp compare; + typedef base_class::less less; + typedef cds::atomicity::item_counter item_counter; + }; + typedef cc::SkipListSet< gc_type, int_item, set_traits >set_type; + + set_type s; + test( s ); + } + + TEST_F( SkipListSet_NoGC, backoff ) + { + struct set_traits: public cc::skip_list::traits + { + typedef cmp compare; + typedef base_class::less less; + typedef cds::atomicity::item_counter item_counter; + typedef cds::backoff::yield back_off; + }; + typedef cc::SkipListSet< gc_type, int_item, set_traits >set_type; + + set_type s; + test( s ); + } + + /*TEST_F( SkipListSet_NoGC, stat ) + { + struct set_traits: public cc::skip_list::traits + { + typedef cmp compare; + typedef base_class::less less; + typedef cds::atomicity::item_counter item_counter; + typedef cds::backoff::yield back_off; + typedef cc::skip_list::stat<> stat; + }; + typedef cc::SkipListSet< gc_type, int_item, set_traits >set_type; + + set_type s; + test( s ); + }*/ + + TEST_F( SkipListSet_NoGC, turbo32 ) + { + struct set_traits: public cc::skip_list::traits + { + typedef cmp compare; + typedef base_class::less less; + typedef cds::atomicity::item_counter item_counter; + typedef cc::skip_list::stat<> stat; + typedef cc::skip_list::turbo32 random_level_generator; + }; + typedef cc::SkipListSet< gc_type, int_item, set_traits >set_type; + + set_type s; + test( s ); + } + +} // namespace