add: [concur] queue and simple ut

This commit is contained in:
mutouyun 2023-05-20 16:58:42 +08:00
parent 934bb31778
commit 426b7c3768
6 changed files with 208 additions and 27 deletions

View File

@ -6,10 +6,14 @@
*/
#pragma once
#include <atomic>
#include <array>
#include <cstddef>
#include <cstdint>
#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 <typename T, typename PRelationT, typename CRelationT>
template <typename T, typename PRelationT = relation::multi
, typename CRelationT = relation::multi>
class queue {
public:
using producer_relation_t = PRelationT;
using consumer_relation_t = CRelationT;
using model_type = prod_cons<trans::unicast, producer_relation_t, consumer_relation_t>;
using value_type = T;
using size_type = std::int64_t;
private:
struct data {
model_type model_;
typename concur::traits<model_type>::header header_;
element<value_type> elements_start_;
::LIBIMP::aligned<element<value_type>> elements_start_;
template <typename U>
data(U &&model) noexcept
: header_(std::forward<U>(model)) {}
: header_(std::forward<U>(model)) {
auto elements = this->elements();
decltype(elements)::size_type i = 0;
LIBIMP_TRY {
for (; i < elements.size(); ++i) {
(void)::LIBIMP::construct<element<value_type>>(&elements[i]);
}
} LIBIMP_CATCH(...) {
for (decltype(i) k = 0; k < i; ++k) {
(void)::LIBIMP::destroy<element<value_type>>(&elements[k]);
}
}
}
~data() noexcept {
for (auto &elem : this->elements()) {
(void)::LIBIMP::destroy<element<value_type>>(&elem);
}
}
static std::size_t size_of(index_t circ_size) noexcept {
return sizeof(struct data) + ( (circ_size - 1) * sizeof(element<value_type>) );
}
std::size_t byte_size() const noexcept {
return size_of(header_.circ_size);
}
/// \brief element<value_type> elements[0];
::LIBIMP::span<element<value_type>> 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<value_type>));
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_type> size_;
data *data_;
typename concur::traits<model_type>::context context_;
public:
queue() = default;
queue(queue const &) = delete;
queue(queue &&) = delete;
queue &operator=(queue const &) = delete;
queue &operator=(queue &&) = delete;
template <typename MR, ::LIBPMR::verify_memory_resource<T> = 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_);
data_allocator_.dealloc(data_, sz);
}
}
template <typename MR, ::LIBPMR::verify_memory_resource<MR> = true>
explicit queue(index_t circ_size, MR *memory_resource) noexcept
: data_allocator_(memory_resource)
, data_(init(circ_size)) {}
template <typename MR, ::LIBPMR::verify_memory_resource<T> = true>
template <typename MR, ::LIBPMR::verify_memory_resource<MR> = 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 <typename U>
bool push(U &&value) noexcept {
if (!valid()) return false;
return data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward<U>(value));
if (!data_->model_.enqueue(data_->elements(), data_->header_, context_, std::forward<U>(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;
}
};

62
include/libimp/aligned.h Normal file
View File

@ -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 <array>
#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 <typename T, std::size_t AlignT = alignof(T)>
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<T *>(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<const T *>(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_

View File

@ -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_

View File

@ -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_

View File

@ -2,3 +2,61 @@
#include "gtest/gtest.h"
#include "libconcur/queue.h"
using namespace concur;
TEST(queue, construct) {
using queue_t = queue<int>;
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<int>;
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);
}
}

View File

@ -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)));
}