From a3c62f0cdd489396e866077f12210517b8e884ad Mon Sep 17 00:00:00 2001 From: Tobias Fuchs Date: Wed, 25 Mar 2015 21:57:13 +0100 Subject: [PATCH] containers_cpp: fixes in LlxScx, added unit test for LlxScx --- containers_cpp/include/embb/containers/internal/fixed_size_list.h | 2 +- containers_cpp/include/embb/containers/internal/primitives/llx_scx-inl.h | 180 +++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------- containers_cpp/include/embb/containers/primitives/llx_scx.h | 61 ++++++++++++++++++++++++++++++++++++++++++++----------------- containers_cpp/test/llx_scx_test.cc | 13 +++++++++---- containers_cpp/test/llx_scx_test.h | 15 ++++++++++++--- containers_cpp/test/main.cc | 3 +++ 6 files changed, 116 insertions(+), 158 deletions(-) diff --git a/containers_cpp/include/embb/containers/internal/fixed_size_list.h b/containers_cpp/include/embb/containers/internal/fixed_size_list.h index e4006de..d4dc1d8 100644 --- a/containers_cpp/include/embb/containers/internal/fixed_size_list.h +++ b/containers_cpp/include/embb/containers/internal/fixed_size_list.h @@ -75,7 +75,7 @@ class FixedSizeList { size_t capacity /**< [IN] Capacity of the list */ ); - + /** * Copy constructor. */ diff --git a/containers_cpp/include/embb/containers/internal/primitives/llx_scx-inl.h b/containers_cpp/include/embb/containers/internal/primitives/llx_scx-inl.h index b10be44..11f3097 100644 --- a/containers_cpp/include/embb/containers/internal/primitives/llx_scx-inl.h +++ b/containers_cpp/include/embb/containers/internal/primitives/llx_scx-inl.h @@ -44,99 +44,8 @@ namespace embb { namespace containers { namespace primitives { -namespace internal { - -#if 0 -/** - * RAII-style implementation of a simplified hazard - * pointer scheme. SmartHazardPointer instances should - * reside only on the stack, never the heap, and T* - * values should reside only in the heap. A NodePtr can - * be automatically converted to a Node*, but an explicit - * constructor is needed to go the other way. - * - * Example: - * SmartHazardPointer shp = SmartHazardPointer( - * nodePool?>allocate()); - * // assign via default copy constructor: - * *shp = Node(...); - */ -template< typename T > -class SmartHazardPointer { - - public: - SmartHazardPointer(T ** node) { - while (true) { - ptr = *node; - table_->add(ptr); - // Full fence: - embb_atomic_memory_barrier(); - T * reread = *node; - // @TODO: Prove practical wait-freedom - if (read == reread) { - return; - } - } - } - - SmartHazardPointer(T * node) { - ptr = node; - } - - /** - * Dereference operator. - */ - T * operator->() { - return ptr; - } - - /** - * Dereference lvalue. - */ - T & operator*() { - return *ptr; - } - - /** - * Equality test with regular pointer - */ - bool operator==(T * const other) { - return ptr == other; - } - - /** - * Equality test with another SmartHazardPointer - */ - bool operator==(const SmartHazardPointer & other) { - return this->ptr == other->ptr; - } - - /** - * Destructor, retires hazard pointer. - */ - ~SmartHazardPointer() { - table_->remove(ptr); - } - - /** - * Conversion to regular pointer. - */ - operator T*() { - return ptr; - } - - private: - static HazardPointerTable * table_; - T * ptr; - -}; - -#endif - -} // namespace internal - -template< class UserData > -unsigned int LlxScx::ThreadId() { +template< typename UserData, typename ValuePool > +unsigned int LlxScx::ThreadId() { unsigned int thread_index; int return_val = embb_internal_thread_index(&thread_index); if (return_val != EMBB_SUCCESS) @@ -144,34 +53,33 @@ unsigned int LlxScx::ThreadId() { return thread_index; } -template< class UserData > -LlxScx::LlxScx(size_t max_links) -: max_links_(max_links) { +template< typename UserData, typename ValuePool > +LlxScx::LlxScx(size_t max_links) +: max_links_(max_links), + max_threads_(embb::base::Thread::GetThreadsMaxCount()), + scx_record_list_pool_(max_threads_) { typedef embb::containers::internal::FixedSizeList llx_result_list_t; typedef embb::containers::internal::FixedSizeList scx_record_list_t; - unsigned int num_threads = embb::base::Thread::GetThreadsMaxCount(); - // Table in shared memory containing for each r in TryStoreConditional's - // dependent links, a copy of r's info value in this threads local table - // of LLX results. - scx_record_list_t empty_scx_list(3); - info_fields_ = static_cast( + // Allocate a list of LLX results for every thread: + thread_llx_results_ = static_cast( embb::base::Allocation::AllocateCacheAligned( - num_threads * sizeof(empty_scx_list))); + max_threads_ * sizeof(llx_result_list_t))); } -template< class UserData > -LlxScx::~LlxScx() { - embb::base::Allocation::FreeAligned(info_fields_); +template< typename UserData, typename ValuePool > +LlxScx::~LlxScx() { + embb::base::Allocation::FreeAligned(thread_llx_results_); } -template< class UserData > -bool LlxScx::TryLoadLinked( +template< typename UserData, typename ValuePool > +bool LlxScx::TryLoadLinked( DataRecord_t * const data_record, DataRecord_t & user_data, bool & finalized) { finalized = false; + unsigned int thread_id = ThreadId(); // Order of initialization matters: bool marked_1 = data_record->IsMarkedForFinalize(); ScxRecord_t * curr_scx = data_record->ScxInfo().Load(); @@ -188,7 +96,7 @@ bool LlxScx::TryLoadLinked( llx_result.data_record = data_record; llx_result.scx_record = curr_scx; llx_result.user_data = user_data_local; - thread_llx_results_.Get().PushBack(llx_result); + thread_llx_results_[thread_id].PushBack(llx_result); // Set return value: user_data = user_data_local; return true; @@ -210,15 +118,15 @@ bool LlxScx::TryLoadLinked( return false; } -template< class UserData > +template< typename UserData, typename ValuePool > template< typename FieldType > -bool LlxScx::TryStoreConditional( +bool LlxScx::TryStoreConditional( embb::base::Atomic * field, FieldType value, embb::containers::internal::FixedSizeList & linked_deps, embb::containers::internal::FixedSizeList & finalize_deps) { typedef embb::containers::internal::FixedSizeList dr_list_t; -//typedef embb::containers::internal::FixedSizeList op_list_t; + typedef embb::containers::internal::FixedSizeList scx_op_list_t; // Preconditions: // 1. For each r in linked_deps, this thread has performed an invocation // I_r of LLX(r) linked to this SCX. @@ -228,17 +136,18 @@ bool LlxScx::TryStoreConditional( unsigned int thread_id = ThreadId(); // Let info_fields be a table in shared memory containing for each r in V, // a copy of r's info value in this threads local table of LLX results: - info_fields_[thread_id].clear(); + scx_op_list_t * info_fields = scx_record_list_pool_.Allocate(max_links_); dr_list_t::const_iterator it; dr_list_t::const_iterator end; end = linked_deps.end(); - // for each r in V ... + // for each r in linked_deps ... for (it = linked_deps.begin(); it != end; ++it) { // Find LLX result of r in thread-local table of LLX results: typedef embb::containers::internal::FixedSizeList llx_result_list; - llx_result_list::iterator l_it = thread_llx_results_.Get().begin(); - llx_result_list::iterator l_end = thread_llx_results_.Get().end(); + llx_result_list::iterator l_it = thread_llx_results_[thread_id].begin(); + llx_result_list::iterator l_end = thread_llx_results_[thread_id].end(); + // Find LLX result of r in thread-local LLX results: for (; l_it != l_end && l_it->data_record != *it; ++l_it); if (l_it == l_end) { // Missing LLX result for given linked data record, user did not @@ -246,9 +155,9 @@ bool LlxScx::TryStoreConditional( EMBB_THROW(embb::base::ErrorException, "Missing preceding LLX on a data record used for SCX"); } - // ... copy of r's info value in this threads local table of LLX results + // Copy of r's info value in this threads local table of LLX results ScxRecord_t scx_op(*(l_it->data_record->ScxInfo().Load())); - info_fields_[thread_id].PushBack(scx_op); + info_fields->PushBack(scx_op); } // Announce SCX operation. Lists linked_deps and finalize_dep are // guaranteed to remain on the stack until this announced operation @@ -263,27 +172,27 @@ bool LlxScx::TryStoreConditional( // old value: reinterpret_cast(field->Load()), // linked SCX operations: - &info_fields_[thread_id], + info_fields, // initial operation state: OperationState::InProgress); return scx.Help(); } -template< class UserData > -bool LlxScx::TryValidateLink( +template< typename UserData, typename ValuePool > +bool LlxScx::TryValidateLink( const DataRecord_t & field) { return true; // @TODO } // LlxScxRecord -template< class UserData > +template< typename UserData > LlxScxRecord::LlxScxRecord() : marked_for_finalize_(false) { scx_op_.Store(&dummy_scx); } -template< class UserData > +template< typename UserData > LlxScxRecord::LlxScxRecord( const UserData & user_data) : user_data_(user_data), @@ -293,24 +202,26 @@ LlxScxRecord::LlxScxRecord( // internal::ScxRecord -template< class DataRecord > +template< typename DataRecord > bool internal::ScxRecord::Help() { // We ensure that an SCX S does not change a data record // while it is frozen for another SCX S'. Instead, S uses // the information in the SCX record of S' to help S' // complete, so that the data record can be unfrozen. typedef embb::containers::internal::FixedSizeList dr_list_t; + typedef embb::containers::internal::FixedSizeList op_list_t; // Freeze all data records in data_records to protect their // mutable fields from being changed by other SCXs: dr_list_t::iterator linked_it = linked_data_records_->begin(); dr_list_t::iterator linked_end = linked_data_records_->end(); - for (unsigned int fieldIdx = 0; - linked_it != linked_end; - ++linked_it, ++fieldIdx) { + op_list_t::iterator scx_op_it = scx_ops_->begin(); + op_list_t::iterator scx_op_end = scx_ops_->end(); + for (; linked_it != linked_end && scx_op_it != scx_op_end; + ++linked_it, ++scx_op_it) { DataRecord * r = *linked_it; // pointer indexed by r in this->info_fields: - ScxRecord * rinfo = &info_fields_[fieldIdx]; - if (!r->ScxInfo().CompareAndSwap(rinfo, this)) { + ScxRecord * rinfo_exp = &(*scx_op_it); + if (!r->ScxInfo().CompareAndSwap(rinfo_exp, this)) { if (r->ScxInfo().Load() != this) { // could not freeze r because it is frozen for // another SCX: @@ -331,10 +242,8 @@ bool internal::ScxRecord::Help() { // mark step: dr_list_t::iterator finalize_it = finalize_data_records_->begin(); dr_list_t::iterator finalize_end = finalize_data_records_->end(); - for (unsigned int field_idx = finalize_range_.first; - finalize_it != finalize_end; - ++finalize_it, ++fieldRangeIdx) { - linked_data_records_[fieldRangeIdx]->MarkForFinalize(); + for (; finalize_it != finalize_end; ++finalize_it) { + (*finalize_it)->MarkForFinalize(); } // update CAS: cas_t expected_old_value = old_value_; @@ -347,6 +256,11 @@ bool internal::ScxRecord::Help() { return true; } +template< typename UserData > +internal::ScxRecord< LlxScxRecord > + LlxScxRecord::dummy_scx = + internal::ScxRecord< LlxScxRecord >(); + } // namespace primitives } // namespace containers } // namespace embb diff --git a/containers_cpp/include/embb/containers/primitives/llx_scx.h b/containers_cpp/include/embb/containers/primitives/llx_scx.h index e657634..f9c1db2 100644 --- a/containers_cpp/include/embb/containers/primitives/llx_scx.h +++ b/containers_cpp/include/embb/containers/primitives/llx_scx.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include namespace embb { @@ -42,7 +44,7 @@ namespace internal { * SCX operation description. An SCX record contains all information * required to allow any process to complete a pending SCX operation. */ -template< class DataRecord > +template< typename DataRecord > class ScxRecord { private: @@ -66,11 +68,11 @@ class ScxRecord { */ ScxRecord() : linked_data_records_(0), - finalize_data_records_(0) + finalize_data_records_(0), new_value_(0), old_value_(0), scx_ops_(0), - state_(OperationState::Undefined), + state_(OperationState::Comitted), all_frozen_(false) { field_ = 0; } @@ -84,7 +86,7 @@ class ScxRecord { embb::base::Atomic * field, cas_t new_value, cas_t old_value, - embb::containers::internal::FixedSizeList * scx_ops, + embb::containers::internal::FixedSizeList * scx_ops, OperationState operation_state) : linked_data_records_(&linked_data_records), finalize_data_records_(&finalize_data_records), @@ -143,7 +145,7 @@ class ScxRecord { * List of SCX operation descriptions associated with data records * linked with this SCX operation. */ - embb::containers::internal::FixedSizeList scx_ops_; + embb::containers::internal::FixedSizeList * scx_ops_; /** * Current state of this SCX record. @@ -165,7 +167,7 @@ class ScxRecord { * Wraps user-defined data with fields required for LLX/SCX algorithm. * Mutable fields must each be contained in a single word. */ -template< class UserData > +template< typename UserData > class LlxScxRecord { private: @@ -203,9 +205,12 @@ class LlxScxRecord { * Assignment operator. */ LlxScxRecord & operator=(const LlxScxRecord & rhs) { - user_data_ = rhs.user_data_; - scx_op_.Store(rhs.scx_info_.Load()); - marked_for_finalize_ = rhs.marked_for_finalize_; + if (this != &rhs) { + user_data_ = rhs.user_data_; + scx_op_.Store(rhs.scx_op_.Load()); + marked_for_finalize_ = rhs.marked_for_finalize_; + } + return *this; } /** @@ -299,10 +304,13 @@ class LlxScxRecord { * "Pragmatic Primitives for Non-blocking Data Structures" * (Brown et al., 2013). * - * \tparam MaxLinks Maximum number of active LL-dependencies per thread * \tparam UserData Type containing mutable fields + * \tparam ValuePool Type containing mutable fields */ -template< class UserData > +template< + typename UserData, + typename ValuePool = embb::containers::LockFreeTreeValuePool< bool, false > +> class LlxScx { private: @@ -335,8 +343,7 @@ class LlxScx { DataRecord_t * const data_record, /**< [IN] Pointer to data record to load */ DataRecord_t & data, - /**< [OUT] Atomic snapshot of \c NumMutableFields fields in data - record at given index */ + /**< [OUT] Atomic snapshot of data record */ bool & finalized /**< [OUT] Indicating whether requested fields have been finalized */ ); @@ -393,6 +400,11 @@ class LlxScx { size_t max_links_; /** + * Maximum number of threads engaging in operations on this LLX/SCX instance. + */ + unsigned int max_threads_; + + /** * Shared table containing for each r in V, a copy of r's info * value in this thread's local table of LLX results. * @@ -402,23 +414,38 @@ class LlxScx { * r_i in V -> *ScxRecord(thread_llx_results_[r_i].data_record.ScxInfo()) * } */ - embb::containers::internal::FixedSizeList * info_fields_; +// embb::containers::internal::FixedSizeList * info_fields_; + + /** + * Shared table containing for each r in V, a copy of r's info + * value in this thread's local table of LLX results. + * + * thread_id -> { + * r_1 in V -> *ScxRecord(thread_llx_results_[r_1].data_record.ScxInfo()), + * ... + * r_i in V -> *ScxRecord(thread_llx_results_[r_i].data_record.ScxInfo()) + * } + */ + embb::containers::ObjectPool< + embb::containers::internal::FixedSizeList, ValuePool > + scx_record_list_pool_; /** * Thread-specific list of LLX results performed by the thread. */ - embb::base::ThreadSpecificStorage< - embb::containers::internal::FixedSizeList > - thread_llx_results_; + embb::containers::internal::FixedSizeList * + thread_llx_results_; /** * Prevent default construction. */ LlxScx(); + /** * Prevent copy construction. */ LlxScx(const LlxScx &); + /** * Prevent assignment. */ diff --git a/containers_cpp/test/llx_scx_test.cc b/containers_cpp/test/llx_scx_test.cc index 3e77768..8a3902e 100644 --- a/containers_cpp/test/llx_scx_test.cc +++ b/containers_cpp/test/llx_scx_test.cc @@ -37,14 +37,13 @@ using embb::containers::primitives::LlxScxRecord; using embb::containers::primitives::LlxScx; LlxScxTest::LlxScxTest() : - num_threads_(static_cast - (partest::TestSuite::GetDefaultNumThreads())) { + num_threads_( + static_cast(partest::TestSuite::GetDefaultNumThreads())) { CreateUnit("SerialTest").Add(&LlxScxTest::SerialTest, this); } void LlxScxTest::SerialTest() { typedef LlxScxTest::Node Node; - // Global: LlxScx llxscx(3); @@ -72,7 +71,7 @@ void LlxScxTest::SerialTest() { PT_ASSERT(llxscx.TryLoadLinked(&dr3, l3, finalized)); PT_ASSERT(!finalized); - FixedSizeList< LlxScxRecord * > + FixedSizeList< LlxScxRecord * > linked_deps(3); linked_deps.PushBack(&dr1); linked_deps.PushBack(&dr2); @@ -92,6 +91,12 @@ void LlxScxTest::SerialTest() { linked_deps, // V: dependencies, must be LL'd before finalize_deps // R: Subsequence of V to be finalized )); + // Following LLX calls on finalized data records are + // expected to fail: + PT_ASSERT(!llxscx.TryLoadLinked(&dr2, l2, finalized)); + PT_ASSERT(finalized); + PT_ASSERT(!llxscx.TryLoadLinked(&dr3, l3, finalized)); + PT_ASSERT(finalized); } } // namespace test diff --git a/containers_cpp/test/llx_scx_test.h b/containers_cpp/test/llx_scx_test.h index 17e925f..9debd3d 100644 --- a/containers_cpp/test/llx_scx_test.h +++ b/containers_cpp/test/llx_scx_test.h @@ -66,10 +66,19 @@ class LlxScxTest : public partest::TestCase { next_.Store(next_node); } + Node(const Node & other) + : value_(other.value_) { + next_.Store(other.next_.Load()); + count_.Store(other.count_.Load()); + } + Node & operator=(const Node & rhs) { - count_.Store(rhs.count_.Load()); - next_.Store(rhs.next_.Load()); - value_ = rhs.value_; + if (this != &rhs) { + count_.Store(rhs.count_.Load()); + next_.Store(rhs.next_.Load()); + value_ = rhs.value_; + } + return *this; } }; diff --git a/containers_cpp/test/main.cc b/containers_cpp/test/main.cc index 0e26fee..6ee68d4 100644 --- a/containers_cpp/test/main.cc +++ b/containers_cpp/test/main.cc @@ -40,6 +40,7 @@ #include "./stack_test.h" #include "./hazard_pointer_test.h" #include "./object_pool_test.h" +#include "./llx_scx_test.h" #define COMMA , @@ -55,6 +56,7 @@ using embb::containers::test::HazardPointerTest; using embb::containers::test::QueueTest; using embb::containers::test::StackTest; using embb::containers::test::ObjectPoolTest; +using embb::containers::test::LlxScxTest; PT_MAIN("Data Structures C++") { unsigned int max_threads = static_cast( @@ -70,6 +72,7 @@ PT_MAIN("Data Structures C++") { PT_RUN(StackTest< LockFreeStack >); PT_RUN(ObjectPoolTest< LockFreeTreeValuePool >); PT_RUN(ObjectPoolTest< WaitFreeArrayValuePool >); + PT_RUN(LlxScxTest); PT_EXPECT(embb_get_bytes_allocated() == 0); } -- libgit2 0.26.0