From beba857cc73ad856db70bb8dac6520dd9f5f0ac7 Mon Sep 17 00:00:00 2001 From: mutouyun Date: Fri, 24 Jan 2025 17:28:39 +0800 Subject: [PATCH] Add `$new` --- include/libipc/mem/new.h | 157 ++++++++++++++++++++++++++++++++++- test/mem/test_mem_new.cpp | 169 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 test/mem/test_mem_new.cpp diff --git a/include/libipc/mem/new.h b/include/libipc/mem/new.h index ce62140..65468a7 100644 --- a/include/libipc/mem/new.h +++ b/include/libipc/mem/new.h @@ -8,18 +8,173 @@ #include #include #include +#include #include "libipc/imp/aligned.h" #include "libipc/imp/uninitialized.h" #include "libipc/imp/byte.h" #include "libipc/imp/detect_plat.h" - #include "libipc/imp/export.h" +#include "libipc/mem/memory_resource.h" +#include "libipc/mem/block_pool.h" namespace ipc { namespace mem { +/// \brief Defines the memory block collector interface. +class LIBIPC_EXPORT block_collector { +public: + virtual ~block_collector() noexcept = default; + virtual void recycle(void *p, std::size_t bytes, std::size_t alignment) noexcept = 0; +}; + +#if defined(LIBIPC_CPP_17) +using get_block_collector_t = block_collector *(*)() noexcept; +#else +using get_block_collector_t = block_collector *(*)(); +#endif + +static constexpr std::size_t regular_head_size + = round_up(sizeof(get_block_collector_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. +template +constexpr inline std::size_t regular_sizeof() noexcept { + return regular_sizeof_impl(regular_head_size + sizeof(T)); +} + +/// \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, std::size_t /*bytes*/, std::size_t /*alignment*/) 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, std::size_t bytes, std::size_t alignment) noexcept { + new_delete_resource::deallocate(p, regular_head_size + bytes, alignment); + } +}; + +/// \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, std::size_t bytes, std::size_t alignment) noexcept override { + base_t::deallocate(p, bytes, alignment); + } + +public: + static block_collector *get() noexcept { + thread_local block_pool_resource instance; + return &instance; + } + + void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept { + void *p = base_t::allocate(bytes, alignment); + *static_cast(p) = get; + return static_cast(p) + regular_head_size; + } + + void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept { + p = static_cast(p) - regular_head_size; + auto g = *static_cast(p); + if (g == get) { + base_t::deallocate(p, bytes, alignment); + return; + } + g()->recycle(p, bytes, alignment); + } +}; + +/// \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()); +} + +/// \brief Creates an object based on the specified type and parameters with block pool resource. +/// \note This function is thread-safe. +template +T *$new(A &&... args) noexcept { + auto *res = get_regular_resource(); + if (res == nullptr) return nullptr; + return construct(res->allocate(sizeof(T), alignof(T)), std::forward(args)...); +} + +/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area. +/// \note This function is thread-safe. If the pointer type passed in is different from `$new`, +/// additional performance penalties may be incurred. +template +void $delete(T *p) noexcept { + if (p == nullptr) return; + destroy(p); + auto *res = get_regular_resource(); + if (res == nullptr) return; +#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015) + res->deallocate(p, sizeof(T), alignof(T)); +#else + // `alignof` of vs2015 requires that type must be able to be instantiated. + res->deallocate(p, sizeof(T)); +#endif +} +/// \brief The destruction policy used by std::unique_ptr. +/// \see https://en.cppreference.com/w/cpp/memory/default_delete +struct deleter { + template + void operator()(T *p) const noexcept { + $delete(p); + } +}; } // namespace mem } // namespace ipc diff --git a/test/mem/test_mem_new.cpp b/test/mem/test_mem_new.cpp new file mode 100644 index 0000000..bb05b6d --- /dev/null +++ b/test/mem/test_mem_new.cpp @@ -0,0 +1,169 @@ + +#include +#include +#include +#include +#include + +#include "test.h" + +#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); + *p = -1; + ASSERT_EQ(*p, -1); + ipc::mem::$delete(p); +} + +TEST(new, new_value) { + auto p = ipc::mem::$new((std::numeric_limits::max)()); + ASSERT_NE(p, nullptr); + ASSERT_EQ(*p, (std::numeric_limits::max)()); + ipc::mem::$delete(p); +} + +namespace { + +template +void test_new$array() { + std::array pts; + using T = std::array; + for (int i = 0; i < (int)pts.size(); ++i) { + auto p = ipc::mem::$new(); + pts[i] = p; + std::memset(p, i, sizeof(T)); + } + for (int i = 0; i < (int)pts.size(); ++i) { + T tmp; + std::memset(&tmp, i, sizeof(T)); + ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0); + ipc::mem::$delete(static_cast(pts[i])); + } +} + +} // namespace + +TEST(new, new_array) { + test_new$array<1000, 10>(); + test_new$array<1000, 100>(); + test_new$array<1000, 1000>(); + test_new$array<1000, 10000>(); + test_new$array<1000, 100000>(); + // test_new$array<1000, 1000000>(); +} + +namespace { + +int construct_count__ = 0; + +class Base { +public: + virtual ~Base() = default; + virtual int get() const = 0; +}; + +class Derived : public Base { +public: + Derived(int value) : value_(value) { + construct_count__ = value_; + } + + ~Derived() override { + construct_count__ = 0; + } + + int get() const override { return value_; } + +private: + int value_; +}; + +class Derived64K : public Derived { +public: + using Derived::Derived; + +private: + std::array padding_; +}; + +} // namespace + +TEST(new, delete_poly) { + Base *p = ipc::mem::$new(-1); + ASSERT_NE(p, nullptr); + ASSERT_EQ(p->get(), -1); + ASSERT_EQ(construct_count__, -1); + ipc::mem::$delete(p); + ASSERT_EQ(construct_count__, 0); + + ASSERT_EQ(p, ipc::mem::$new((std::numeric_limits::max)())); + ASSERT_EQ(p->get(), (std::numeric_limits::max)()); + ASSERT_EQ(construct_count__, (std::numeric_limits::max)()); + ipc::mem::$delete(p); + ASSERT_EQ(construct_count__, 0); +} + +TEST(new, delete_poly64k) { + Base *p = ipc::mem::$new(-1); + ASSERT_NE(p, nullptr); + ASSERT_EQ(p->get(), -1); + ASSERT_EQ(construct_count__, -1); + ipc::mem::$delete(p); + ASSERT_EQ(construct_count__, 0); + + Base *q = ipc::mem::$new((std::numeric_limits::max)()); + ASSERT_EQ(q->get(), (std::numeric_limits::max)()); + ASSERT_EQ(construct_count__, (std::numeric_limits::max)()); + ipc::mem::$delete(q); + ASSERT_EQ(construct_count__, 0); +} + +TEST(new, delete_null) { + Base *p = nullptr; + ipc::mem::$delete(p); + SUCCEED(); +} + +TEST(new, multi_thread) { + std::array threads; + for (auto &t : threads) { + t = std::thread([] { + for (int i = 0; i < 10000; ++i) { + auto p = ipc::mem::$new(); + *p = i; + ipc::mem::$delete(p); + } + std::array pts; + for (int i = 0; i < 10000; ++i) { + auto p = ipc::mem::$new>(); + pts[i] = p; + std::memset(p, i, sizeof(std::array)); + } + for (int i = 0; i < 10000; ++i) { + std::array tmp; + std::memset(&tmp, i, sizeof(std::array)); + ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array)), 0); + ipc::mem::$delete(static_cast *>(pts[i])); + } + }); + } + for (auto &t : threads) { + t.join(); + } + SUCCEED(); +}