mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
upd: [pmr] improve block_pool
This commit is contained in:
parent
a8c6654178
commit
3be10b67bc
@ -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 <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_
|
||||
|
||||
@ -12,13 +12,19 @@
|
||||
#include <deque>
|
||||
|
||||
#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 <std::size_t BlockSize>
|
||||
template <typename BlockT, std::size_t BlockPoolExpansion>
|
||||
class central_cache_pool {
|
||||
|
||||
/// \brief The block type.
|
||||
using block_t = block<BlockSize>;
|
||||
/// \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<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.
|
||||
::LIBCONCUR::intrusive_stack<block_t *> cache_;
|
||||
::LIBCONCUR::intrusive_stack<block_t *> cached_;
|
||||
::LIBCONCUR::intrusive_stack<block_t *> 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<block_t, block_pool_expansion>;
|
||||
if (blocks == nullptr) {
|
||||
auto *chunk = central_cache_allocator().construct<chunk_t>();
|
||||
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<block_t *>::node;
|
||||
a = central_cache_allocator().construct<node_t>();
|
||||
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 <std::size_t BlockSize>
|
||||
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
class block_pool {
|
||||
|
||||
/// \brief The block type.
|
||||
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.
|
||||
block_t *expand() noexcept {
|
||||
return central_cache_pool<BlockSize>::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<BlockSize>::instance().release(cursor_);
|
||||
central_cache_pool_t::instance().release(cursor_);
|
||||
}
|
||||
|
||||
block_pool(block_pool const &) = delete;
|
||||
|
||||
@ -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_
|
||||
|
||||
@ -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.
|
||||
|
||||
40
include/libpmr/synchronized_pool_resource.h
Normal file
40
include/libpmr/synchronized_pool_resource.h
Normal 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
14
src/libpmr/block_pool.cpp
Normal file
@ -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_
|
||||
11
src/libpmr/synchronized_pool_resource.cpp
Normal file
11
src/libpmr/synchronized_pool_resource.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
#include "libimp/log.h"
|
||||
|
||||
#include "libpmr/synchronized_pool_resource.h"
|
||||
|
||||
LIBPMR_NAMESPACE_BEG_
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
LIBPMR_NAMESPACE_END_
|
||||
@ -1,7 +1,62 @@
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#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<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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user