diff --git a/include/libpmr/block_pool.h b/include/libpmr/block_pool.h index 4f881cf..7649634 100644 --- a/include/libpmr/block_pool.h +++ b/include/libpmr/block_pool.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "libimp/byte.h" #include "libimp/export.h" @@ -37,7 +38,7 @@ union block { /** * \brief A fixed-length memory block central cache pool. - * \tparam BlockSize specifies the memory block size + * \tparam BlockT specifies the memory block type */ template class central_cache_pool { @@ -66,7 +67,7 @@ public: if (chunk == nullptr) { return nullptr; } - for (std::size_t i = 0; i < block_pool_expansion - 1; ++i) { + for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) { (*chunk)[i].next = &(*chunk)[i + 1]; } chunk->back().next = nullptr; @@ -91,12 +92,103 @@ public: } }; +/// \brief A fixed-length memory block central cache pool with no default expansion size. +template +class central_cache_pool { + + /// \brief The block type, which should be a union of a pointer and a storage. + using block_t = BlockT; + /// \brief The node type, which is used to store the block pointer. + using node_t = typename ::LIBCONCUR::intrusive_stack::node; + + /// \brief The central cache stack. + ::LIBCONCUR::intrusive_stack cached_; + ::LIBCONCUR::intrusive_stack aqueired_; + + central_cache_pool() noexcept = default; + +public: + block_t *aqueire() noexcept { + auto *n = cached_.pop(); + if (n != nullptr) { + aqueired_.push(n); + return n->value; + } + // For pools with no default expansion size, + // the central cache pool is only buffered, not allocated. + return nullptr; + } + + void release(block_t *p) noexcept { + if (p == nullptr) return; + auto *a = aqueired_.pop(); + if (a == nullptr) { + a = central_cache_allocator().construct(); + if (a == nullptr) return; + } + a->value = p; + cached_.push(a); + } + + /// \brief Get the singleton instance. + static central_cache_pool &instance() noexcept { + static central_cache_pool pool; + return pool; + } +}; + /** * \brief Fixed-length memory block pool. * \tparam BlockSize specifies the memory block size * \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted */ template +class block_pool; + +/// \brief General-purpose block pool for any size of memory block. +/// \note This block pool can only be used to deallocate a group of memory blocks of unknown but consistent size, +/// and cannot be used for memory block allocation. +template <> +class block_pool<0, 0> { + + template + friend class block_pool; + + /// \brief The block type. + struct block_t { + block_t *next; + }; + + /// \brief The central cache pool type. + using central_cache_pool_t = central_cache_pool; + +public: + constexpr static std::size_t block_size = 0; + + block_pool() noexcept : cursor_(central_cache_pool_t::instance().aqueire()) {} + ~block_pool() noexcept { + central_cache_pool_t::instance().release(cursor_); + } + + block_pool(block_pool const &) = delete; + block_pool& operator=(block_pool const &) = delete; + + block_pool(block_pool &&rhs) noexcept : cursor_(std::exchange(rhs.cursor_, nullptr)) {} + block_pool &operator=(block_pool &&) noexcept = delete; + + void deallocate(void *p) noexcept { + if (p == nullptr) return; + block_t *b = static_cast(p); + b->next = cursor_; + cursor_ = b; + } + +private: + block_t *cursor_; +}; + +/// \brief A block pool for a block of memory of a specific size. +template class block_pool { /// \brief The block type. @@ -120,6 +212,15 @@ public: block_pool(block_pool const &) = delete; block_pool& operator=(block_pool const &) = delete; + block_pool(block_pool &&rhs) noexcept + : cursor_(std::exchange(rhs.cursor_, nullptr)) {} + block_pool &operator=(block_pool &&) noexcept = delete; + + /// \brief Used to take all memory blocks from within a general-purpose block pool. + /// \note Of course, the actual memory blocks they manage must be the same size. + block_pool(block_pool<0, 0> &&rhs) noexcept + : cursor_(reinterpret_cast(std::exchange(rhs.cursor_, nullptr))) {} + void *allocate() noexcept { if (cursor_ == nullptr) { cursor_ = expand(); diff --git a/include/libpmr/def.h b/include/libpmr/def.h index 38a265a..3b8e1dd 100644 --- a/include/libpmr/def.h +++ b/include/libpmr/def.h @@ -6,6 +6,10 @@ */ #pragma once +#include + +#include "libimp/aligned.h" + #define LIBPMR pmr #define LIBPMR_NAMESPACE_BEG_ namespace LIBPMR { #define LIBPMR_NAMESPACE_END_ } @@ -15,8 +19,8 @@ LIBPMR_NAMESPACE_BEG_ /// \brief Constants. enum : std::size_t { - block_pool_expansion = 64, central_cache_default_size = 1024 * 1024, ///< 1MB + regular_head_size = ::LIBIMP::round_up(sizeof(std::size_t), alignof(std::max_align_t)), }; LIBPMR_NAMESPACE_END_ diff --git a/include/libpmr/new.h b/include/libpmr/new.h index 61ac1f7..2ef15c8 100644 --- a/include/libpmr/new.h +++ b/include/libpmr/new.h @@ -7,9 +7,13 @@ #pragma once #include +#include +#include #include "libimp/aligned.h" #include "libimp/uninitialized.h" +#include "libimp/byte.h" +#include "libimp/detect_plat.h" #include "libpmr/def.h" #include "libpmr/block_pool.h" @@ -27,34 +31,126 @@ constexpr inline std::size_t regular_level(std::size_t s) noexcept { constexpr inline std::size_t regular_sizeof(std::size_t s) noexcept { switch (regular_level(s)) { - case 0 : return ::LIBIMP::round_up(s, 8); + case 0 : return std::max(::LIBIMP::round_up(s, 8), regular_head_size); case 1 : return ::LIBIMP::round_up(s, 128); case 2 : return ::LIBIMP::round_up(s, 1024); case 3 : return ::LIBIMP::round_up(s, 8192); - default: return 0; + default: return (std::numeric_limits::max)(); } } template constexpr inline std::size_t regular_sizeof() noexcept { - return regular_sizeof(sizeof(T)); + return regular_sizeof(regular_head_size + sizeof(T)); +} + +class block_pool_like { + public: + virtual ~block_pool_like() noexcept = default; + virtual void *allocate() noexcept = 0; + virtual void deallocate(void *p) noexcept = 0; +}; + +inline std::unordered_map &get_block_pool_map() noexcept { + thread_local std::unordered_map instances; + return instances; } template -class block_pool_resource : public block_pool { +class block_pool_resource; + +template <> +class block_pool_resource<0, 0> : public block_pool<0, 0> + , public block_pool_like { + + void *allocate() noexcept override { + return nullptr; + } + public: - static block_pool_resource *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 { - return block_pool::allocate(); - } - void deallocate(void *p, std::size_t /*bytes*/, std::size_t /*alignment*/ = alignof(std::max_align_t)) noexcept { - block_pool::deallocate(p); + void deallocate(void *p) noexcept override { + block_pool<0, 0>::deallocate(p); } }; +template +class block_pool_resource : public block_pool + , public block_pool_like { + + using base_t = block_pool; + + void *allocate() noexcept override { + return base_t::allocate(); + } + + void deallocate(void *p) noexcept override { + base_t::deallocate(p); + } + +public: + static block_pool_resource *get() noexcept; + + using base_t::base_t; + + void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/ = alignof(std::max_align_t)) noexcept { + void *p = base_t::allocate(); + p = ::LIBIMP::construct(p, BlockSize); + return reinterpret_cast<::LIBIMP::byte *>(p) + regular_head_size; + } + + void deallocate(void *p, std::size_t /*bytes*/, std::size_t /*alignment*/ = alignof(std::max_align_t)) noexcept { + p = reinterpret_cast<::LIBIMP::byte *>(p) - regular_head_size; + auto r_size = *static_cast(p); + if (r_size <= BlockSize) { + base_t::deallocate(p); + return; + } + auto &map = get_block_pool_map(); + auto it = map.find(r_size); + if ((it == map.end()) || (it->second == nullptr)) LIBIMP_TRY { + // If the corresponding memory resource cannot be found, + // create a temporary general-purpose block pool to deallocate memory. + it = map.emplace(r_size, new block_pool_resource<0, 0>).first; + } LIBIMP_CATCH(...) { + // If the memory resource cannot be created, + // store the pointer directly to avoid leakage. + base_t::deallocate(p); + return; + } + it->second->deallocate(p); + } +}; + +template +auto block_pool_resource::get() noexcept + -> block_pool_resource * { + auto &map = get_block_pool_map(); + thread_local block_pool_resource *pi = nullptr; + if (pi != nullptr) { + return pi; + } + auto it = map.find(BlockSize); + if ((it != map.end()) && (it->second != nullptr)) { + auto *bp = static_cast *>( + dynamic_cast *>(it->second)); + if (bp == nullptr) { + return nullptr; + } + thread_local block_pool_resource instance(std::move(*bp)); + delete static_cast *>(bp); + pi = &instance; + } else { + thread_local block_pool_resource instance; + pi = &instance; + } + LIBIMP_TRY { + map.emplace(BlockSize, pi); + } LIBIMP_CATCH(...) { + return nullptr; + } + return pi; +} + template class regular_resource : public new_delete_resource {}; @@ -71,16 +167,19 @@ template class regular_resource : public block_pool_resource {}; template -T *new_(A &&... args) noexcept { +T *new$(A &&... args) noexcept { auto *mem_res = regular_resource()>::get(); + if (mem_res == nullptr) return nullptr; return ::LIBIMP::construct(mem_res->allocate(sizeof(T), alignof(T)), std::forward(args)...); } template -void delete_(T *p) noexcept { +void delete$(T *p) noexcept { if (p == nullptr) return; + ::LIBIMP::destroy(p); auto *mem_res = regular_resource()>::get(); - mem_res->deallocate(::LIBIMP::destroy(p), sizeof(T), alignof(T)); + if (mem_res == nullptr) return; + mem_res->deallocate(p, sizeof(T), alignof(T)); } LIBPMR_NAMESPACE_END_ diff --git a/test/pmr/test_pmr_new.cpp b/test/pmr/test_pmr_new.cpp index 96d59e7..efa1182 100644 --- a/test/pmr/test_pmr_new.cpp +++ b/test/pmr/test_pmr_new.cpp @@ -1,5 +1,97 @@ +#include +#include +#include +#include + #include "gtest/gtest.h" #include "libpmr/new.h" +TEST(pmr_new, regular_sizeof) { + ASSERT_EQ(pmr::regular_sizeof(), pmr::regular_head_size + 8); + ASSERT_EQ(pmr::regular_sizeof(), pmr::regular_head_size + 8); + ASSERT_EQ(pmr::regular_sizeof(), pmr::regular_head_size + 8); + ASSERT_EQ(pmr::regular_sizeof(), pmr::regular_head_size + 8); + + ASSERT_EQ((pmr::regular_sizeof>()), ::LIBIMP::round_up(pmr::regular_head_size + 10 , 8)); + ASSERT_EQ((pmr::regular_sizeof>()), ::LIBIMP::round_up(pmr::regular_head_size + 100 , 8)); + ASSERT_EQ((pmr::regular_sizeof>()), ::LIBIMP::round_up(pmr::regular_head_size + 1000 , 128)); + ASSERT_EQ((pmr::regular_sizeof>()), ::LIBIMP::round_up(pmr::regular_head_size + 10000, 8192)); + ASSERT_EQ((pmr::regular_sizeof>()), (std::numeric_limits::max)()); +} + +TEST(pmr_new, new$) { + auto p = pmr::new$(); + ASSERT_NE(p, nullptr); + *p = -1; + ASSERT_EQ(*p, -1); + pmr::delete$(p); +} + +TEST(pmr_new, new$value) { + auto p = pmr::new$((std::numeric_limits::max)()); + ASSERT_NE(p, nullptr); + ASSERT_EQ(*p, (std::numeric_limits::max)()); + pmr::delete$(p); +} + +namespace { + +template +void test_new$array() { + std::array pts; + using T = std::array; + for (int i = 0; i < pts.size(); ++i) { + auto p = pmr::new$(); + pts[i] = p; + std::memset(p, i, sizeof(T)); + } + for (int i = 0; i < pts.size(); ++i) { + T tmp; + std::memset(&tmp, i, sizeof(T)); + ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0); + pmr::delete$(static_cast(pts[i])); + } +} + +} // namespace + +TEST(pmr_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 { + +class Base { +public: + virtual ~Base() = default; + virtual int get() const = 0; +}; + +class Derived : public Base { +public: + Derived(int value) : value_(value) {} + int get() const override { return value_; } + +private: + int value_; +}; + +} // namespace + +TEST(pmr_new, delete$poly) { + Base *p = pmr::new$(-1); + ASSERT_NE(p, nullptr); + ASSERT_EQ(p->get(), -1); + pmr::delete$(p); + + ASSERT_EQ(p, pmr::new$((std::numeric_limits::max)())); + ASSERT_EQ(p->get(), (std::numeric_limits::max)()); + pmr::delete$(p); +}