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 {
if (!data_allocator_) {
return nullptr;
}
void *data_ptr = nullptr;
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) {
return nullptr;
}
return ::LIBIMP::construct<data>(data_ptr, circ_size);
} LIBIMP_CATCH(...) {
data_allocator_.dealloc(data_ptr, data::size_of(circ_size));
data_allocator_.deallocate(data_ptr, data::size_of(circ_size));
return nullptr;
}
}
@ -128,7 +125,7 @@ public:
if (valid()) {
auto sz = data_->byte_size();
(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) {}
bool valid() const noexcept {
return (data_ != nullptr) && data_allocator_.valid();
return data_ != nullptr;
}
explicit operator bool() const noexcept {

View File

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

View File

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

View File

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

View File

@ -122,7 +122,7 @@ public:
, level_limit_(level_limit) {}
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_)) {
return *this;
}

View File

@ -24,7 +24,7 @@ struct is_comfortable {
};
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 {};
// construct an object using memory of a pointer
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>
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)...};
}
@ -60,7 +60,7 @@ template <typename T>
class Obj {
public:
template <typename... A>
static T *make(A &&... args) {
static T *make(A &&...args) {
return pimpl::make<T>(std::forward<A>(args)...);
}

View File

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

View File

@ -32,63 +32,54 @@ LIBPMR_NAMESPACE_BEG_
*/
class LIBIMP_EXPORT allocator {
class holder_base {
class holder_mr_base {
public:
virtual ~holder_base() noexcept = default;
virtual void *alloc(std::size_t) = 0;
virtual void dealloc(void *, std::size_t) = 0;
virtual bool valid() const noexcept = 0;
virtual ~holder_mr_base() noexcept = default;
virtual void *alloc(std::size_t, std::size_t) const = 0;
virtual void dealloc(void *, std::size_t, std::size_t) const = 0;
};
class holder_null : public holder_base {
public:
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;
}
};
template <typename MR, typename = bool>
class holder_mr;
/**
* \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
*/
template <typename MR, typename U>
class holder_memory_resource : public holder_null {
MR *p_dummy_;
class holder_mr : public holder_mr_base {
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;
holder_base const &get_holder() const noexcept;
void *alloc(std::size_t s, std::size_t a) const override {
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:
allocator() noexcept;
@ -97,24 +88,25 @@ public:
allocator(allocator const &other) noexcept = default;
allocator &operator=(allocator const &other) & noexcept = default;
allocator(allocator &&other) noexcept;
allocator &operator=(allocator &&other) & noexcept;
allocator(allocator &&other) noexcept = default;
allocator &operator=(allocator &&other) & noexcept = default;
/// \brief Constructs a allocator from a memory resource pointer
/// The lifetime of the pointer must be longer than that of allocator.
template <typename T, verify_memory_resource<T> = true>
allocator(T *p_mr) : allocator() {
if (p_mr == nullptr) return;
::LIBIMP::construct<holder_memory_resource<T>>(holder_.data(), p_mr);
allocator(T *p_mr) noexcept {
if (p_mr == nullptr) {
::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;
bool valid() const noexcept;
explicit operator bool() const noexcept;
/// \brief Allocate/deallocate memory.
void *alloc(std::size_t s);
void dealloc(void *p, std::size_t s);
void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const;
void deallocate(void *p, std::size_t s, std::size_t = alignof(std::max_align_t)) const;
};
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_
allocator::holder_base &allocator::get_holder() noexcept {
return *reinterpret_cast<holder_base *>(holder_.data());
allocator::holder_mr_base &allocator::get_holder() noexcept {
return *reinterpret_cast<holder_mr_base *>(holder_.data());
}
allocator::holder_base const &allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_base const *>(holder_.data());
allocator::holder_mr_base const &allocator::get_holder() const noexcept {
return *reinterpret_cast<holder_mr_base const *>(holder_.data());
}
allocator::allocator() noexcept {
::LIBIMP::construct<holder_null>(holder_.data());
}
allocator::allocator() noexcept
: allocator(new_delete_resource::get()) {}
allocator::~allocator() noexcept {
::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 {
std::swap(this->holder_, other.holder_);
}
bool allocator::valid() const noexcept {
return get_holder().valid();
void *allocator::allocate(std::size_t s, std::size_t a) const {
return get_holder().alloc(s, a);
}
allocator::operator bool() const noexcept {
return valid();
}
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);
void allocator::deallocate(void *p, std::size_t s, std::size_t a) const {
get_holder().dealloc(p, s, a);
}
LIBPMR_NAMESPACE_END_

View File

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

View File

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