From a1f858f560344cda56267a3c2bdf17bedbdb9649 Mon Sep 17 00:00:00 2001 From: mutouyun Date: Sun, 9 Mar 2025 18:02:11 +0800 Subject: [PATCH] Refactoring the generic memory allocator --- include/libipc/imp/generic.h | 8 +- include/libipc/mem/container_allocator.h | 20 ++- include/libipc/mem/new.h | 169 ++++++----------------- src/libipc/mem/new.cpp | 121 ++++++++++++++++ src/libipc/utility/utility.h | 12 -- test/mem/test_mem_new.cpp | 13 -- 6 files changed, 185 insertions(+), 158 deletions(-) create mode 100644 src/libipc/mem/new.cpp diff --git a/include/libipc/imp/generic.h b/include/libipc/imp/generic.h index 21f8a6e..c3b63ed 100644 --- a/include/libipc/imp/generic.h +++ b/include/libipc/imp/generic.h @@ -285,15 +285,17 @@ namespace detail_horrible_cast { template union temp { - std::decay_t in; + U in; T out; }; } // namespace detail_horrible_cast template -constexpr T horrible_cast(U &&in) noexcept { - return detail_horrible_cast::temp{std::forward(in)}.out; +constexpr auto horrible_cast(U &&in) noexcept + -> typename std::enable_if::value + && std::is_trivially_copyable>::value, T>::type { + return detail_horrible_cast::temp>{std::forward(in)}.out; } } // namespace ipc diff --git a/include/libipc/mem/container_allocator.h b/include/libipc/mem/container_allocator.h index a01adac..4087b00 100644 --- a/include/libipc/mem/container_allocator.h +++ b/include/libipc/mem/container_allocator.h @@ -7,6 +7,7 @@ #include #include +#include #include "libipc/imp/uninitialized.h" #include "libipc/mem/new.h" @@ -56,19 +57,32 @@ public: container_allocator& operator=(container_allocator &&) noexcept = default; constexpr size_type max_size() const noexcept { - return 1; + return (std::numeric_limits::max)() / sizeof(value_type); } pointer allocate(size_type count) noexcept { if (count == 0) return nullptr; if (count > this->max_size()) return nullptr; - return mem::$new(); + if (count == 1) { + return mem::$new(); + } else { + void *p = mem::alloc(sizeof(value_type) * count); + if (p == nullptr) return nullptr; + for (std::size_t i = 0; i < count; ++i) { + std::ignore = ipc::construct(static_cast(p) + sizeof(value_type) * i); + } + return static_cast(p); + } } void deallocate(pointer p, size_type count) noexcept { if (count == 0) return; if (count > this->max_size()) return; - mem::$delete(p); + if (count == 1) { + mem::$delete(p); + } else { + mem::free(ipc::destroy_n(p, count), sizeof(value_type) * count); + } } template diff --git a/include/libipc/mem/new.h b/include/libipc/mem/new.h index 0145854..cf246a4 100644 --- a/include/libipc/mem/new.h +++ b/include/libipc/mem/new.h @@ -26,133 +26,43 @@ namespace mem { class LIBIPC_EXPORT block_collector { public: virtual ~block_collector() noexcept = default; - virtual void recycle(void */*p*/) noexcept {} + virtual void *allocate(std::size_t /*bytes*/) noexcept = 0; + virtual void deallocate(void */*p*/, std::size_t /*bytes*/) noexcept = 0; }; -#if defined(LIBIPC_CPP_17) -using recycle_t = void (*)(void *p, void *o) noexcept; -#else -using recycle_t = void (*)(void *p, void *o); -#endif +/// \brief Matches the appropriate memory block resource based on a specified size. +LIBIPC_EXPORT block_collector &get_regular_resource(std::size_t s) noexcept; -static constexpr std::size_t regular_head_size - = round_up(sizeof(recycle_t), alignof(std::max_align_t)); - -/// \brief Select the incremental level based on the size. -constexpr inline std::size_t regular_level(std::size_t s) noexcept { - return (s <= 128 ) ? 0 : - (s <= 1024 ) ? 1 : - (s <= 8192 ) ? 2 : - (s <= 65536) ? 3 : 4; -} - -/// \brief Calculates the appropriate memory block size based on the increment level and size. -constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept { - return (l == 0) ? round_up(s, regular_head_size) : - (l == 1) ? round_up(s, 128 ) : - (l == 2) ? round_up(s, 1024) : - (l == 3) ? round_up(s, 8192) : (std::numeric_limits::max)(); -} - -/// \brief Calculates the appropriate memory block size based on the size. -constexpr inline std::size_t regular_sizeof_impl(std::size_t s) noexcept { - return regular_sizeof_impl(regular_level(s), s); -} - -/// \brief Calculates the appropriate memory block size based on the specific type. -constexpr inline std::size_t regular_sizeof(std::size_t s) noexcept { - return regular_sizeof_impl(regular_head_size + s); -} -template -constexpr inline std::size_t regular_sizeof() noexcept { - return regular_sizeof(S); -} - -/// \brief Use block pools to handle memory less than 64K. -template -class block_resource_base : public block_pool { -public: - void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept { - return block_pool::allocate(); - } - - void deallocate(void *p) noexcept { - block_pool::deallocate(p); - } -}; - -/// \brief Use `new`/`delete` to handle memory larger than 64K. -template -class block_resource_base : public new_delete_resource { -public: - void *allocate(std::size_t bytes, std::size_t alignment) noexcept { - return new_delete_resource::allocate(regular_head_size + bytes, alignment); - } - - void deallocate(void *p) noexcept { - new_delete_resource::deallocate(p, regular_head_size); - } -}; - -/// \brief Defines block pool memory resource based on block pool. -template -class block_pool_resource : public block_resource_base - , public block_collector { - - using base_t = block_resource_base; - - void recycle(void *p) noexcept override { - base_t::deallocate(p); - } - -public: - static block_collector *get() noexcept { - thread_local block_pool_resource instance; - return &instance; - } - - template - void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept { - void *b = base_t::allocate(bytes, alignment); - *static_cast(b) - = [](void *b, void *p) noexcept { - std::ignore = destroy(static_cast(p)); - get()->recycle(b); - }; - return static_cast(b) + regular_head_size; - } -}; - -/// \brief Different increment levels match different chunk sizes. -/// 512 means that 512 consecutive memory blocks are allocated at a time. -template -constexpr std::size_t block_pool_expansion = 0; - -template <> constexpr std::size_t block_pool_expansion<0> = 512; -template <> constexpr std::size_t block_pool_expansion<1> = 256; -template <> constexpr std::size_t block_pool_expansion<2> = 128; -template <> constexpr std::size_t block_pool_expansion<3> = 64; - -/// \brief Matches the appropriate memory block resource based on the specified type. -template (), std::size_t L = regular_level(N)> -auto *get_regular_resource() noexcept { - using block_poll_resource_t = block_pool_resource>; - return dynamic_cast(block_poll_resource_t::get()); -} -template ::value, bool> = true> -auto *get_regular_resource() noexcept { - using block_poll_resource_t = block_pool_resource<0, 0>; - return dynamic_cast(block_poll_resource_t::get()); -} +/// \brief Allocates storage with a size of at least bytes bytes. +LIBIPC_EXPORT void *alloc(std::size_t bytes) noexcept; +LIBIPC_EXPORT void free (void *p, std::size_t bytes) noexcept; namespace detail_new { +#if defined(LIBIPC_CPP_17) +using recycle_t = void (*)(void *p) noexcept; +#else +using recycle_t = void (*)(void *p); +#endif + +static constexpr std::size_t recycler_size = round_up(sizeof(recycle_t), alignof(std::size_t)); +static constexpr std::size_t allocated_size = sizeof(std::size_t); +static constexpr std::size_t regular_head_size = round_up(recycler_size + allocated_size, alignof(std::max_align_t)); + template struct do_allocate { - template - static T *apply(R *res, A &&... args) noexcept { + template + static T *apply(A &&... args) noexcept { + void *b = mem::alloc(regular_head_size + sizeof(T)); + auto *p = static_cast(b) + regular_head_size; LIBIPC_TRY { - return construct(res->template allocate(sizeof(T), alignof(T)), std::forward(args)...); + T *t = construct(p, std::forward(args)...); + *reinterpret_cast(b) + = [](void *p) noexcept { + mem::free(static_cast(destroy(static_cast(p))) - regular_head_size + , regular_head_size + sizeof(T)); + }; + return t; } LIBIPC_CATCH(...) { return nullptr; } @@ -161,10 +71,18 @@ struct do_allocate { template <> struct do_allocate { - template - static void *apply(R *res, std::size_t bytes) noexcept { + static void *apply(std::size_t bytes) noexcept { if (bytes == 0) return nullptr; - return res->template allocate(bytes); + std::size_t rbz = regular_head_size + bytes; + void *b = mem::alloc(rbz); + *reinterpret_cast(b) + = [](void *p) noexcept { + auto *b = static_cast(p) - regular_head_size; + mem::free(b, *reinterpret_cast(b + recycler_size)); + }; + auto *z = static_cast(b) + recycler_size; + *reinterpret_cast(z) = rbz; + return static_cast(b) + regular_head_size; } }; @@ -174,9 +92,7 @@ struct do_allocate { /// \note This function is thread-safe. template T *$new(A &&... args) noexcept { - auto *res = get_regular_resource(); - if (res == nullptr) return nullptr; - return detail_new::do_allocate::apply(res, std::forward(args)...); + return detail_new::do_allocate::apply(std::forward(args)...); } /// \brief Destroys object previously allocated by the `$new` and releases obtained memory area. @@ -184,9 +100,8 @@ T *$new(A &&... args) noexcept { /// additional performance penalties may be incurred. inline void $delete(void *p) noexcept { if (p == nullptr) return; - auto *b = reinterpret_cast(p) - regular_head_size; - auto *r = reinterpret_cast(b); - (*r)(b, p); + auto *r = reinterpret_cast(static_cast(p) - detail_new::regular_head_size); + (*r)(p); } /// \brief The destruction policy used by std::unique_ptr. diff --git a/src/libipc/mem/new.cpp b/src/libipc/mem/new.cpp new file mode 100644 index 0000000..a8f6e2e --- /dev/null +++ b/src/libipc/mem/new.cpp @@ -0,0 +1,121 @@ + +#include "libipc/mem/new.h" + +namespace ipc { +namespace mem { + +/// \brief Select the incremental level based on the size. +constexpr inline std::size_t regular_level(std::size_t s) noexcept { + return (s <= 128 ) ? 0 : + (s <= 1024 ) ? 1 : + (s <= 8192 ) ? 2 : + (s <= 65536) ? 3 : 4; +} + +/// \brief Use block pools to handle memory less than 64K. +template +class block_resource_base : public block_pool + , public block_collector { +public: + void *allocate(std::size_t /*bytes*/) noexcept override { + return block_pool::allocate(); + } + + void deallocate(void *p, std::size_t /*bytes*/) noexcept override { + block_pool::deallocate(p); + } +}; + +/// \brief Use `new`/`delete` to handle memory larger than 64K. +template <> +class block_resource_base<0, 0> : public new_delete_resource + , public block_collector { +public: + void *allocate(std::size_t bytes) noexcept override { + return new_delete_resource::allocate(bytes); + } + + void deallocate(void *p, std::size_t bytes) noexcept override { + new_delete_resource::deallocate(p, bytes); + } +}; + +/// \brief Defines block pool memory resource based on block pool. +template +class block_pool_resource : public block_resource_base { +public: + static block_collector &get() noexcept { + thread_local block_pool_resource instance; + return instance; + } +}; + +/// \brief Matches the appropriate memory block resource based on a specified size. +block_collector &get_regular_resource(std::size_t s) noexcept { + std::size_t l = regular_level(s); + switch (l) { + case 0: + switch (round_up(s, 16)) { + case 16 : return block_pool_resource<16 , 512>::get(); + case 32 : return block_pool_resource<32 , 512>::get(); + case 48 : return block_pool_resource<48 , 512>::get(); + case 64 : return block_pool_resource<64 , 512>::get(); + case 80 : return block_pool_resource<80 , 512>::get(); + case 96 : return block_pool_resource<96 , 512>::get(); + case 112: return block_pool_resource<112, 512>::get(); + case 128: return block_pool_resource<128, 512>::get(); + default : break; + } + break; + case 1: + switch (round_up(s, 128)) { + case 256 : return block_pool_resource<256 , 256>::get(); + case 384 : return block_pool_resource<384 , 256>::get(); + case 512 : return block_pool_resource<512 , 256>::get(); + case 640 : return block_pool_resource<640 , 256>::get(); + case 768 : return block_pool_resource<768 , 256>::get(); + case 896 : return block_pool_resource<896 , 256>::get(); + case 1024: return block_pool_resource<1024, 256>::get(); + default : break; + } + break; + case 2: + switch (round_up(s, 1024)) { + case 2048: return block_pool_resource<2048, 128>::get(); + case 3072: return block_pool_resource<3072, 128>::get(); + case 4096: return block_pool_resource<4096, 128>::get(); + case 5120: return block_pool_resource<5120, 128>::get(); + case 6144: return block_pool_resource<6144, 128>::get(); + case 7168: return block_pool_resource<7168, 128>::get(); + case 8192: return block_pool_resource<8192, 128>::get(); + default : break; + } + break; + case 3: + switch (round_up(s, 8192)) { + case 16384: return block_pool_resource<16384, 64>::get(); + case 24576: return block_pool_resource<24576, 64>::get(); + case 32768: return block_pool_resource<32768, 64>::get(); + case 40960: return block_pool_resource<40960, 64>::get(); + case 49152: return block_pool_resource<49152, 64>::get(); + case 57344: return block_pool_resource<57344, 64>::get(); + case 65536: return block_pool_resource<65536, 64>::get(); + default : break; + } + break; + default: + break; + } + return block_pool_resource<0, 0>::get(); +} + +void *alloc(std::size_t bytes) noexcept { + return get_regular_resource(bytes).allocate(bytes); +} + +void free(void *p, std::size_t bytes) noexcept { + return get_regular_resource(bytes).deallocate(p, bytes); +} + +} // namespace mem +} // namespace ipc diff --git a/src/libipc/utility/utility.h b/src/libipc/utility/utility.h index f4387e4..f9de13f 100755 --- a/src/libipc/utility/utility.h +++ b/src/libipc/utility/utility.h @@ -44,18 +44,6 @@ enum { // #endif/*__cplusplus < 201703L*/ }; -template -auto horrible_cast(U rhs) noexcept - -> typename std::enable_if::value - && std::is_trivially_copyable::value, T>::type { - union { - T t; - U u; - } r = {}; - r.u = rhs; - return r.t; -} - IPC_CONSTEXPR_ std::size_t make_align(std::size_t align, std::size_t size) { // align must be 2^n return (size + align - 1) & ~(align - 1); diff --git a/test/mem/test_mem_new.cpp b/test/mem/test_mem_new.cpp index 2a0432d..85a7904 100644 --- a/test/mem/test_mem_new.cpp +++ b/test/mem/test_mem_new.cpp @@ -9,19 +9,6 @@ #include "libipc/mem/new.h" -TEST(new, regular_sizeof) { - ASSERT_EQ(ipc::mem::regular_sizeof(), ipc::mem::regular_head_size + alignof(std::max_align_t)); - ASSERT_EQ(ipc::mem::regular_sizeof(), ipc::mem::regular_head_size + alignof(std::max_align_t)); - ASSERT_EQ(ipc::mem::regular_sizeof(), ipc::mem::regular_head_size + alignof(std::max_align_t)); - ASSERT_EQ(ipc::mem::regular_sizeof(), ipc::mem::regular_head_size + alignof(std::max_align_t)); - - ASSERT_EQ((ipc::mem::regular_sizeof>()), ipc::round_up(ipc::mem::regular_head_size + 10 , alignof(std::max_align_t))); - ASSERT_EQ((ipc::mem::regular_sizeof>()), ipc::round_up(ipc::mem::regular_head_size + 100 , alignof(std::max_align_t))); - ASSERT_EQ((ipc::mem::regular_sizeof>()), ipc::round_up(ipc::mem::regular_head_size + 1000 , 128)); - ASSERT_EQ((ipc::mem::regular_sizeof>()), ipc::round_up(ipc::mem::regular_head_size + 10000, 8192)); - ASSERT_EQ((ipc::mem::regular_sizeof>()), (std::numeric_limits::max)()); -} - TEST(new, new) { auto p = ipc::mem::$new(); ASSERT_NE(p, nullptr);