From 426b7c37684d0f98d82d9587e5f462ab73d9e8fc Mon Sep 17 00:00:00 2001 From: mutouyun Date: Sat, 20 May 2023 16:58:42 +0800 Subject: [PATCH] add: [concur] queue and simple ut --- include/libconcur/queue.h | 87 ++++++++++++++++++++++++++----- include/libimp/aligned.h | 62 ++++++++++++++++++++++ include/libpmr/allocator.h | 8 +-- src/libpmr/allocator.cpp | 4 +- test/concur/test_concur_queue.cpp | 58 +++++++++++++++++++++ test/pmr/test_pmr_allocator.cpp | 16 +++--- 6 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 include/libimp/aligned.h diff --git a/include/libconcur/queue.h b/include/libconcur/queue.h index 392c10c..25cc1ec 100644 --- a/include/libconcur/queue.h +++ b/include/libconcur/queue.h @@ -6,10 +6,14 @@ */ #pragma once +#include +#include #include +#include #include "libimp/construct.h" #include "libimp/detect_plat.h" +#include "libimp/aligned.h" #include "libpmr/allocator.h" #include "libpmr/memory_resource.h" @@ -19,27 +23,55 @@ LIBCONCUR_NAMESPACE_BEG_ -template +template class queue { public: using producer_relation_t = PRelationT; using consumer_relation_t = CRelationT; using model_type = prod_cons; using value_type = T; + using size_type = std::int64_t; private: struct data { model_type model_; typename concur::traits::header header_; - element elements_start_; + ::LIBIMP::aligned> elements_start_; template data(U &&model) noexcept - : header_(std::forward(model)) {} + : header_(std::forward(model)) { + auto elements = this->elements(); + decltype(elements)::size_type i = 0; + LIBIMP_TRY { + for (; i < elements.size(); ++i) { + (void)::LIBIMP::construct>(&elements[i]); + } + } LIBIMP_CATCH(...) { + for (decltype(i) k = 0; k < i; ++k) { + (void)::LIBIMP::destroy>(&elements[k]); + } + } + } + + ~data() noexcept { + for (auto &elem : this->elements()) { + (void)::LIBIMP::destroy>(&elem); + } + } + + static std::size_t size_of(index_t circ_size) noexcept { + return sizeof(struct data) + ( (circ_size - 1) * sizeof(element) ); + } + + std::size_t byte_size() const noexcept { + return size_of(header_.circ_size); + } /// \brief element elements[0]; ::LIBIMP::span> elements() noexcept { - return {&elements_start_, header_.circ_size}; + return {elements_start_.ptr(), header_.circ_size}; } }; @@ -48,7 +80,7 @@ private: return nullptr; } LIBIMP_TRY { - auto data_ptr = data_allocator_.allocate(sizeof(data) + (circ_size - 1) * sizeof(element)); + auto data_ptr = data_allocator_.alloc(data::size_of(circ_size)); if (data_ptr == nullptr) { return nullptr; } @@ -59,43 +91,72 @@ private: } ::LIBPMR::allocator data_allocator_; + std::atomic size_; data *data_; typename concur::traits::context context_; public: - queue() = default; queue(queue const &) = delete; queue(queue &&) = delete; queue &operator=(queue const &) = delete; queue &operator=(queue &&) = delete; - template = true> - explicit queue(index_t circ_size = default_circle_buffer_size, - MR *memory_resource = ::LIBPMR::new_delete_resource::get()) noexcept + ~queue() noexcept { + if (valid()) { + auto sz = data_->byte_size(); + (void)::LIBIMP::destroy(data_); + data_allocator_.dealloc(data_, sz); + } + } + + template = true> + explicit queue(index_t circ_size, MR *memory_resource) noexcept : data_allocator_(memory_resource) , data_(init(circ_size)) {} - template = true> + template = true> explicit queue(MR *memory_resource) noexcept : queue(default_circle_buffer_size, memory_resource) {} + explicit queue(index_t circ_size) noexcept + : queue(circ_size, ::LIBPMR::new_delete_resource::get()) {} + + queue() noexcept + : queue(default_circle_buffer_size) {} + bool valid() const noexcept { - return data_ != nullptr; + return (data_ != nullptr) && data_allocator_.valid(); } explicit operator bool() const noexcept { return valid(); } + size_type approx_size() const noexcept { + return size_.load(std::memory_order_relaxed); + } + + bool empty() const noexcept { + return !valid() || (approx_size() == 0); + } + template bool push(U &&value) noexcept { if (!valid()) return false; - return data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward(value)); + if (!data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward(value))) { + return false; + } + size_.fetch_add(1, std::memory_order_relaxed); + return true; } bool pop(value_type &value) noexcept { if (!valid()) return false; - return data_->model_.dequeue(data_->elements(), data_->header_, context_, value); + if (!data_->model_.dequeue(data_->elements(), data_->header_, context_, value)) { + return false; + } + size_.fetch_sub(1, std::memory_order_relaxed); + return true; } }; diff --git a/include/libimp/aligned.h b/include/libimp/aligned.h new file mode 100644 index 0000000..4514b4c --- /dev/null +++ b/include/libimp/aligned.h @@ -0,0 +1,62 @@ +/** + * \file libimp/aligned.h + * \author mutouyun (orz@orzz.org) + * \brief Defines the type suitable for use as uninitialized storage for types of given type. + * \date 2023-05-20 + */ +#pragma once + +#include + +#include "libimp/def.h" +#include "libimp/byte.h" + +LIBIMP_NAMESPACE_BEG_ + +/** + * \brief The type suitable for use as uninitialized storage for types of given type. + * std::aligned_storage is deprecated in C++23, so we define our own. + * \tparam T The type to be aligned. + * \tparam AlignT The alignment of the type. + * \see https://en.cppreference.com/w/cpp/types/aligned_storage + * https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1413r3.pdf + */ +template +class aligned { + alignas(AlignT) std::array<::LIBIMP::byte, sizeof(T)> storage_; + +public: + /** + * \brief Returns a pointer to the aligned storage. + * \return A pointer to the aligned storage. + */ + T *ptr() noexcept { + return reinterpret_cast(storage_.data()); + } + + /** + * \brief Returns a pointer to the aligned storage. + * \return A pointer to the aligned storage. + */ + T const *ptr() const noexcept { + return reinterpret_cast(storage_.data()); + } + + /** + * \brief Returns a reference to the aligned storage. + * \return A reference to the aligned storage. + */ + T &ref() noexcept { + return *ptr(); + } + + /** + * \brief Returns a reference to the aligned storage. + * \return A reference to the aligned storage. + */ + T const &ref() const noexcept { + return *ptr(); + } +}; + +LIBIMP_NAMESPACE_END_ diff --git a/include/libpmr/allocator.h b/include/libpmr/allocator.h index 51a3955..fb61efe 100644 --- a/include/libpmr/allocator.h +++ b/include/libpmr/allocator.h @@ -36,14 +36,14 @@ class LIBIMP_EXPORT allocator { public: virtual ~holder_base() noexcept = default; virtual void *alloc(std::size_t) = 0; - virtual void free (void *, std::size_t) = 0; + virtual void dealloc(void *, std::size_t) = 0; virtual bool valid() const noexcept = 0; }; class holder_null : public holder_base { public: void *alloc(std::size_t) override { return nullptr; } - void free (void *, std::size_t) override {} + void dealloc(void *, std::size_t) override {} bool valid() const noexcept override { return false; } }; @@ -66,7 +66,7 @@ class LIBIMP_EXPORT allocator { return p_mem_res_->allocate(s); } - void free(void *p, std::size_t s) override { + void dealloc(void *p, std::size_t s) override { p_mem_res_->deallocate(p, s); } @@ -114,7 +114,7 @@ public: /// \brief Allocate/deallocate memory. void *alloc(std::size_t s); - void free (void *p, std::size_t s); + void dealloc(void *p, std::size_t s); }; LIBPMR_NAMESPACE_END_ diff --git a/src/libpmr/allocator.cpp b/src/libpmr/allocator.cpp index c83147e..4760347 100644 --- a/src/libpmr/allocator.cpp +++ b/src/libpmr/allocator.cpp @@ -50,9 +50,9 @@ void *allocator::alloc(std::size_t s) { return get_holder().alloc(s); } -void allocator::free(void *p, std::size_t s) { +void allocator::dealloc(void *p, std::size_t s) { if (!valid()) return; - get_holder().free(p, s); + get_holder().dealloc(p, s); } LIBPMR_NAMESPACE_END_ diff --git a/test/concur/test_concur_queue.cpp b/test/concur/test_concur_queue.cpp index e05c1b1..7444c15 100644 --- a/test/concur/test_concur_queue.cpp +++ b/test/concur/test_concur_queue.cpp @@ -2,3 +2,61 @@ #include "gtest/gtest.h" #include "libconcur/queue.h" + +using namespace concur; + +TEST(queue, construct) { + using queue_t = queue; + queue_t q1; + EXPECT_TRUE(q1.valid()); + EXPECT_TRUE(q1.empty()); + EXPECT_EQ(q1.approx_size(), 0); +} + +TEST(queue, push_pop) { + using queue_t = queue; + queue_t q1; + EXPECT_TRUE(q1.valid()); + EXPECT_TRUE(q1.empty()); + EXPECT_EQ(q1.approx_size(), 0); + + EXPECT_TRUE(q1.push(1)); + EXPECT_FALSE(q1.empty()); + EXPECT_EQ(q1.approx_size(), 1); + + int value; + EXPECT_TRUE(q1.pop(value)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(q1.empty()); + EXPECT_EQ(q1.approx_size(), 0); + + int count = 0; + auto push = [&q1, &count](int i) { + EXPECT_TRUE(q1.push(i)); + EXPECT_FALSE(q1.empty()); + ++count; + EXPECT_EQ(q1.approx_size(), count); + }; + auto pop = [&q1, &count](int i) { + int value; + EXPECT_TRUE(q1.pop(value)); + EXPECT_EQ(value, i); + --count; + EXPECT_EQ(q1.approx_size(), count); + }; + + for (int i = 0; i < 1000; ++i) { + push(i); + } + for (int i = 0; i < 1000; ++i) { + pop(i); + } + + for (int i = 0; i < default_circle_buffer_size; ++i) { + push(i); + } + EXPECT_FALSE(q1.push(65536)); + for (int i = 0; i < default_circle_buffer_size; ++i) { + pop(i); + } +} \ No newline at end of file diff --git a/test/pmr/test_pmr_allocator.cpp b/test/pmr/test_pmr_allocator.cpp index 2860979..a1d2368 100644 --- a/test/pmr/test_pmr_allocator.cpp +++ b/test/pmr/test_pmr_allocator.cpp @@ -20,7 +20,7 @@ TEST(allocator, construct_with_memory_resource) { auto p = alc.alloc(128); EXPECT_NE(p, nullptr); - EXPECT_NO_THROW(alc.free(p, 128)); + EXPECT_NO_THROW(alc.dealloc(p, 128)); } TEST(allocator, construct_copy_move) { @@ -64,15 +64,15 @@ TEST(allocator, invalid_alloc_free) { pmr::allocator alc1 {&mem_res}, alc2; EXPECT_EQ(alc1.alloc(0), nullptr); - EXPECT_NO_THROW(alc1.free(nullptr, 128)); - EXPECT_NO_THROW(alc1.free(nullptr, 0)); - EXPECT_NO_THROW(alc1.free(&alc1, 0)); + EXPECT_NO_THROW(alc1.dealloc(nullptr, 128)); + EXPECT_NO_THROW(alc1.dealloc(nullptr, 0)); + EXPECT_NO_THROW(alc1.dealloc(&alc1, 0)); EXPECT_EQ(alc2.alloc(0), nullptr); - EXPECT_NO_THROW(alc2.free(nullptr, 128)); - EXPECT_NO_THROW(alc2.free(nullptr, 0)); - EXPECT_NO_THROW(alc2.free(&alc1, 0)); + EXPECT_NO_THROW(alc2.dealloc(nullptr, 128)); + EXPECT_NO_THROW(alc2.dealloc(nullptr, 0)); + EXPECT_NO_THROW(alc2.dealloc(&alc1, 0)); EXPECT_EQ(alc2.alloc(1024), nullptr); - EXPECT_NO_THROW(alc2.free(&alc1, sizeof(alc1))); + EXPECT_NO_THROW(alc2.dealloc(&alc1, sizeof(alc1))); } \ No newline at end of file