upd: [pmr] improve block_pool

This commit is contained in:
mutouyun 2023-12-23 16:34:47 +08:00
parent a8c6654178
commit 3be10b67bc
8 changed files with 165 additions and 19 deletions

View File

@ -115,6 +115,18 @@ public:
/// \brief Allocate/deallocate memory. /// \brief Allocate/deallocate memory.
void *allocate(std::size_t s, std::size_t = alignof(std::max_align_t)) const; 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; 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 <typename T, typename... A>
T *construct(A &&...args) const {
return ::LIBIMP::construct<T>(allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
}
/// \brief Calls the destructor of the object pointed to by p and deallocates the memory.
template <typename T>
void destroy(T *p) const noexcept {
deallocate(::LIBIMP::destroy(p), sizeof(T), alignof(T));
}
}; };
LIBPMR_NAMESPACE_END_ LIBPMR_NAMESPACE_END_

View File

@ -12,13 +12,19 @@
#include <deque> #include <deque>
#include "libimp/byte.h" #include "libimp/byte.h"
#include "libimp/export.h"
#include "libconcur/intrusive_stack.h" #include "libconcur/intrusive_stack.h"
#include "libpmr/def.h" #include "libpmr/def.h"
#include "libpmr/memory_resource.h" #include "libpmr/allocator.h"
LIBPMR_NAMESPACE_BEG_ 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 &central_cache_allocator() noexcept;
/** /**
* \brief The block type. * \brief The block type.
* \tparam BlockSize specifies the memory block size * \tparam BlockSize specifies the memory block size
@ -33,45 +39,49 @@ union block {
* \brief A fixed-length memory block central cache pool. * \brief A fixed-length memory block central cache pool.
* \tparam BlockSize specifies the memory block size * \tparam BlockSize specifies the memory block size
*/ */
template <std::size_t BlockSize> template <typename BlockT, std::size_t BlockPoolExpansion>
class central_cache_pool { class central_cache_pool {
/// \brief The block type. /// \brief The block type, which should be a union of a pointer and a storage.
using block_t = block<BlockSize>; using block_t = BlockT;
/// \brief The chunk type, which is an array of blocks.
using chunk_t = std::array<block_t, BlockPoolExpansion>;
/// \brief The node type, which is used to store the block pointer.
using node_t = typename ::LIBCONCUR::intrusive_stack<block_t *>::node;
/// \brief The central cache stack. /// \brief The central cache stack.
::LIBCONCUR::intrusive_stack<block_t *> cache_; ::LIBCONCUR::intrusive_stack<block_t *> cached_;
::LIBCONCUR::intrusive_stack<block_t *> aqueired_; ::LIBCONCUR::intrusive_stack<block_t *> aqueired_;
central_cache_pool() noexcept = default; central_cache_pool() noexcept = default;
public: public:
block_t *aqueire() noexcept { block_t *aqueire() noexcept {
auto *n = cache_.pop(); auto *n = cached_.pop();
if (n != nullptr) { if (n != nullptr) {
aqueired_.push(n); aqueired_.push(n);
return n->value; return n->value;
} }
auto *blocks = new(std::nothrow) std::array<block_t, block_pool_expansion>; auto *chunk = central_cache_allocator().construct<chunk_t>();
if (blocks == nullptr) { if (chunk == nullptr) {
return nullptr; return nullptr;
} }
for (std::size_t i = 0; i < block_pool_expansion - 1; ++i) { 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; chunk->back().next = nullptr;
return blocks->data(); return chunk->data();
} }
void release(block_t *p) noexcept { void release(block_t *p) noexcept {
if (p == nullptr) return; if (p == nullptr) return;
auto *a = aqueired_.pop(); auto *a = aqueired_.pop();
if (a == nullptr) { if (a == nullptr) {
a = new(std::nothrow) ::LIBCONCUR::intrusive_stack<block_t *>::node; a = central_cache_allocator().construct<node_t>();
if (a == nullptr) return; if (a == nullptr) return;
} }
a->value = p; a->value = p;
cache_.push(a); cached_.push(a);
} }
/// \brief Get the singleton instance. /// \brief Get the singleton instance.
@ -84,16 +94,19 @@ public:
/** /**
* \brief Fixed-length memory block pool. * \brief Fixed-length memory block pool.
* \tparam BlockSize specifies the memory block size * \tparam BlockSize specifies the memory block size
* \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted
*/ */
template <std::size_t BlockSize> template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_pool { class block_pool {
/// \brief The block type. /// \brief The block type.
using block_t = block<BlockSize>; using block_t = block<BlockSize>;
/// \brief The central cache pool type.
using central_cache_pool_t = central_cache_pool<block_t, BlockPoolExpansion>;
/// \brief Expand the block pool when it is exhausted. /// \brief Expand the block pool when it is exhausted.
block_t *expand() noexcept { block_t *expand() noexcept {
return central_cache_pool<BlockSize>::instance().aqueire(); return central_cache_pool_t::instance().aqueire();
} }
public: public:
@ -101,7 +114,7 @@ public:
block_pool() noexcept : cursor_(expand()) {} block_pool() noexcept : cursor_(expand()) {}
~block_pool() noexcept { ~block_pool() noexcept {
central_cache_pool<BlockSize>::instance().release(cursor_); central_cache_pool_t::instance().release(cursor_);
} }
block_pool(block_pool const &) = delete; block_pool(block_pool const &) = delete;

View File

@ -15,7 +15,8 @@ LIBPMR_NAMESPACE_BEG_
/// \brief Constants. /// \brief Constants.
enum : std::size_t { enum : std::size_t {
block_pool_expansion = 64, block_pool_expansion = 64,
central_cache_default_size = 1024 * 1024, ///< 1MB
}; };
LIBPMR_NAMESPACE_END_ LIBPMR_NAMESPACE_END_

View File

@ -49,7 +49,7 @@ using verify_memory_resource =
*/ */
class LIBIMP_EXPORT new_delete_resource { class LIBIMP_EXPORT new_delete_resource {
public: 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; static new_delete_resource *get() noexcept;
/// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment. /// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.

View File

@ -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 <cstddef> // 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_

14
src/libpmr/block_pool.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "libpmr/block_pool.h"
#include "libpmr/monotonic_buffer_resource.h"
LIBPMR_NAMESPACE_BEG_
allocator &central_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_

View File

@ -0,0 +1,11 @@
#include "libimp/log.h"
#include "libpmr/synchronized_pool_resource.h"
LIBPMR_NAMESPACE_BEG_
namespace {
} // namespace
LIBPMR_NAMESPACE_END_

View File

@ -1,7 +1,62 @@
#include <type_traits>
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "libpmr/block_pool.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<sizeof(void*)> b2;
EXPECT_EQ(sizeof(b2), sizeof(void*));
pmr::block<sizeof(void*) + 1> b3;
EXPECT_EQ(sizeof(b3), sizeof(void*) * 2);
}
TEST(block_pool, central_cache_pool_ctor) {
ASSERT_FALSE((std::is_default_constructible<pmr::central_cache_pool<pmr::block<1>, 1>>::value));
ASSERT_FALSE((std::is_copy_constructible<pmr::central_cache_pool<pmr::block<1>, 1>>::value));
ASSERT_FALSE((std::is_move_constructible<pmr::central_cache_pool<pmr::block<1>, 1>>::value));
ASSERT_FALSE((std::is_copy_assignable<pmr::central_cache_pool<pmr::block<1>, 1>>::value));
ASSERT_FALSE((std::is_move_assignable<pmr::central_cache_pool<pmr::block<1>, 1>>::value));
{
auto &pool = pmr::central_cache_pool<pmr::block<1>, 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<pmr::block<1>, 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);
}
} }