mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
Add block_pool
This commit is contained in:
parent
dd8307e81a
commit
0b33859d33
121
include/libipc/mem/block_pool.h
Normal file
121
include/libipc/mem/block_pool.h
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* \file libipc/block_pool.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The fixed-length memory block pool.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/mem/central_cache_pool.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \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, std::size_t BlockPoolExpansion>
|
||||
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 <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||
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<block_t, 0>;
|
||||
|
||||
public:
|
||||
static constexpr 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<block_t *>(p);
|
||||
b->next = cursor_;
|
||||
cursor_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
block_t *cursor_;
|
||||
};
|
||||
|
||||
/// \brief A block pool for a block of memory of a specific size.
|
||||
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_t::instance().aqueire();
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr std::size_t block_size = BlockSize;
|
||||
|
||||
block_pool() noexcept : cursor_(expand()) {}
|
||||
~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;
|
||||
|
||||
/// \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<block_t *>(std::exchange(rhs.cursor_, nullptr))) {}
|
||||
|
||||
void *allocate() noexcept {
|
||||
if (cursor_ == nullptr) {
|
||||
cursor_ = expand();
|
||||
if (cursor_ == nullptr) return nullptr;
|
||||
}
|
||||
block_t *p = cursor_;
|
||||
cursor_ = cursor_->next;
|
||||
return p->storage.data();
|
||||
}
|
||||
|
||||
void deallocate(void *p) noexcept {
|
||||
if (p == nullptr) return;
|
||||
block_t *b = static_cast<block_t *>(p);
|
||||
b->next = cursor_;
|
||||
cursor_ = b;
|
||||
}
|
||||
|
||||
private:
|
||||
block_t *cursor_;
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
20
include/libipc/mem/central_cache_allocator.h
Normal file
20
include/libipc/mem/central_cache_allocator.h
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* \file libipc/central_cache_allocator.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The central cache allocator getter.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "libipc/imp/export.h"
|
||||
#include "libipc/mem/polymorphic_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/// \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.
|
||||
LIBIPC_EXPORT bytes_allocator ¢ral_cache_allocator() noexcept;
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
132
include/libipc/mem/central_cache_pool.h
Normal file
132
include/libipc/mem/central_cache_pool.h
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* \file libipc/central_cache_pool.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief The fixed-length memory block central cache pool.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/concur/intrusive_stack.h"
|
||||
#include "libipc/mem/central_cache_allocator.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
/**
|
||||
* \brief The block type.
|
||||
* \tparam BlockSize specifies the memory block size
|
||||
*/
|
||||
template <std::size_t BlockSize>
|
||||
union block {
|
||||
block *next;
|
||||
alignas(std::max_align_t) std::array<byte, BlockSize> storage;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A fixed-length memory block central cache pool.
|
||||
* \tparam BlockT specifies the memory block type
|
||||
*/
|
||||
template <typename BlockT, std::size_t BlockPoolExpansion>
|
||||
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 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 concur::intrusive_stack<block_t *>::node;
|
||||
|
||||
/// \brief The central cache stack.
|
||||
concur::intrusive_stack<block_t *> cached_;
|
||||
concur::intrusive_stack<block_t *> aqueired_;
|
||||
|
||||
central_cache_pool() noexcept = default;
|
||||
|
||||
public:
|
||||
block_t *aqueire() noexcept {
|
||||
auto *n = cached_.pop();
|
||||
if (n != nullptr) {
|
||||
aqueired_.push(n);
|
||||
return n->value;
|
||||
}
|
||||
auto *chunk = central_cache_allocator().construct<chunk_t>();
|
||||
if (chunk == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) {
|
||||
(*chunk)[i].next = &(*chunk)[i + 1];
|
||||
}
|
||||
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 = central_cache_allocator().construct<node_t>();
|
||||
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 A fixed-length memory block central cache pool with no default expansion size.
|
||||
template <typename BlockT>
|
||||
class central_cache_pool<BlockT, 0> {
|
||||
|
||||
/// \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 concur::intrusive_stack<block_t *>::node;
|
||||
|
||||
/// \brief The central cache stack.
|
||||
concur::intrusive_stack<block_t *> cached_;
|
||||
concur::intrusive_stack<block_t *> 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<node_t>();
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
47
src/libipc/mem/central_cache_allocator.cpp
Normal file
47
src/libipc/mem/central_cache_allocator.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "libipc/def.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/byte.h"
|
||||
#include "libipc/mem/polymorphic_allocator.h"
|
||||
#include "libipc/mem/memory_resource.h"
|
||||
|
||||
namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
class thread_safe_resource : public monotonic_buffer_resource {
|
||||
public:
|
||||
thread_safe_resource(span<byte> buffer) noexcept
|
||||
: monotonic_buffer_resource(buffer) {}
|
||||
|
||||
~thread_safe_resource() noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
monotonic_buffer_resource::release();
|
||||
}
|
||||
|
||||
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
return monotonic_buffer_resource::allocate(bytes, alignment);
|
||||
}
|
||||
|
||||
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
|
||||
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
|
||||
monotonic_buffer_resource::deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
bytes_allocator ¢ral_cache_allocator() noexcept {
|
||||
static std::array<byte, central_cache_default_size> buf;
|
||||
static thread_safe_resource res(buf);
|
||||
static bytes_allocator a(&res);
|
||||
return a;
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
32
test/mem/test_mem_block_pool.cpp
Normal file
32
test/mem/test_mem_block_pool.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/block_pool.h"
|
||||
|
||||
TEST(block_pool, ctor) {
|
||||
ASSERT_TRUE ((std::is_default_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_TRUE ((std::is_move_constructible<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::block_pool<1, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_assignable<ipc::mem::block_pool<1, 1>>::value));
|
||||
}
|
||||
|
||||
TEST(block_pool, allocate) {
|
||||
std::vector<void *> v;
|
||||
ipc::mem::block_pool<1, 1> pool;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
v.push_back(pool.allocate());
|
||||
}
|
||||
for (void *p: v) {
|
||||
ASSERT_FALSE(nullptr == p);
|
||||
pool.deallocate(p);
|
||||
}
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_EQ(v[v.size() - i - 1], pool.allocate());
|
||||
}
|
||||
for (void *p: v) {
|
||||
pool.deallocate(p);
|
||||
}
|
||||
}
|
||||
15
test/mem/test_mem_central_cache_allocator.cpp
Normal file
15
test/mem/test_mem_central_cache_allocator.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/central_cache_allocator.h"
|
||||
|
||||
TEST(central_cache_allocator, allocate) {
|
||||
auto &a = ipc::mem::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));
|
||||
}
|
||||
44
test/mem/test_mem_central_cache_pool.cpp
Normal file
44
test/mem/test_mem_central_cache_pool.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libipc/mem/central_cache_pool.h"
|
||||
|
||||
TEST(central_cache_pool, ctor) {
|
||||
ASSERT_FALSE((std::is_default_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_constructible<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
ASSERT_FALSE((std::is_move_assignable<ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>>::value));
|
||||
{
|
||||
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1>, 1>::instance();
|
||||
ipc::mem::block<1> *b1 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b1);
|
||||
ASSERT_TRUE (nullptr == b1->next);
|
||||
pool.release(b1);
|
||||
ipc::mem::block<1> *b2 = pool.aqueire();
|
||||
EXPECT_EQ(b1, b2);
|
||||
ipc::mem::block<1> *b3 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b3);
|
||||
ASSERT_TRUE (nullptr == b3->next);
|
||||
EXPECT_NE(b1, b3);
|
||||
}
|
||||
{
|
||||
auto &pool = ipc::mem::central_cache_pool<ipc::mem::block<1>, 2>::instance();
|
||||
ipc::mem::block<1> *b1 = pool.aqueire();
|
||||
ASSERT_FALSE(nullptr == b1);
|
||||
ASSERT_FALSE(nullptr == b1->next);
|
||||
ASSERT_TRUE (nullptr == b1->next->next);
|
||||
pool.release(b1);
|
||||
ipc::mem::block<1> *b2 = pool.aqueire();
|
||||
EXPECT_EQ(b1, b2);
|
||||
ipc::mem::block<1> *b3 = pool.aqueire();
|
||||
EXPECT_NE(b1, b3);
|
||||
ipc::mem::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