upd: [pmr] optimize allocator implementation

This commit is contained in:
mutouyun 2023-09-02 14:10:51 +08:00
parent 774a3c9af7
commit cd545653c4
13 changed files with 284 additions and 155 deletions

View File

@ -77,18 +77,15 @@ private:
}; };
data *init(index_t circ_size) noexcept { data *init(index_t circ_size) noexcept {
if (!data_allocator_) {
return nullptr;
}
void *data_ptr = nullptr; void *data_ptr = nullptr;
LIBIMP_TRY { LIBIMP_TRY {
data_ptr = data_allocator_.alloc(data::size_of(circ_size)); data_ptr = data_allocator_.allocate(data::size_of(circ_size));
if (data_ptr == nullptr) { if (data_ptr == nullptr) {
return nullptr; return nullptr;
} }
return ::LIBIMP::construct<data>(data_ptr, circ_size); return ::LIBIMP::construct<data>(data_ptr, circ_size);
} LIBIMP_CATCH(...) { } LIBIMP_CATCH(...) {
data_allocator_.dealloc(data_ptr, data::size_of(circ_size)); data_allocator_.deallocate(data_ptr, data::size_of(circ_size));
return nullptr; return nullptr;
} }
} }
@ -128,7 +125,7 @@ public:
if (valid()) { if (valid()) {
auto sz = data_->byte_size(); auto sz = data_->byte_size();
(void)::LIBIMP::destroy<data>(data_); (void)::LIBIMP::destroy<data>(data_);
data_allocator_.dealloc(data_, sz); data_allocator_.deallocate(data_, sz);
} }
} }
@ -148,7 +145,7 @@ public:
: data_model(default_circle_buffer_size) {} : data_model(default_circle_buffer_size) {}
bool valid() const noexcept { bool valid() const noexcept {
return (data_ != nullptr) && data_allocator_.valid(); return data_ != nullptr;
} }
explicit operator bool() const noexcept { explicit operator bool() const noexcept {

View File

@ -64,7 +64,7 @@ public:
using value_type = T; using value_type = T;
template <typename... A> template <typename... A>
element(A &&... args) element(A &&...args)
noexcept(noexcept(T{std::forward<A>(args)...})) noexcept(noexcept(T{std::forward<A>(args)...}))
: f_ct_{state::invalid_value} : f_ct_{state::invalid_value}
, data_{std::forward<A>(args)...} {} , data_{std::forward<A>(args)...} {}

View File

@ -44,8 +44,7 @@ class byte {
std::uint8_t bits_; std::uint8_t bits_;
public: public:
constexpr byte() noexcept byte() noexcept = default;
: byte(0) {}
template <typename T, typename = detail::is_integral<T>> template <typename T, typename = detail::is_integral<T>>
constexpr byte(T v) noexcept constexpr byte(T v) noexcept

View File

@ -23,7 +23,7 @@ LIBIMP_NAMESPACE_BEG_
*/ */
template <typename T, typename... A> template <typename T, typename... A>
auto construct(void *p, A &&... args) auto construct(void *p, A &&...args)
-> std::enable_if_t<::std::is_constructible<T, A...>::value, T *> { -> std::enable_if_t<::std::is_constructible<T, A...>::value, T *> {
#if defined(LIBIMP_CPP_20) #if defined(LIBIMP_CPP_20)
return std::construct_at(static_cast<T *>(p), std::forward<A>(args)...); return std::construct_at(static_cast<T *>(p), std::forward<A>(args)...);
@ -33,7 +33,7 @@ auto construct(void *p, A &&... args)
} }
template <typename T, typename... A> template <typename T, typename... A>
auto construct(void *p, A &&... args) auto construct(void *p, A &&...args)
-> std::enable_if_t<!::std::is_constructible<T, A...>::value, T *> { -> std::enable_if_t<!::std::is_constructible<T, A...>::value, T *> {
return ::new (p) T{std::forward<A>(args)...}; return ::new (p) T{std::forward<A>(args)...};
} }

View File

@ -122,7 +122,7 @@ public:
, level_limit_(level_limit) {} , level_limit_(level_limit) {}
template <typename... A> template <typename... A>
grip const &operator()(log::level l, A &&... args) const noexcept { grip const &operator()(log::level l, A &&...args) const noexcept {
if (underlyof(l) < underlyof(level_limit_)) { if (underlyof(l) < underlyof(level_limit_)) {
return *this; return *this;
} }

View File

@ -24,7 +24,7 @@ struct is_comfortable {
}; };
template <typename T, typename... A> template <typename T, typename... A>
auto make(A &&... args) -> std::enable_if_t<is_comfortable<T>::value, T *> { auto make(A &&...args) -> std::enable_if_t<is_comfortable<T>::value, T *> {
T *buf {}; T *buf {};
// construct an object using memory of a pointer // construct an object using memory of a pointer
construct<T>(&buf, std::forward<A>(args)...); construct<T>(&buf, std::forward<A>(args)...);
@ -42,7 +42,7 @@ auto clear(T *p) noexcept -> std::enable_if_t<is_comfortable<T>::value> {
} }
template <typename T, typename... A> template <typename T, typename... A>
auto make(A &&... args) -> std::enable_if_t<!is_comfortable<T>::value, T *> { auto make(A &&...args) -> std::enable_if_t<!is_comfortable<T>::value, T *> {
return new T{std::forward<A>(args)...}; return new T{std::forward<A>(args)...};
} }
@ -60,7 +60,7 @@ template <typename T>
class Obj { class Obj {
public: public:
template <typename... A> template <typename... A>
static T *make(A &&... args) { static T *make(A &&...args) {
return pimpl::make<T>(std::forward<A>(args)...); return pimpl::make<T>(std::forward<A>(args)...);
} }

View File

@ -142,7 +142,7 @@ public:
typename = not_match<result, A...>, typename = not_match<result, A...>,
typename = decltype(type_traits_t::init_code(std::declval<storage_t &>() typename = decltype(type_traits_t::init_code(std::declval<storage_t &>()
, std::declval<A>()...))> , std::declval<A>()...))>
result(A &&... args) noexcept { result(A &&...args) noexcept {
type_traits_t::init_code(code_, std::forward<A>(args)...); type_traits_t::init_code(code_, std::forward<A>(args)...);
} }
@ -171,7 +171,7 @@ public:
typename = not_match<result, A...>, typename = not_match<result, A...>,
typename = decltype(type_traits_t::init_code(std::declval<storage_t &>() typename = decltype(type_traits_t::init_code(std::declval<storage_t &>()
, std::declval<A>()...))> , std::declval<A>()...))>
result(A &&... args) noexcept { result(A &&...args) noexcept {
type_traits_t::init_code(code_, std::forward<A>(args)...); type_traits_t::init_code(code_, std::forward<A>(args)...);
} }

View File

@ -32,63 +32,54 @@ LIBPMR_NAMESPACE_BEG_
*/ */
class LIBIMP_EXPORT allocator { class LIBIMP_EXPORT allocator {
class holder_base { class holder_mr_base {
public: public:
virtual ~holder_base() noexcept = default; virtual ~holder_mr_base() noexcept = default;
virtual void *alloc(std::size_t) = 0; virtual void *alloc(std::size_t, std::size_t) const = 0;
virtual void dealloc(void *, std::size_t) = 0; virtual void dealloc(void *, std::size_t, std::size_t) const = 0;
virtual bool valid() const noexcept = 0;
}; };
class holder_null : public holder_base { template <typename MR, typename = bool>
public: class holder_mr;
void *alloc(std::size_t) override { return nullptr; }
void dealloc(void *, std::size_t) override {}
bool valid() const noexcept override { return false; }
};
template <typename MemRes, typename = bool>
class holder_memory_resource;
/**
* \brief A memory resource pointer holder class for type erasure.
* \tparam MR memory resource type
*/
template <typename MR>
class holder_memory_resource<MR, verify_memory_resource<MR>> : public holder_base {
MR *p_mem_res_;
public:
holder_memory_resource(MR *p_mr) noexcept
: p_mem_res_(p_mr) {}
void *alloc(std::size_t s) override {
return p_mem_res_->allocate(s);
}
void dealloc(void *p, std::size_t s) override {
p_mem_res_->deallocate(p, s);
}
bool valid() const noexcept override {
return p_mem_res_ != nullptr;
}
};
/** /**
* \brief An empty holding class used to calculate a reasonable memory size for the holder. * \brief An empty holding class used to calculate a reasonable memory size for the holder.
* \tparam MR cannot be converted to the type of memory resource * \tparam MR cannot be converted to the type of memory resource
*/ */
template <typename MR, typename U> template <typename MR, typename U>
class holder_memory_resource : public holder_null { class holder_mr : public holder_mr_base {
MR *p_dummy_; protected:
MR *res_;
public:
holder_mr(MR *p_mr) noexcept
: res_(p_mr) {}
}; };
using void_holder_type = holder_memory_resource<void>; /**
alignas(void_holder_type) std::array<::LIBIMP::byte, sizeof(void_holder_type)> holder_; * \brief A memory resource pointer holder class for type erasure.
* \tparam MR memory resource type
*/
template <typename MR>
class holder_mr<MR, verify_memory_resource<MR>> : public holder_mr<MR, void> {
public:
holder_mr(MR *p_mr) noexcept
: holder_mr<MR, void>{p_mr} {}
holder_base & get_holder() noexcept; void *alloc(std::size_t s, std::size_t a) const override {
holder_base const &get_holder() const noexcept; return res_->allocate(s, a);
}
void dealloc(void *p, std::size_t s, std::size_t a) const override {
res_->deallocate(p, s, a);
}
};
using void_holder_t = holder_mr<void *>;
alignas(void_holder_t) std::array<::LIBIMP::byte, sizeof(void_holder_t)> holder_;
holder_mr_base & get_holder() noexcept;
holder_mr_base const &get_holder() const noexcept;
public: public:
allocator() noexcept; allocator() noexcept;
@ -97,24 +88,25 @@ public:
allocator(allocator const &other) noexcept = default; allocator(allocator const &other) noexcept = default;
allocator &operator=(allocator const &other) & noexcept = default; allocator &operator=(allocator const &other) & noexcept = default;
allocator(allocator &&other) noexcept; allocator(allocator &&other) noexcept = default;
allocator &operator=(allocator &&other) & noexcept; allocator &operator=(allocator &&other) & noexcept = default;
/// \brief Constructs a allocator from a memory resource pointer /// \brief Constructs a allocator from a memory resource pointer
/// The lifetime of the pointer must be longer than that of allocator. /// The lifetime of the pointer must be longer than that of allocator.
template <typename T, verify_memory_resource<T> = true> template <typename T, verify_memory_resource<T> = true>
allocator(T *p_mr) : allocator() { allocator(T *p_mr) noexcept {
if (p_mr == nullptr) return; if (p_mr == nullptr) {
::LIBIMP::construct<holder_memory_resource<T>>(holder_.data(), p_mr); ::LIBIMP::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
return;
}
::LIBIMP::construct<holder_mr<T>>(holder_.data(), p_mr);
} }
void swap(allocator &other) noexcept; void swap(allocator &other) noexcept;
bool valid() const noexcept;
explicit operator bool() const noexcept;
/// \brief Allocate/deallocate memory. /// \brief Allocate/deallocate memory.
void *alloc(std::size_t s); void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const;
void dealloc(void *p, std::size_t s); void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const;
}; };
LIBPMR_NAMESPACE_END_ LIBPMR_NAMESPACE_END_

View File

@ -0,0 +1,122 @@
/**
* \file libpmr/small_storage.h
* \author mutouyun (orz@orzz.org)
* \brief Unified SSO (Small Size Optimization) holder base.
* \date 2023-09-02
*/
#pragma once
#include <type_traits>
#include <array>
#include <typeinfo>
#include <utility>
#include <cstddef>
#include "libimp/export.h"
#include "libimp/construct.h"
#include "libimp/byte.h"
#include "libimp/generic.h"
#include "libpmr/def.h"
LIBPMR_NAMESPACE_BEG_
class LIBIMP_EXPORT allocator;
/**
* \class holder_base
* \brief Data holder base class.
*/
class holder_base {
public:
virtual ~holder_base() noexcept = default;
virtual bool valid() const noexcept = 0;
virtual std::type_info const &type() const noexcept = 0;
virtual std::size_t sizeof_type() const noexcept = 0;
virtual std::size_t count() const noexcept = 0;
virtual std::size_t size () const noexcept = 0;
virtual void *get() noexcept = 0;
virtual void const *get() const noexcept = 0;
virtual void move_to(allocator const &, void *) noexcept = 0;
virtual void copy_to(allocator const &, void *) const noexcept(false) = 0;
};
/**
* \class holder_null
* \brief A holder implementation that does not hold any data objects.
*/
class holder_null : public holder_base {
public:
bool valid() const noexcept override { return false; }
std::type_info const &type() const noexcept override { return typeid(nullptr);}
std::size_t sizeof_type() const noexcept override { return 0; }
std::size_t count() const noexcept override { return 0; }
std::size_t size () const noexcept override { return 0; }
void *get() noexcept override { return nullptr; }
void const *get() const noexcept override { return nullptr; }
/// \brief The passed destination pointer is never null,
/// and the memory to which it points is uninitialized.
void move_to(allocator const &, void *) noexcept override {}
void copy_to(allocator const &, void *) const noexcept(false) override {}
};
template <typename Value, bool/*is on stack*/>
class holder;
/**
* \class template <typename Value> holder<Value, true>
* \brief A holder implementation that holds a data object if type `Value` on stack memory.
* \tparam Value The storage type of the holder.
*/
template <typename Value>
class holder<Value, true> : public holder_base {
alignas(alignof(Value)) std::array<::LIBIMP::byte, sizeof(Value)> value_storage_;
public:
holder() = default; // uninitialized
template <typename... A>
holder(::LIBIMP::in_place_t, A &&...args) {
::LIBIMP::construct<Value>(value_storage_.data(), std::forward<A>(args)...);
}
bool valid() const noexcept override {
return true;
}
std::type_info const &type() const noexcept override {
return typeid(Value);
}
std::size_t sizeof_type() const noexcept override {
return sizeof(Value);
}
std::size_t count() const noexcept override {
return 1;
}
std::size_t size() const noexcept override {
return value_storage_.size();
}
void *get() noexcept override {
return value_storage_.data();
}
void const *get() const noexcept override {
return value_storage_.data();
}
void move_to(allocator const &, void *p) noexcept override {
::LIBIMP::construct<holder>(p, ::LIBIMP::in_place, std::move(*static_cast<Value *>(get())));
}
void copy_to(allocator const &, void *p) const noexcept(false) override {
::LIBIMP::construct<holder>(p, ::LIBIMP::in_place, *static_cast<Value *>(get()));
}
};
LIBPMR_NAMESPACE_END_

View File

@ -0,0 +1,35 @@
/**
* \file libpmr/small_storage.h
* \author mutouyun (orz@orzz.org)
* \brief Unified SSO (Small Size Optimization).
* \date 2023-09-02
*/
#pragma once
#include <type_traits>
#include <array>
#include <typeinfo>
#include <utility>
#include <memory>
#include <cstddef>
#include "libimp/export.h"
#include "libimp/construct.h"
#include "libimp/byte.h"
#include "libimp/generic.h"
#include "libpmr/def.h"
#include "libpmr/holder_base.h"
LIBPMR_NAMESPACE_BEG_
/**
* \class template <typename Value> holder<Value, false>
* \brief A holder implementation that holds a data object if type `Value` on heap memory.
* \tparam Value The storage type of the holder.
*/
template <typename Value>
class holder<Value, false> : public holder_base {
};
LIBPMR_NAMESPACE_END_

View File

@ -5,54 +5,31 @@
LIBPMR_NAMESPACE_BEG_ LIBPMR_NAMESPACE_BEG_
allocator::holder_base &allocator::get_holder() noexcept { allocator::holder_mr_base &allocator::get_holder() noexcept {
return *reinterpret_cast<holder_base *>(holder_.data()); return *reinterpret_cast<holder_mr_base *>(holder_.data());
} }
allocator::holder_base const &allocator::get_holder() const noexcept { allocator::holder_mr_base const &allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_base const *>(holder_.data()); return *reinterpret_cast<holder_mr_base const *>(holder_.data());
} }
allocator::allocator() noexcept { allocator::allocator() noexcept
::LIBIMP::construct<holder_null>(holder_.data()); : allocator(new_delete_resource::get()) {}
}
allocator::~allocator() noexcept { allocator::~allocator() noexcept {
::LIBIMP::destroy(&get_holder()); ::LIBIMP::destroy(&get_holder());
} }
allocator::allocator(allocator &&other) noexcept
: allocator(other) /*copy*/ {
::LIBIMP::construct<holder_null>(other.holder_.data());
}
allocator &allocator::operator=(allocator &&other) & noexcept {
if (this == &other) return *this;
this->holder_ = other.holder_;
::LIBIMP::construct<holder_null>(other.holder_.data());
return *this;
}
void allocator::swap(allocator &other) noexcept { void allocator::swap(allocator &other) noexcept {
std::swap(this->holder_, other.holder_); std::swap(this->holder_, other.holder_);
} }
bool allocator::valid() const noexcept { void *allocator::allocate(std::size_t s, std::size_t a) const {
return get_holder().valid(); return get_holder().alloc(s, a);
} }
allocator::operator bool() const noexcept { void allocator::deallocate(void *p, std::size_t s, std::size_t a) const {
return valid(); get_holder().dealloc(p, s, a);
}
void *allocator::alloc(std::size_t s) {
if (!valid()) return nullptr;
return get_holder().alloc(s);
}
void allocator::dealloc(void *p, std::size_t s) {
if (!valid()) return;
get_holder().dealloc(p, s);
} }
LIBPMR_NAMESPACE_END_ LIBPMR_NAMESPACE_END_

View File

@ -8,71 +8,71 @@
TEST(allocator, construct) { TEST(allocator, construct) {
pmr::allocator alc; pmr::allocator alc;
EXPECT_FALSE(alc.valid()); SUCCEED();
EXPECT_FALSE(alc);
} }
TEST(allocator, construct_with_memory_resource) { TEST(allocator, construct_value_initialization) {
pmr::new_delete_resource mem_res; pmr::allocator alc{};
pmr::allocator alc {&mem_res}; auto p = alc.allocate(128);
EXPECT_TRUE(alc.valid());
EXPECT_TRUE(alc);
auto p = alc.alloc(128);
EXPECT_NE(p, nullptr); EXPECT_NE(p, nullptr);
EXPECT_NO_THROW(alc.dealloc(p, 128)); EXPECT_NO_THROW(alc.deallocate(p, 128));
} }
namespace {
class dummy_resource {
public:
void *allocate(std::size_t, std::size_t = 0) noexcept {
return nullptr;
}
void deallocate(void *, std::size_t, std::size_t = 0) noexcept {
return;
}
};
} // namespace
TEST(allocator, construct_copy_move) { TEST(allocator, construct_copy_move) {
pmr::new_delete_resource mem_res; pmr::new_delete_resource mem_res;
dummy_resource dummy_res;
pmr::allocator alc1{&mem_res}, alc2{&dummy_res};
auto p = alc1.allocate(128);
ASSERT_NE(p, nullptr);
ASSERT_NO_THROW(alc1.deallocate(p, 128));
ASSERT_EQ(alc2.allocate(128), nullptr);
pmr::allocator alc1 {&mem_res}, alc2; pmr::allocator alc3{alc1}, alc4{alc2}, alc5{std::move(alc1)};
EXPECT_TRUE (alc1.valid());
EXPECT_TRUE (alc1);
EXPECT_FALSE(alc2.valid());
EXPECT_FALSE(alc2);
pmr::allocator alc3 {alc1}, alc4{alc2}, alc5 {std::move(alc1)}; p = alc3.allocate(128);
EXPECT_TRUE (alc3.valid()); ASSERT_NE(p, nullptr);
EXPECT_TRUE (alc3); ASSERT_NO_THROW(alc3.deallocate(p, 128));
EXPECT_FALSE(alc4.valid());
EXPECT_FALSE(alc4); ASSERT_EQ(alc4.allocate(128), nullptr);
EXPECT_TRUE (alc5.valid());
EXPECT_TRUE (alc5); p = alc5.allocate(128);
EXPECT_FALSE(alc1.valid()); ASSERT_NE(p, nullptr);
EXPECT_FALSE(alc1); ASSERT_NO_THROW(alc5.deallocate(p, 128));
} }
TEST(allocator, swap) { TEST(allocator, swap) {
pmr::new_delete_resource mem_res; pmr::new_delete_resource mem_res;
dummy_resource dummy_res;
pmr::allocator alc1 {&mem_res}, alc2; pmr::allocator alc1{&mem_res}, alc2{&dummy_res};
EXPECT_TRUE (alc1.valid());
EXPECT_TRUE (alc1);
EXPECT_FALSE(alc2.valid());
EXPECT_FALSE(alc2);
alc1.swap(alc2); alc1.swap(alc2);
EXPECT_FALSE(alc1.valid()); auto p = alc2.allocate(128);
EXPECT_FALSE(alc1); ASSERT_NE(p, nullptr);
EXPECT_TRUE (alc2.valid()); ASSERT_NO_THROW(alc2.deallocate(p, 128));
EXPECT_TRUE (alc2); ASSERT_EQ(alc1.allocate(128), nullptr);
} }
TEST(allocator, invalid_alloc_free) { TEST(allocator, invalid_alloc_free) {
pmr::new_delete_resource mem_res; pmr::allocator alc1;
EXPECT_EQ(alc1.allocate(0), nullptr);
EXPECT_NO_THROW(alc1.deallocate(nullptr, 128));
EXPECT_NO_THROW(alc1.deallocate(nullptr, 0));
EXPECT_NO_THROW(alc1.deallocate(&alc1, 0));
}
pmr::allocator alc1 {&mem_res}, alc2; TEST(allocator, sizeof) {
EXPECT_EQ(alc1.alloc(0), nullptr); EXPECT_EQ(sizeof(pmr::allocator), sizeof(void *) * 2);
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.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.dealloc(&alc1, sizeof(alc1)));
}

View File

@ -0,0 +1,7 @@
#include "gtest/gtest.h"
#include "libpmr/small_storage.h"
TEST(small_storage, construct) {
}