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.
|
/// \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_
|
||||||
|
|||||||
@ -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 ¢ral_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;
|
||||||
|
|||||||
@ -16,6 +16,7 @@ LIBPMR_NAMESPACE_BEG_
|
|||||||
|
|
||||||
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_
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
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 "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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user