diff --git a/include/libpmr/allocator.h b/include/libpmr/allocator.h index c02c8d7..d4ebce2 100644 --- a/include/libpmr/allocator.h +++ b/include/libpmr/allocator.h @@ -115,6 +115,18 @@ public: /// \brief Allocate/deallocate memory. 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; + + /// \brief Allocates uninitialized memory and constructs an object of type T in the memory. + template + T *construct(A &&...args) const { + return ::LIBIMP::construct(allocate(sizeof(T), alignof(T)), std::forward(args)...); + } + + /// \brief Calls the destructor of the object pointed to by p and deallocates the memory. + template + void destroy(T *p) const noexcept { + deallocate(::LIBIMP::destroy(p), sizeof(T), alignof(T)); + } }; LIBPMR_NAMESPACE_END_ diff --git a/include/libpmr/block_pool.h b/include/libpmr/block_pool.h index f38ecbb..4f881cf 100644 --- a/include/libpmr/block_pool.h +++ b/include/libpmr/block_pool.h @@ -12,13 +12,19 @@ #include #include "libimp/byte.h" +#include "libimp/export.h" #include "libconcur/intrusive_stack.h" #include "libpmr/def.h" -#include "libpmr/memory_resource.h" +#include "libpmr/allocator.h" LIBPMR_NAMESPACE_BEG_ +/// \brief Get the central cache allocator. +/// \note The central cache allocator is used to allocate memory for the central cache pool. +/// The underlying memory resource is a `monotonic_buffer_resource` with a fixed-size buffer. +LIBIMP_EXPORT allocator ¢ral_cache_allocator() noexcept; + /** * \brief The block type. * \tparam BlockSize specifies the memory block size @@ -33,45 +39,49 @@ union block { * \brief A fixed-length memory block central cache pool. * \tparam BlockSize specifies the memory block size */ -template +template class central_cache_pool { - /// \brief The block type. - using block_t = block; + /// \brief The block type, which should be a union of a pointer and a storage. + using block_t = BlockT; + /// \brief The chunk type, which is an array of blocks. + using chunk_t = std::array; + /// \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 cache_; + ::LIBCONCUR::intrusive_stack cached_; ::LIBCONCUR::intrusive_stack aqueired_; central_cache_pool() noexcept = default; public: block_t *aqueire() noexcept { - auto *n = cache_.pop(); + auto *n = cached_.pop(); if (n != nullptr) { aqueired_.push(n); return n->value; } - auto *blocks = new(std::nothrow) std::array; - if (blocks == nullptr) { + auto *chunk = central_cache_allocator().construct(); + if (chunk == nullptr) { return nullptr; } for (std::size_t i = 0; i < block_pool_expansion - 1; ++i) { - (*blocks)[i].next = &(*blocks)[i + 1]; + (*chunk)[i].next = &(*chunk)[i + 1]; } - blocks->back().next = nullptr; - return blocks->data(); + chunk->back().next = nullptr; + return chunk->data(); } void release(block_t *p) noexcept { if (p == nullptr) return; auto *a = aqueired_.pop(); if (a == nullptr) { - a = new(std::nothrow) ::LIBCONCUR::intrusive_stack::node; + a = central_cache_allocator().construct(); if (a == nullptr) return; } a->value = p; - cache_.push(a); + cached_.push(a); } /// \brief Get the singleton instance. @@ -84,16 +94,19 @@ public: /** * \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 +template class block_pool { /// \brief The block type. using block_t = block; + /// \brief The central cache pool type. + using central_cache_pool_t = central_cache_pool; /// \brief Expand the block pool when it is exhausted. block_t *expand() noexcept { - return central_cache_pool::instance().aqueire(); + return central_cache_pool_t::instance().aqueire(); } public: @@ -101,7 +114,7 @@ public: block_pool() noexcept : cursor_(expand()) {} ~block_pool() noexcept { - central_cache_pool::instance().release(cursor_); + central_cache_pool_t::instance().release(cursor_); } block_pool(block_pool const &) = delete; diff --git a/include/libpmr/def.h b/include/libpmr/def.h index b724aa9..38a265a 100644 --- a/include/libpmr/def.h +++ b/include/libpmr/def.h @@ -15,7 +15,8 @@ LIBPMR_NAMESPACE_BEG_ /// \brief Constants. enum : std::size_t { - block_pool_expansion = 64, + block_pool_expansion = 64, + central_cache_default_size = 1024 * 1024, ///< 1MB }; LIBPMR_NAMESPACE_END_ diff --git a/include/libpmr/memory_resource.h b/include/libpmr/memory_resource.h index be30853..27aa5dd 100644 --- a/include/libpmr/memory_resource.h +++ b/include/libpmr/memory_resource.h @@ -49,7 +49,7 @@ using verify_memory_resource = */ class LIBIMP_EXPORT new_delete_resource { public: - /// \brief Returns a pointer to a new_delete_resource. + /// \brief Returns a pointer to a `new_delete_resource`. static new_delete_resource *get() noexcept; /// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment. diff --git a/include/libpmr/synchronized_pool_resource.h b/include/libpmr/synchronized_pool_resource.h new file mode 100644 index 0000000..8c54bf5 --- /dev/null +++ b/include/libpmr/synchronized_pool_resource.h @@ -0,0 +1,40 @@ +/** + * \file libpmr/synchronized_pool_resource.h + * \author mutouyun (orz@orzz.org) + * \brief A thread-safe std::pmr::memory_resource for managing allocations in pools of different block sizes. + * \date 2023-12-23 + */ +#pragma once + +#include // std::size_t, std::max_align_t + +#include "libimp/export.h" +#include "libpmr/def.h" + +LIBPMR_NAMESPACE_BEG_ + +/** + * \class LIBIMP_EXPORT synchronized_pool_resource + * \brief `synchronized_pool_resource` may be accessed from multiple threads without external synchronization, + * and have thread-specific pools to reduce synchronization costs. + * \note Unlike the standard library implementation, `synchronized_pool_resource` automatically manages + * the block size and reclaims all allocated memory to the central heap when the thread is destroyed, + * rather than being destructed on its own. + * \see https://en.cppreference.com/w/cpp/memory/synchronized_pool_resource + */ +class LIBIMP_EXPORT synchronized_pool_resource { +public: + /// \brief Returns a pointer to a `synchronized_pool_resource`. + static synchronized_pool_resource *get() noexcept; + + /// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment. + /// \remark Returns nullptr if storage of the requested size and alignment cannot be obtained. + /// \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate + void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept; + + /// \brief Deallocates the storage pointed to by p. + /// \see https://en.cppreference.com/w/cpp/memory/memory_resource/deallocate + void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept; +}; + +LIBPMR_NAMESPACE_END_ diff --git a/src/libpmr/block_pool.cpp b/src/libpmr/block_pool.cpp new file mode 100644 index 0000000..47893c6 --- /dev/null +++ b/src/libpmr/block_pool.cpp @@ -0,0 +1,14 @@ + +#include "libpmr/block_pool.h" +#include "libpmr/monotonic_buffer_resource.h" + +LIBPMR_NAMESPACE_BEG_ + +allocator ¢ral_cache_allocator() noexcept { + static std::array<::LIBIMP::byte, central_cache_default_size> buffer; + static monotonic_buffer_resource mr(buffer); + static allocator a(&mr); + return a; +} + +LIBPMR_NAMESPACE_END_ diff --git a/src/libpmr/synchronized_pool_resource.cpp b/src/libpmr/synchronized_pool_resource.cpp new file mode 100644 index 0000000..0c44d88 --- /dev/null +++ b/src/libpmr/synchronized_pool_resource.cpp @@ -0,0 +1,11 @@ + +#include "libimp/log.h" + +#include "libpmr/synchronized_pool_resource.h" + +LIBPMR_NAMESPACE_BEG_ +namespace { + +} // namespace + +LIBPMR_NAMESPACE_END_ diff --git a/test/pmr/test_pmr_block_pool.cpp b/test/pmr/test_pmr_block_pool.cpp index 7e8ec86..1271dd8 100644 --- a/test/pmr/test_pmr_block_pool.cpp +++ b/test/pmr/test_pmr_block_pool.cpp @@ -1,7 +1,62 @@ +#include + #include "gtest/gtest.h" #include "libpmr/block_pool.h" -TEST(block_pool, construct) { +TEST(block_pool, central_cache_allocator) { + auto &a = pmr::central_cache_allocator(); + ASSERT_FALSE(nullptr == a.allocate(1)); + ASSERT_FALSE(nullptr == a.allocate(10)); + ASSERT_FALSE(nullptr == a.allocate(100)); + ASSERT_FALSE(nullptr == a.allocate(1000)); + ASSERT_FALSE(nullptr == a.allocate(10000)); +} + +TEST(block_pool, block) { + pmr::block<1> b1; + EXPECT_EQ(sizeof(b1), sizeof(void*)); + pmr::block b2; + EXPECT_EQ(sizeof(b2), sizeof(void*)); + pmr::block b3; + EXPECT_EQ(sizeof(b3), sizeof(void*) * 2); +} + +TEST(block_pool, central_cache_pool_ctor) { + ASSERT_FALSE((std::is_default_constructible, 1>>::value)); + ASSERT_FALSE((std::is_copy_constructible, 1>>::value)); + ASSERT_FALSE((std::is_move_constructible, 1>>::value)); + ASSERT_FALSE((std::is_copy_assignable, 1>>::value)); + ASSERT_FALSE((std::is_move_assignable, 1>>::value)); + { + auto &pool = pmr::central_cache_pool, 1>::instance(); + pmr::block<1> *b1 = pool.aqueire(); + ASSERT_FALSE(nullptr == b1); + ASSERT_TRUE (nullptr == b1->next); + pool.release(b1); + pmr::block<1> *b2 = pool.aqueire(); + EXPECT_EQ(b1, b2); + pmr::block<1> *b3 = pool.aqueire(); + ASSERT_FALSE(nullptr == b3); + ASSERT_TRUE (nullptr == b3->next); + EXPECT_NE(b1, b3); + } + { + auto &pool = pmr::central_cache_pool, 2>::instance(); + pmr::block<1> *b1 = pool.aqueire(); + ASSERT_FALSE(nullptr == b1); + ASSERT_FALSE(nullptr == b1->next); + ASSERT_TRUE (nullptr == b1->next->next); + pool.release(b1); + pmr::block<1> *b2 = pool.aqueire(); + EXPECT_EQ(b1, b2); + pmr::block<1> *b3 = pool.aqueire(); + EXPECT_NE(b1, b3); + pmr::block<1> *b4 = pool.aqueire(); + ASSERT_FALSE(nullptr == b4); + ASSERT_FALSE(nullptr == b4->next); + ASSERT_TRUE (nullptr == b4->next->next); + EXPECT_NE(b1, b4); + } }