diff --git a/cds/compiler/gcc/amd64/ts_hardwaretimestamp.h b/cds/compiler/gcc/amd64/ts_hardwaretimestamp.h new file mode 100644 index 000000000..278170d5a --- /dev/null +++ b/cds/compiler/gcc/amd64/ts_hardwaretimestamp.h @@ -0,0 +1,43 @@ +#ifndef CDSLIB_COMPILER_GCC_AMD64_TS_HARDWARETIMESTAMP_H +#define CDSLIB_COMPILER_GCC_AMD64_TS_HARDWARETIMESTAMP_H + +#include +#include + +namespace cds { namespace tshardwaretimestamp { + namespace gcc { namespace amd64 { + +# define CDS_ts_hardwaretimestamp_hwptime_defined + static inline uint64_t get_hwptime() + { + uint64_t aux; + uint64_t rax, rdx; + __asm__ volatile ("rdtscp\n" : "=a" (rax), "=d" (rdx), "=c" (aux) : : ); + return (rdx << 32) + rax; + } + +# define CDS_ts_hardwaretimestamp_hwtime_defined + static inline uint64_t get_hwtime() + { + uint64_t high, low; + __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); + return ((uint64_t)low) | (((uint64_t)high) << 32); + } + + static inline int has_rdtscp() + { + unsigned int eax, ebx, ecx, edx; + if (__get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx)) + return (edx >> 27) & 0x1; + else + return 0; + } + + }} // namespace gcc::amd64 + + namespace platform { + using namespace gcc::amd64; + } +}} // namespace cds::tshardwaretimestamp + +#endif // #ifndef CDSLIB_COMPILER_GCC_AMD64_TS_HARDWARETIMESTAMP_H diff --git a/cds/compiler/gcc/x86/ts_hardwaretimestamp.h b/cds/compiler/gcc/x86/ts_hardwaretimestamp.h new file mode 100644 index 000000000..ee269339e --- /dev/null +++ b/cds/compiler/gcc/x86/ts_hardwaretimestamp.h @@ -0,0 +1,42 @@ +#ifndef CDSLIB_COMPILER_GCC_X86_TS_HARDWARETIMESTAMP_H +#define CDSLIB_COMPILER_GCC_X86_TS_HARDWARETIMESTAMP_H + +#include +#include + +namespace cds { namespace tshardwaretimestamp { + namespace gcc { namespace x86 { + +# define CDS_ts_hardwaretimestamp_hwptime_defined + static inline uint64_t get_hwptime() + { + uint64_t ret; + __asm__ volatile ("rdtscp\n" : "=A" (ret) : : "ecx"); + return ret; + } + +# define CDS_ts_hardwaretimestamp_hwtime_defined + static inline uint64_t get_hwtime() + { + uint64_t ret; + __asm__ volatile("rdtsc" : "=A"(ret)); + return ret; + } + + static inline int has_rdtscp() + { + unsigned int eax, ebx, ecx, edx; + if (__get_cpuid(0x80000001, &eax, &ebx, &ecx, &edx)) + return (edx >> 27) & 0x1; + else + return 0; + } + + }} // namespace gcc::x86 + + namespace platform { + using namespace gcc::x86; + } +}} // namespace cds::tshardwaretimestamp + +#endif // #ifndef CDSLIB_COMPILER_GCC_X86_TS_HARDWARETIMESTAMP_H diff --git a/cds/compiler/ts_hardwaretimestamp.h b/cds/compiler/ts_hardwaretimestamp.h new file mode 100644 index 000000000..0a4352b77 --- /dev/null +++ b/cds/compiler/ts_hardwaretimestamp.h @@ -0,0 +1,16 @@ +#ifndef CDSLIB_COMPILER_TS_HARDWARETIMESTAMP_H +#define CDSLIB_COMPILER_TS_HARDWARETIMESTAMP_H + +// Choose appropriate header for current architecture and compiler + +#if CDS_COMPILER == CDS_COMPILER_GCC || CDS_COMPILER == CDS_COMPILER_CLANG || CDS_COMPILER == CDS_COMPILER_INTEL +# if CDS_PROCESSOR_ARCH == CDS_PROCESSOR_X86 +# include +# elif CDS_PROCESSOR_ARCH == CDS_PROCESSOR_AMD64 +# include +# endif +#else +# error "Undefined compiler" +#endif + +#endif // #ifndef CDSLIB_COMPILER_TS_HARDWARETIMESTAMP_H diff --git a/cds/container/ts_deque.h b/cds/container/ts_deque.h new file mode 100644 index 000000000..7352e38e7 --- /dev/null +++ b/cds/container/ts_deque.h @@ -0,0 +1,225 @@ +#ifndef CDSLIB_CONTAINER_TS_DEQUE_H +#define CDSLIB_CONTAINER_TS_DEQUE_H + +#include +#include +#include + +namespace cds { namespace container { + + /// TSDeque related definitions + /** @ingroup cds_nonintrusive_helper + */ + namespace tsdeque { + /// TSDeque default type traits + struct traits + { + /// Item counting feature; by default, disabled. Use \p cds::atomicity::item_counter to enable item counting + typedef cds::atomicity::empty_item_counter item_counter; + + /// Random engine to generate a random position in array of thread-local buffers + typedef opt::v::c_rand random_engine; + }; + + /// Metafunction converting option list to \p tsdeque::traits + /** + Supported \p Options are: + - opt::item_counter - the type of item counting feature. + Default is \p cds::atomicity::empty_item_counter (item counting disabled). + - opt::random_engine - a random engine to generate a random position in array of thread-local buffers. + Default is \p opt::v::c_rand. + + Example: declare \p %TSDeque with item counting + \code + typedef cds::container::TSDeque< Foo, + typename cds::container::tsdeque::make_traits< + cds::opt::item_counter< cds::atomicity::item_counter > + >::type + > myDeque; + \endcode + */ + 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 tsdeque + + /// Fast Concurrent Deque Through Explicit Timestamping + /** @ingroup cds_nonintrusive_deque + + Source + - [2014] Mike Dodds, Andreas Haas, Christoph M. Kirsch + "Fast Concurrent Data-Structures Through Explicit Timestamping" + + Template arguments + - \p T - value type to be stored in the deque + - \p Timestamp - the way to acquire timestamps + - \p Traits - deque traits, default is \p tsdeque::traits. You can use \p tsdeque::make_traits + metafunction to make your traits or just derive your traits from \p %tsdeque::traits: + \code + struct myTraits: public cds::container::tsdeque::traits { + typedef cds::atomicity::item_counter item_counter; + }; + typedef cds::container::TSDeque< Foo, Timestamp, myTraits > myDeque; + + // Equivalent make_traits example: + typedef cds::container::TSDeque< Foo, Timestamp + typename cds::container::tsdeque::make_traits< + cds::opt::item_counter< cds::atomicity::item_counter > + >::type + > myDeque; + \endcode + */ + template + class TSDeque + { + public: + typedef T value_type; ///< Type of value to be stored in the deque + typedef Timestamp timestamp; ///< Algorithm of acquiring timestamps + typedef Traits traits; ///< Deque traits + + typedef typename traits::item_counter item_counter; ///< Item counting policy used + typedef typename traits::random_engine random_engine; ///< Random engine used + + private: + TSDequeBuffer *buffer_; + timestamp *timestamping_; + item_counter item_counter_; + + public: + TSDeque(uint64_t num_threads, uint64_t delay) + { + timestamping_ = new timestamp(); + timestamping_->initialize(delay, num_threads); + + buffer_ = new TSDequeBuffer(num_threads, timestamping_); + } + + ~TSDeque() + { + clear(); + + delete timestamping_; + delete buffer_; + } + + /// Inserts a new element at the left end of the deque container + /** + The function always returns \p true + */ + bool insert_left(value_type element) + { + std::atomic *item = buffer_->insert_left(element); + timestamping_->set_timestamp(item); + ++item_counter_; + return true; + } + + /// Inserts a new element at the right end of the deque container + /** + The function always returns \p true + */ + bool insert_right(value_type element) + { + std::atomic *item = buffer_->insert_right(element); + timestamping_->set_timestamp(item); + ++item_counter_; + return true; + } + + /// Removes the element from the left end of the deque container + /** + The function returns \p false if the deque is empty, \p true otherwise. + */ + bool remove_left(value_type *element) + { + uint64_t invocation_time[2]; + timestamping_->read_time(invocation_time); + bool empty; + while (buffer_->try_remove_left(element, invocation_time, &empty)) + { + if (!empty) + { + --item_counter_; + return true; + } + } + return false; + } + + /// Removes the element from the right end of the deque container + /** + The function returns \p false if the deque is empty, \p true otherwise. + */ + bool remove_right(value_type *element) + { + uint64_t invocation_time[2]; + timestamping_->read_time(invocation_time); + bool empty; + while (buffer_->try_remove_right(element, invocation_time, &empty)) + { + if (!empty) + { + --item_counter_; + return true; + } + } + return false; + } + + /// Clears the deque (non-atomic) + /** + The function erases all items from the deque. + + The function is not atomic. It cleans up the deque and then resets the item counter to zero. + If there are a thread that performs insertion while \p clear is working the result is undefined in general case: + empty() may return \p true but the deque may contain item(s). + Therefore, \p clear may be used only for debugging purposes. + */ + void clear() + { + value_type item; + while (remove_right(&item)) {} + item_counter_.reset(); + } + + /// Checks if the deque is empty + /** + @warning If you use \p atomicity::empty_item_counter in \p traits::item_counter, + the function always returns \p true. + */ + bool empty() const + { + return size() == 0; + } + + /// Returns item count in the deque + /** + @warning If you use \p atomicity::empty_item_counter in \p traits::item_counter, + the function always returns 0. + */ + size_t size() const + { + return item_counter_; + } + + //@cond + /// The class has no internal statistics. For test consistency only + std::nullptr_t statistics() const + { + return nullptr; + } + //@endcond + }; + +}} // namespace cds::container + +#endif // #ifndef CDSLIB_CONTAINER_TS_DEQUE_H diff --git a/cds/container/ts_deque_buffer.h b/cds/container/ts_deque_buffer.h new file mode 100644 index 000000000..c1e8ed466 --- /dev/null +++ b/cds/container/ts_deque_buffer.h @@ -0,0 +1,668 @@ +/* +Copyright (c) 2012-2016, the Scal Project Authors. All rights reserved. Please see the AUTHORS file for details. + +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 OWNER 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. + +The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the Scal Project. + +-------------------------------------------------- + +Modified by: + Alexey Chirukhin https://github.com/pr3sto + Eduard Blees https://github.com/EduardBlees +*/ + +#ifndef CDSLIB_CONTAINER_TS_DEQUE_BUFFER_H +#define CDSLIB_CONTAINER_TS_DEQUE_BUFFER_H + +#include +#include +#include +#include + +namespace cds { namespace container { + + template + class TSDequeBuffer + { + private: + typedef struct Item + { + std::atomic left; + std::atomic right; + std::atomic taken; + std::atomic data = {T()}; + std::atomic timestamp[2]; + // Insertion index, needed for the termination condition in + // get_left_item. Items inserted at the left get negative + // indices, items inserted at the right get positive indices. + std::atomic index; + } Item; + + uint64_t num_threads_; + std::atomic **left_; + std::atomic **right_; + int64_t **next_index_; + // The pointers for the emptiness check. + Item ***emptiness_check_left_; + Item ***emptiness_check_right_; + + TimeStamp *timestamping_; + + RandomEngine random_engine_; + + uint64_t global_thread_id_counter_; + boost::thread_specific_ptr thread_id_; + + // Helper function to get index of thread + int get_thread_id() + { + int* temp = thread_id_.get(); + if (temp == NULL) + { + int index = __sync_fetch_and_add(&global_thread_id_counter_, 1); + thread_id_.reset(new int (index)); + return index; + } + return *temp; + } + + // Helper function to remove the ABA counter from a pointer. + void *get_aba_free_pointer(void *pointer) + { + uint64_t result = (uint64_t)pointer; + result &= 0xfffffffffffffff8; + return (void *)result; + } + + // Helper function which retrieves the ABA counter of a pointer old + // and sets this ABA counter + increment to the pointer pointer. + void *add_next_aba(void *pointer, void *old, uint64_t increment) + { + uint64_t aba = (uint64_t)old; + aba += increment; + aba &= 0x7; + uint64_t result = (uint64_t)pointer; + result = (result & 0xfffffffffffffff8) | aba; + return (void *)((result & 0xffffffffffffff8) | aba); + } + + // Returns the leftmost not-taken item from the thread-local list + // indicated by thread_id. + Item *get_left_item(uint64_t thread_id) + { + // Read the item pointed to by the right pointer. The iteration through + // the linked list can stop at that item. + Item *old_right = right_[thread_id]->load(); + Item *right = (Item *)get_aba_free_pointer(old_right); + int64_t threshold = right->index.load(); + + // Read the leftmost item. + Item *result = (Item *)get_aba_free_pointer(left_[thread_id]->load()); + + // We start at the left pointer and iterate to the right until we + // find the first item which has not been taken yet. + while (true) + { + // We reached a node further right than the original right-most + // node. We do not have to search any further to the right, we + // will not take the element anyways. + if (result->index.load() > threshold) + { + return NULL; + } + // We found a good node, return it. + if (result->taken.load() == 0) + { + return result; + } + // We have reached the end of the list and found nothing, so we + // return NULL. + if (result->right.load() == result) + { + return NULL; + } + result = result->right.load(); + } + } + + // Returns the rightmost not-taken item from the thread-local list + // indicated by thread_id. + Item *get_right_item(uint64_t thread_id) + { + // Read the item pointed to by the left pointer. The iteration through + // the linked list can stop at that item. + Item *old_left = left_[thread_id]->load(); + Item *left = (Item *)get_aba_free_pointer(old_left); + int64_t threshold = left->index.load(); + + Item *result = (Item *)get_aba_free_pointer(right_[thread_id]->load()); + + // We start at the right pointer and iterate to the left until we + // find the first item which has not been taken yet. + while (true) + { + // We reached a node further left than the original left-most + // node. We do not have to search any further to the left, we + // will not take the element anyways. + if (result->index.load() < threshold) + { + return NULL; + } + // We found a good node, return it. + if (result->taken.load() == 0) + { + return result; + } + // We have reached the end of the list and found nothing, so we + // return NULL. + if (result->left.load() == result) + { + return NULL; + } + result = result->left.load(); + } + } + + public: + TSDequeBuffer(uint64_t num_threads, TimeStamp *timestamping) + { + global_thread_id_counter_ = 0; + + num_threads_ = num_threads; + timestamping_ = timestamping; + + left_ = new std::atomic *[num_threads_]; + right_ = new std::atomic *[num_threads_]; + + next_index_ = new int64_t *[num_threads_]; + + emptiness_check_left_ = new Item **[num_threads_]; + emptiness_check_right_ = new Item **[num_threads_]; + + for (uint64_t i = 0; i < num_threads_; i++) + { + left_[i] = new std::atomic(); + right_[i] = new std::atomic(); + + next_index_[i] = new int64_t(); + + // Add a sentinal node. + Item *new_item = new Item(); + timestamping_->init_sentinel_atomic(new_item->timestamp); + new_item->taken.store(1); + new_item->left.store(new_item); + new_item->right.store(new_item); + new_item->index.store(0); + left_[i]->store(new_item); + right_[i]->store(new_item); + *next_index_[i] = 1; + + emptiness_check_left_[i] = new Item *[num_threads_]; + emptiness_check_right_[i] = new Item *[num_threads_]; + } + } + + ~TSDequeBuffer() + { + for (uint64_t i = 0; i < num_threads_; i++) + { + delete left_[i]; + delete right_[i]; + delete next_index_[i]; + delete[] emptiness_check_left_[i]; + delete[] emptiness_check_right_[i]; + } + delete[] left_; + delete[] right_; + delete[] next_index_; + delete[] emptiness_check_left_; + delete[] emptiness_check_right_; + + } + + inline std::atomic *insert_left(T element) + { + uint64_t thread_id = get_thread_id(); + + // Create a new item. + Item *new_item = new Item(); + timestamping_->init_top_atomic(new_item->timestamp); + new_item->data.store(element); + new_item->taken.store(0); + new_item->left.store(new_item); + // Items inserted at the left get negative indices. Thereby the + // order of items in the thread-local lists correspond with the + // order of indices, and we can use the sign of the index to + // determine on which side an item has been inserted. + new_item->index = -((*next_index_[thread_id])++); + + // Determine leftmost not-taken item in the list. The new item is + // inserted to the left of that item. + Item *old_left = left_[thread_id]->load(); + + Item *left = (Item *)get_aba_free_pointer(old_left); + while (left->right.load() != left && left->taken.load()) + { + left = left->right.load(); + } + + if (left->taken.load() && left->right.load() == left) + { + // The buffer is empty. We have to increase the aba counter of the + // right pointer too to guarantee that a pending right-pointer + // update of a remove operation does not make the left and the + // right pointer point to different lists. + + left = (Item *)get_aba_free_pointer(old_left); + left->right.store(left); + Item *old_right = right_[thread_id]->load(); + right_[thread_id]->store((Item *)add_next_aba(left, old_right, 1)); + } + + // Add the new item to the list. + new_item->right.store(left); + left->left.store(new_item); + left_[thread_id]->store( + (Item *)add_next_aba(new_item, old_left, 1)); + + // Return a pointer to the timestamp location of the item so that a + // timestamp can be added. + return new_item->timestamp; + } + + inline std::atomic *insert_right(T element) + { + uint64_t thread_id = get_thread_id(); + + // Create a new item. + Item *new_item = new Item(); + timestamping_->init_top_atomic(new_item->timestamp); + new_item->data.store(element); + new_item->taken.store(0); + new_item->right.store(new_item); + new_item->index = (*next_index_[thread_id])++; + + // Determine the rightmost not-taken item in the list. The new item is + // inserted to the right of that item. + Item *old_right = right_[thread_id]->load(); + + Item *right = (Item *)get_aba_free_pointer(old_right); + while (right->left.load() != right && right->taken.load()) + { + right = right->left.load(); + } + + if (right->taken.load() && right->left.load() == right) + { + // The buffer is empty. We have to increase the aba counter of the + // left pointer too to guarantee that a pending left-pointer + // update of a remove operation does not make the left and the + // right pointer point to different lists. + right = (Item *)get_aba_free_pointer(old_right); + right->left.store(right); + Item *old_left = left_[thread_id]->load(); + left_[thread_id]->store((Item *)add_next_aba(right, old_left, 1)); + } + + // Add the new item to the list. + new_item->left.store(right); + right->right.store(new_item); + right_[thread_id]->store((Item *)add_next_aba(new_item, old_right, 1)); + + // Return a pointer to the timestamp location of the item so that a + // timestamp can be added. + return new_item->timestamp; + } + + // Helper function which returns true if the item was inserted at the left. + inline bool inserted_left(Item *item) + { + return item->index.load() < 0; + } + + // Helper function which returns true if the item was inserted at the right. + inline bool inserted_right(Item *item) + { + return item->index.load() > 0; + } + + // Helper function which returns true if item1 is more left than item2. + inline bool is_more_left(Item *item1, uint64_t *timestamp1, Item *item2, uint64_t *timestamp2) + { + if (inserted_left(item2)) + { + if (inserted_left(item1)) + { + return timestamping_->is_later(timestamp1, timestamp2); + } + else + { + return false; + } + } + else + { + if (inserted_left(item1)) + { + return true; + } + else + { + return timestamping_->is_later(timestamp2, timestamp1); + } + } + } + + // Helper function which returns true if item1 is more right than item2. + inline bool is_more_right(Item *item1, uint64_t *timestamp1, Item *item2, uint64_t *timestamp2) + { + if (inserted_right(item2)) + { + if (inserted_right(item1)) + { + return timestamping_->is_later(timestamp1, timestamp2); + } + else + { + return false; + } + } + else + { + if (inserted_right(item1)) + { + return true; + } + else + { + return timestamping_->is_later(timestamp2, timestamp1); + } + } + } + + bool try_remove_left(T *element, uint64_t *invocation_time, bool *empty_check) + { + *empty_check = false; + + // Initialize the data needed for the emptiness check. + uint64_t thread_id = get_thread_id(); + Item **emptiness_check_left = + emptiness_check_left_[thread_id]; + Item **emptiness_check_right = + emptiness_check_right_[thread_id]; + bool empty = true; + // Initialize the result pointer to NULL, which means that no + // element has been removed. + Item *result = NULL; + // Indicates the index which contains the youngest item. + uint64_t buffer_index = -1; + // Memory on the stack frame where timestamps of items can be stored + // temporarily. + uint64_t tmp_timestamp[2][2]; + // Index in the tmp_timestamp array which is not used at the moment. + uint64_t tmp_index = 1; + timestamping_->init_sentinel(tmp_timestamp[0]); + uint64_t *timestamp = tmp_timestamp[0]; + // Stores the value of the remove pointer of a thead-local buffer + // before the buffer is actually accessed. + Item *old_left = NULL; + + // Read the start time of the iteration. Items which were timestamped + // after the start time and inserted at the right are not removed. + uint64_t start_time[2]; + timestamping_->read_time(start_time); + // We start iterating over the thread-local lists at a random index. + uint64_t start = random_engine_(); + // We iterate over all thead-local buffers + for (uint64_t i = 0; i < num_threads_; i++) + { + uint64_t tmp_buffer_index = (start + i) % num_threads_; + // We get the remove/insert pointer of the current thread-local buffer. + Item *tmp_left = left_[tmp_buffer_index]->load(); + // We get the youngest element from that thread-local buffer. + Item *item = get_left_item(tmp_buffer_index); + // If we found an element, we compare it to the youngest element + // we have found until now. + if (item != NULL) + { + empty = false; + uint64_t *item_timestamp; + timestamping_->load_timestamp(tmp_timestamp[tmp_index], item->timestamp); + item_timestamp = tmp_timestamp[tmp_index]; + + if (inserted_left(item) && !timestamping_->is_later(invocation_time, item_timestamp)) + { + uint64_t expected = 0; + if (item->taken.load() == 0 && item->taken.compare_exchange_weak(expected, 1)) + { + // Try to adjust the remove pointer. It does not matter if + // this CAS fails. + left_[tmp_buffer_index]->compare_exchange_weak( + tmp_left, (Item *)add_next_aba(item, tmp_left, 0)); + *element = item->data.load(); + return true; + } + else + { + item = get_left_item(tmp_buffer_index); + if (item != NULL) + { + timestamping_->load_timestamp(tmp_timestamp[tmp_index], item->timestamp); + item_timestamp = tmp_timestamp[tmp_index]; + } + } + } + + if (item != NULL && (result == NULL || is_more_left(item, item_timestamp, result, timestamp))) + { + // We found a new leftmost item, so we remember it. + result = item; + buffer_index = tmp_buffer_index; + timestamp = item_timestamp; + tmp_index ^= 1; + old_left = tmp_left; + + // Check if we can remove the element immediately. + if (inserted_left(result) && !timestamping_->is_later(invocation_time, timestamp)) + { + uint64_t expected = 0; + if (result->taken.load() == 0) + { + if (result->taken.compare_exchange_weak( + expected, 1)) + { + // Try to adjust the remove pointer. It does not matter if + // this CAS fails. + left_[buffer_index]->compare_exchange_weak( + old_left, (Item *)add_next_aba(result, old_left, 0)); + + *element = result->data.load(); + return true; + } + } + } + } + } + else + { + // No element was found, work on the emptiness check. + if (emptiness_check_left[tmp_buffer_index] != tmp_left) + { + empty = false; + emptiness_check_left[tmp_buffer_index] = + tmp_left; + } + Item *tmp_right = right_[tmp_buffer_index]->load(); + if (emptiness_check_right[tmp_buffer_index] != tmp_right) + { + empty = false; + emptiness_check_right[tmp_buffer_index] = + tmp_right; + } + } + } + if (result != NULL) + { + if (!timestamping_->is_later(timestamp, start_time)) + { + // The found item was timestamped after the start of the iteration, + // so it is save to remove it. + uint64_t expected = 0; + if (result->taken.load() == 0) + { + if (result->taken.compare_exchange_weak( + expected, 1)) + { + // Try to adjust the remove pointer. It does not matter if this + // CAS fails. + left_[buffer_index]->compare_exchange_weak( + old_left, (Item *)add_next_aba(result, old_left, 0)); + *element = result->data.load(); + return true; + } + } + } + } + + *empty_check = true; + return !empty; + } + + bool try_remove_right(T *element, uint64_t *invocation_time, bool *empty_check) + { + *empty_check = false; + + // Initialize the data needed for the emptiness check. + uint64_t thread_id = get_thread_id(); + Item **emptiness_check_left = + emptiness_check_left_[thread_id]; + Item **emptiness_check_right = + emptiness_check_right_[thread_id]; + bool empty = true; + // Initialize the result pointer to NULL, which means that no + // element has been removed. + Item *result = NULL; + // Indicates the index which contains the youngest item. + uint64_t buffer_index = -1; + // Memory on the stack frame where timestamps of items can be stored + // temporarily. + uint64_t tmp_timestamp[2][2]; + // Index in the tmp_timestamp array whihc is not used at the moment. + uint64_t tmp_index = 1; + timestamping_->init_sentinel(tmp_timestamp[0]); + uint64_t *timestamp = tmp_timestamp[0]; + // Stores the value of the remove pointer of a thead-local buffer + // before the buffer is actually accessed. + Item *old_right = NULL; + + // Read the start time of the iteration. Items which were timestamped + // after the start time and inserted at the left are not removed. + uint64_t start_time[2]; + timestamping_->read_time(start_time); + // We start iterating over the thread-local lists at a random index. + uint64_t start = random_engine_(); + // We iterate over all thead-local buffers + for (uint64_t i = 0; i < num_threads_; i++) + { + uint64_t tmp_buffer_index = (start + i) % num_threads_; + // We get the remove/insert pointer of the current thread-local buffer. + Item *tmp_right = right_[tmp_buffer_index]->load(); + // We get the youngest element from that thread-local buffer. + Item *item = get_right_item(tmp_buffer_index); + // If we found an element, we compare it to the youngest element + // we have found until now. + if (item != NULL) + { + empty = false; + uint64_t *item_timestamp; + timestamping_->load_timestamp(tmp_timestamp[tmp_index], item->timestamp); + item_timestamp = tmp_timestamp[tmp_index]; + + if (inserted_right(item) && !timestamping_->is_later(invocation_time, item_timestamp)) + { + uint64_t expected = 0; + if (item->taken.load() == 0 && item->taken.compare_exchange_weak(expected, 1)) + { + // Try to adjust the remove pointer. It does not matter if + // this CAS fails. + right_[tmp_buffer_index]->compare_exchange_weak( + tmp_right, (Item *)add_next_aba(item, tmp_right, 0)); + *element = item->data.load(); + return true; + } + else + { + item = get_right_item(tmp_buffer_index); + if (item != NULL) + { + timestamping_->load_timestamp(tmp_timestamp[tmp_index], item->timestamp); + item_timestamp = tmp_timestamp[tmp_index]; + } + } + } + + if (item != NULL && (result == NULL || is_more_right(item, item_timestamp, result, timestamp))) + { + // We found a new youngest element, so we remember it. + result = item; + buffer_index = tmp_buffer_index; + timestamp = item_timestamp; + tmp_index ^= 1; + old_right = tmp_right; + } + } + else + { + // No element was found, work on the emptiness check. + if (emptiness_check_right[tmp_buffer_index] != tmp_right) + { + empty = false; + emptiness_check_right[tmp_buffer_index] = + tmp_right; + } + Item *tmp_left = left_[tmp_buffer_index]->load(); + if (emptiness_check_left[tmp_buffer_index] != tmp_left) + { + empty = false; + emptiness_check_left[tmp_buffer_index] = + tmp_left; + } + } + } + if (result != NULL) + { + if (!timestamping_->is_later(timestamp, start_time)) + { + // The found item was timestamped after the start of the iteration, + // so it is save to remove it. + uint64_t expected = 0; + if (result->taken.load() == 0) + { + if (result->taken.compare_exchange_weak( + expected, 1)) + { + // Try to adjust the remove pointer. It does not matter if + // this CAS fails. + right_[buffer_index]->compare_exchange_weak( + old_right, (Item *)add_next_aba(result, old_right, 0)); + *element = result->data.load(); + return true; + } + } + } + } + + *empty_check = true; + return !empty; + } + }; + +}} // namespace cds::container + +#endif // #ifndef CDSLIB_CONTAINER_TS_DEQUE_BUFFER_H diff --git a/cds/container/ts_timestamp.h b/cds/container/ts_timestamp.h new file mode 100644 index 000000000..042a6e265 --- /dev/null +++ b/cds/container/ts_timestamp.h @@ -0,0 +1,182 @@ +#ifndef CDSLIB_CONTAINER_TS_TIMESTAMP_H +#define CDSLIB_CONTAINER_TS_TIMESTAMP_H + +#include +#include + +namespace cds { namespace container { + + using namespace cds::tshardwaretimestamp; + + class HardwareTimestamp + { + public: + inline void initialize(uint64_t delay, uint64_t num_threads) + { + } + + inline void init_sentinel(uint64_t *result) + { + result[0] = 0; + } + + inline void init_sentinel_atomic(std::atomic *result) + { + result[0].store(0); + } + + inline void init_top_atomic(std::atomic *result) + { + result[0].store(UINT64_MAX); + } + + inline void init_top(uint64_t *result) + { + result[0] = UINT64_MAX; + } + + inline void load_timestamp(uint64_t *result, std::atomic *source) + { + result[0] = source[0].load(); + } + + inline void set_timestamp(std::atomic *result) + { + result[0].store(platform::get_hwptime()); + } + + inline void read_time(uint64_t *result) + { + result[0] = platform::get_hwptime(); + } + + inline bool is_later(uint64_t *timestamp1, uint64_t *timestamp2) + { + return timestamp2[0] < timestamp1[0]; + } + }; + + class HardwareIntervalTimestamp + { + private: + uint64_t delay_; + + public: + inline void initialize(uint64_t delay, uint64_t num_threads) + { + delay_ = delay; + } + + inline void init_sentinel(uint64_t *result) + { + result[0] = 0; + result[1] = 0; + } + + inline void init_sentinel_atomic(std::atomic *result) + { + result[0].store(0); + result[1].store(0); + } + + inline void init_top_atomic(std::atomic *result) + { + result[0].store(UINT64_MAX); + result[1].store(UINT64_MAX); + } + + inline void init_top(uint64_t *result) + { + result[0] = UINT64_MAX; + result[1] = UINT64_MAX; + } + + inline void load_timestamp(uint64_t *result, std::atomic *source) + { + result[0] = source[0].load(); + result[1] = source[1].load(); + } + + inline void set_timestamp(std::atomic *result) + { + result[0].store(platform::get_hwptime()); + uint64_t wait = platform::get_hwtime() + delay_; + while (platform::get_hwtime() < wait) + { + } + result[1].store(platform::get_hwptime()); + } + + inline void read_time(uint64_t *result) + { + result[0] = platform::get_hwptime(); + result[1] = result[0]; + } + + inline bool is_later(uint64_t *timestamp1, uint64_t *timestamp2) + { + return timestamp2[1] < timestamp1[0]; + } + }; + + class AtomicCounterTimestamp + { + private: + std::atomic *clock_; + + public: + inline void initialize(uint64_t delay, uint64_t num_threads) + { + clock_ = new std::atomic(); + clock_->store(1); + } + + inline void init_sentinel(uint64_t *result) + { + result[0] = 0; + } + + inline void init_sentinel_atomic(std::atomic *result) + { + result[0].store(0); + } + + inline void init_top_atomic(std::atomic *result) + { + result[0].store(UINT64_MAX); + } + + inline void init_top(uint64_t *result) + { + result[0] = UINT64_MAX; + } + + inline void load_timestamp(uint64_t *result, std::atomic *source) + { + result[0] = source[0].load(); + } + + inline void set_timestamp(std::atomic *result) + { + result[0].store(clock_->fetch_add(1)); + } + + inline void set_timestamp_local(uint64_t *result) + { + result[0] = clock_->fetch_add(1); + } + + inline void read_time(uint64_t *result) + { + result[0] = clock_->load(); + } + + inline bool is_later(uint64_t *timestamp1, uint64_t *timestamp2) + { + return timestamp2[0] < timestamp1[0]; + } + }; + +}} // namespace cds::container + +#endif // #ifndef CDSLIB_CONTAINER_TS_TIMESTAMP_H diff --git a/test/stress/queue/pop.cpp b/test/stress/queue/pop.cpp index 5dbb72cd5..845c93b2d 100644 --- a/test/stress/queue/pop.cpp +++ b/test/stress/queue/pop.cpp @@ -149,6 +149,7 @@ namespace { CDSSTRESS_FCDeque( queue_pop ) CDSSTRESS_RWQueue( queue_pop ) CDSSTRESS_StdQueue( queue_pop ) + CDSSTRESS_TSDeque( queue_pop ) #undef CDSSTRESS_Queue_F #define CDSSTRESS_Queue_F( test_fixture, type_name ) \ diff --git a/test/stress/queue/push.cpp b/test/stress/queue/push.cpp index c2e80bba2..3b7b608e6 100644 --- a/test/stress/queue/push.cpp +++ b/test/stress/queue/push.cpp @@ -152,6 +152,7 @@ namespace { CDSSTRESS_FCDeque( queue_push ) CDSSTRESS_RWQueue( queue_push ) CDSSTRESS_StdQueue( queue_push ) + CDSSTRESS_TSDeque( queue_push ) #undef CDSSTRESS_Queue_F #define CDSSTRESS_Queue_F( test_fixture, type_name ) \ diff --git a/test/stress/queue/push_pop.cpp b/test/stress/queue/push_pop.cpp index 4803f5238..b855881d4 100644 --- a/test/stress/queue/push_pop.cpp +++ b/test/stress/queue/push_pop.cpp @@ -329,6 +329,7 @@ namespace { CDSSTRESS_FCDeque_HeavyValue( fc_with_heavy_value ) CDSSTRESS_RWQueue( simple_queue_push_pop ) CDSSTRESS_StdQueue( simple_queue_push_pop ) + CDSSTRESS_TSDeque( simple_queue_push_pop ) #undef CDSSTRESS_Queue_F #define CDSSTRESS_Queue_F( test_fixture, type_name ) \ diff --git a/test/stress/queue/queue_type.h b/test/stress/queue/queue_type.h index 4dd0f454b..79f5feb8c 100644 --- a/test/stress/queue/queue_type.h +++ b/test/stress/queue/queue_type.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include @@ -102,6 +104,60 @@ namespace queue { } }; + template + class TSDequeL: public cds::container::TSDeque + { + typedef cds::container::TSDeque base_class; + public: + TSDequeL() : base_class( 17, 0 ) + {} + + bool push( T const& v ) + { + return base_class::insert_left( v ); + } + bool enqueue( T const& v ) + { + return push( v ); + } + + bool pop( T& v ) + { + return base_class::remove_right( &v ); + } + bool deque( T& v ) + { + return pop(v); + } + }; + + template + class TSDequeR: public cds::container::TSDeque + { + typedef cds::container::TSDeque base_class; + public: + TSDequeR() : base_class( 17, 0 ) + {} + + bool push( T const& v ) + { + return base_class::insert_right( v ); + } + bool enqueue( T const& v ) + { + return push( v ); + } + + bool pop( T& v ) + { + return base_class::remove_left( &v ); + } + bool deque( T& v ) + { + return pop(v); + } + }; + } // namespace details namespace fc_details{ @@ -503,6 +559,20 @@ namespace fc_details{ typedef details::FCDequeR< Value, fc_details::traits_FCDeque_elimination, boost::container::deque > FCDequeR_boost_elimination; typedef details::FCDequeR< Value, fc_details::traits_FCDeque_elimination_stat, boost::container::deque > FCDequeR_boost_elimination_stat; + // TSDeque + struct traits_TSDeque_item_counter: + public cds::container::tsdeque::make_traits< + cds::opt::item_counter< cds::atomicity::cache_friendly_item_counter > + >::type + {}; + + typedef details::TSDequeL< Value, cds::container::HardwareTimestamp, traits_TSDeque_item_counter > TSDequeL_hardware_timestamp; + typedef details::TSDequeL< Value, cds::container::HardwareIntervalTimestamp, traits_TSDeque_item_counter > TSDequeL_hardware_interval_timestamp; + typedef details::TSDequeL< Value, cds::container::AtomicCounterTimestamp, traits_TSDeque_item_counter > TSDequeL_atomic_counter_timestamp; + typedef details::TSDequeR< Value, cds::container::HardwareTimestamp, traits_TSDeque_item_counter > TSDequeR_hardware_timestamp; + typedef details::TSDequeR< Value, cds::container::HardwareIntervalTimestamp, traits_TSDeque_item_counter > TSDequeR_hardware_interval_timestamp; + typedef details::TSDequeR< Value, cds::container::AtomicCounterTimestamp, traits_TSDeque_item_counter > TSDequeR_atomic_counter_timestamp; + // STL typedef StdQueue_deque StdQueue_deque_Spinlock; typedef StdQueue_list StdQueue_list_Spinlock; @@ -831,6 +901,10 @@ namespace cds_test { CDSSTRESS_FCQueue_F( test_fixture, FCDequeL_HeavyValue_stat ) \ CDSSTRESS_FCDeque_HeavyValue_1( test_fixture ) +#define CDSSTRESS_TSDeque( test_fixture ) \ + CDSSTRESS_Queue_F( test_fixture, TSDequeL_atomic_counter_timestamp ) \ + CDSSTRESS_Queue_F( test_fixture, TSDequeR_atomic_counter_timestamp ) + #define CDSSTRESS_RWQueue( test_fixture ) \ CDSSTRESS_Queue_F( test_fixture, RWQueue_Spin ) \ CDSSTRESS_Queue_F( test_fixture, RWQueue_mutex ) \ diff --git a/test/stress/queue/random.cpp b/test/stress/queue/random.cpp index 357737d9e..029cb9422 100644 --- a/test/stress/queue/random.cpp +++ b/test/stress/queue/random.cpp @@ -216,6 +216,7 @@ namespace { CDSSTRESS_FCDeque( queue_random ) CDSSTRESS_RWQueue( queue_random ) CDSSTRESS_StdQueue( queue_random ) + CDSSTRESS_TSDeque( queue_random ) #undef CDSSTRESS_Queue_F #define CDSSTRESS_Queue_F( test_fixture, type_name ) \ diff --git a/test/stress/stack/push.cpp b/test/stress/stack/push.cpp index ae582aae7..f895570d2 100644 --- a/test/stress/stack/push.cpp +++ b/test/stress/stack/push.cpp @@ -180,5 +180,6 @@ namespace { CDSSTRESS_FCStack( stack_push ) CDSSTRESS_FCDeque( stack_push ) CDSSTRESS_StdStack( stack_push ) + CDSSTRESS_TSDeque( stack_push ) } // namespace diff --git a/test/stress/stack/push_pop.cpp b/test/stress/stack/push_pop.cpp index 74d8b8968..e47754576 100644 --- a/test/stress/stack/push_pop.cpp +++ b/test/stress/stack/push_pop.cpp @@ -257,5 +257,6 @@ namespace { CDSSTRESS_FCStack( stack_push_pop ) CDSSTRESS_FCDeque( stack_push_pop ) CDSSTRESS_StdStack( stack_push_pop ) + CDSSTRESS_TSDeque( stack_push_pop ) } // namespace diff --git a/test/stress/stack/stack_type.h b/test/stress/stack/stack_type.h index 4e58955ba..5490fd338 100644 --- a/test/stress/stack/stack_type.h +++ b/test/stress/stack/stack_type.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -78,6 +80,44 @@ namespace stack { } }; + template + class TSDequeL: public cds::container::TSDeque + { + typedef cds::container::TSDeque base_class; + public: + TSDequeL() : base_class( 9, 0 ) + {} + + bool push( T const& v ) + { + return base_class::insert_left( v ); + } + + bool pop( T& v ) + { + return base_class::remove_left( &v ); + } + }; + + template + class TSDequeR: public cds::container::TSDeque + { + typedef cds::container::TSDeque base_class; + public: + TSDequeR() : base_class( 9, 0 ) + {} + + bool push( T const& v ) + { + return base_class::insert_right( v ); + } + + bool pop( T& v ) + { + return base_class::remove_right( &v ); + } + }; + template < typename T, typename Stack, typename Lock> class StdStack { @@ -344,7 +384,7 @@ namespace stack { typedef cds::container::FCStack< T, std::stack >, traits_FCStack_elimination > FCStack_list_elimination; typedef cds::container::FCStack< T, std::stack >, traits_FCStack_elimination_stat > FCStack_list_elimination_stat; - // FCDeque + // FCDeque struct traits_FCDeque_stat: public cds::container::fcdeque::make_traits< cds::opt::stat< cds::container::fcdeque::stat<> > @@ -380,6 +420,19 @@ namespace stack { typedef details::FCDequeR< T, traits_FCDeque_elimination > FCDequeR_elimination; typedef details::FCDequeR< T, traits_FCDeque_elimination_stat > FCDequeR_elimination_stat; + // TSDeque + struct traits_TSDeque_item_counter: + public cds::container::tsdeque::make_traits< + cds::opt::item_counter< cds::atomicity::cache_friendly_item_counter > + >::type + {}; + + typedef details::TSDequeL< T, cds::container::HardwareTimestamp, traits_TSDeque_item_counter > TSDequeL_hardware_timestamp; + typedef details::TSDequeL< T, cds::container::HardwareIntervalTimestamp, traits_TSDeque_item_counter > TSDequeL_hardware_interval_timestamp; + typedef details::TSDequeL< T, cds::container::AtomicCounterTimestamp, traits_TSDeque_item_counter > TSDequeL_atomic_counter_timestamp; + typedef details::TSDequeR< T, cds::container::HardwareTimestamp, traits_TSDeque_item_counter > TSDequeR_hardware_timestamp; + typedef details::TSDequeR< T, cds::container::HardwareIntervalTimestamp, traits_TSDeque_item_counter > TSDequeR_hardware_interval_timestamp; + typedef details::TSDequeR< T, cds::container::AtomicCounterTimestamp, traits_TSDeque_item_counter > TSDequeR_atomic_counter_timestamp; // std::stack typedef details::StdStack< T, std::stack< T >, std::mutex > StdStack_Deque_Mutex; @@ -532,6 +585,10 @@ namespace cds_test { CDSSTRESS_Stack_F( test_fixture, FCDequeR_elimination ) \ CDSSTRESS_Stack_F( test_fixture, FCDequeR_elimination_stat ) +#define CDSSTRESS_TSDeque( test_fixture ) \ + CDSSTRESS_Stack_F( test_fixture, TSDequeL_atomic_counter_timestamp ) \ + CDSSTRESS_Stack_F( test_fixture, TSDequeR_atomic_counter_timestamp ) + #define CDSSTRESS_StdStack( test_fixture ) \ CDSSTRESS_Stack_F( test_fixture, StdStack_Deque_Mutex ) \ CDSSTRESS_Stack_F( test_fixture, StdStack_Deque_Spin ) \ diff --git a/test/unit/deque/CMakeLists.txt b/test/unit/deque/CMakeLists.txt index cf8fb8076..6985edff5 100644 --- a/test/unit/deque/CMakeLists.txt +++ b/test/unit/deque/CMakeLists.txt @@ -3,6 +3,7 @@ set(PACKAGE_NAME unit-deque) set(CDSGTEST_DEQUE_SOURCES ../main.cpp fcdeque.cpp + ts_deque.cpp ) include_directories( @@ -12,4 +13,4 @@ include_directories( add_executable(${PACKAGE_NAME} ${CDSGTEST_DEQUE_SOURCES}) target_link_libraries(${PACKAGE_NAME} ${CDS_TEST_LIBRARIES}) -add_test(NAME ${PACKAGE_NAME} COMMAND ${PACKAGE_NAME} WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) \ No newline at end of file +add_test(NAME ${PACKAGE_NAME} COMMAND ${PACKAGE_NAME} WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) diff --git a/test/unit/deque/ts_deque.cpp b/test/unit/deque/ts_deque.cpp new file mode 100644 index 000000000..0d35ce765 --- /dev/null +++ b/test/unit/deque/ts_deque.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include + +namespace { + + class TSDeque: public ::testing::Test + { + protected: + template + void test( Deque& dq ) + { + size_t const c_nSize = 100; + + // insert_right/remove_right + for ( int i = 0; i < static_cast( c_nSize ); ++i ) { + EXPECT_TRUE( dq.insert_right( i )); + } + EXPECT_EQ( dq.size(), c_nSize ); + + size_t nCount = 0; + int val; + while ( !dq.empty()) { + EXPECT_TRUE( dq.remove_right( &val )); + ++nCount; + EXPECT_EQ( static_cast(c_nSize - nCount), val ); + } + EXPECT_EQ( nCount, c_nSize ); + + // insert_left/remove_left + for ( int i = 0; i < static_cast( c_nSize ); ++i ) + EXPECT_TRUE( dq.insert_left( i )); + EXPECT_EQ( dq.size(), c_nSize ); + + nCount = 0; + while ( !dq.empty()) { + EXPECT_TRUE( dq.remove_left( &val )); + ++nCount; + EXPECT_EQ( static_cast(c_nSize - nCount), val ); + } + EXPECT_EQ( nCount, c_nSize ); + + // insert_left/remove_right + for ( int i = 0; i < static_cast( c_nSize ); ++i ) + EXPECT_TRUE( dq.insert_left( i )); + EXPECT_EQ( dq.size(), c_nSize ); + + nCount = 0; + while ( !dq.empty()) { + EXPECT_TRUE( dq.remove_right( &val )); + EXPECT_EQ( static_cast( nCount ), val ); + ++nCount; + } + EXPECT_EQ( nCount, c_nSize ); + + // insert_right/remove_left + for ( int i = 0; i < static_cast( c_nSize ); ++i ) + EXPECT_TRUE( dq.insert_right( i )); + EXPECT_EQ( dq.size(), c_nSize ); + + nCount = 0; + while ( !dq.empty()) { + EXPECT_TRUE( dq.remove_left( &val )); + EXPECT_EQ( static_cast( nCount ), val ); + ++nCount; + } + EXPECT_EQ( nCount, c_nSize ); + + // clear + for ( int i = 0; i < static_cast( c_nSize ); ++i ) + EXPECT_TRUE( dq.insert_right( i )); + EXPECT_EQ( dq.size(), c_nSize ); + + EXPECT_FALSE( dq.empty()); + dq.clear(); + EXPECT_TRUE( dq.empty()); + } + }; + + #if defined(CDS_ts_hardwaretimestamp_hwptime_defined) && defined(CDS_ts_hardwaretimestamp_hwtime_defined) + + TEST_F( TSDeque, hardware_timestamping ) + { + if (cds::tshardwaretimestamp::platform::has_rdtscp() == 1) + { + typedef cds::container::TSDeque + >::type + > deque_type; + + deque_type dq(1, 0); + test( dq ); + } + } + + TEST_F( TSDeque, hardware_interval_timestamping ) + { + if (cds::tshardwaretimestamp::platform::has_rdtscp() == 1) + { + typedef cds::container::TSDeque + >::type + > deque_type; + + deque_type dq(1, 100000); + test( dq ); + } + } + + #endif + + TEST_F( TSDeque, atomic_counter_timestamping ) + { + typedef cds::container::TSDeque + >::type + > deque_type; + + deque_type dq(1, 0); + test( dq ); + } + +} // namespace