Skip to content

Commit f989bd7

Browse files
committed
Add block_pool
1 parent 35a10f3 commit f989bd7

7 files changed

+411
-0
lines changed

include/libipc/mem/block_pool.h

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* \file libipc/block_pool.h
3+
* \author mutouyun ([email protected])
4+
* \brief The fixed-length memory block pool.
5+
*/
6+
#pragma once
7+
8+
#include <cstddef>
9+
10+
#include "libipc/mem/central_cache_pool.h"
11+
12+
namespace ipc {
13+
namespace mem {
14+
15+
/**
16+
* \brief Fixed-length memory block pool.
17+
* \tparam BlockSize specifies the memory block size
18+
* \tparam BlockPoolExpansion specifies the default number of blocks to expand when the block pool is exhausted
19+
*/
20+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
21+
class block_pool;
22+
23+
/// \brief General-purpose block pool for any size of memory block.
24+
/// \note This block pool can only be used to deallocate a group of memory blocks of unknown but consistent size,
25+
/// and cannot be used for memory block allocation.
26+
template <>
27+
class block_pool<0, 0> {
28+
29+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
30+
friend class block_pool;
31+
32+
/// \brief The block type.
33+
struct block_t {
34+
block_t *next;
35+
};
36+
37+
/// \brief The central cache pool type.
38+
using central_cache_pool_t = central_cache_pool<block_t, 0>;
39+
40+
public:
41+
static constexpr std::size_t block_size = 0;
42+
43+
block_pool() noexcept : cursor_(central_cache_pool_t::instance().aqueire()) {}
44+
~block_pool() noexcept {
45+
central_cache_pool_t::instance().release(cursor_);
46+
}
47+
48+
block_pool(block_pool const &) = delete;
49+
block_pool& operator=(block_pool const &) = delete;
50+
51+
block_pool(block_pool &&rhs) noexcept : cursor_(std::exchange(rhs.cursor_, nullptr)) {}
52+
block_pool &operator=(block_pool &&) noexcept = delete;
53+
54+
void deallocate(void *p) noexcept {
55+
if (p == nullptr) return;
56+
block_t *b = static_cast<block_t *>(p);
57+
b->next = cursor_;
58+
cursor_ = b;
59+
}
60+
61+
private:
62+
block_t *cursor_;
63+
};
64+
65+
/// \brief A block pool for a block of memory of a specific size.
66+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
67+
class block_pool {
68+
69+
/// \brief The block type.
70+
using block_t = block<BlockSize>;
71+
/// \brief The central cache pool type.
72+
using central_cache_pool_t = central_cache_pool<block_t, BlockPoolExpansion>;
73+
74+
/// \brief Expand the block pool when it is exhausted.
75+
block_t *expand() noexcept {
76+
return central_cache_pool_t::instance().aqueire();
77+
}
78+
79+
public:
80+
static constexpr std::size_t block_size = BlockSize;
81+
82+
block_pool() noexcept : cursor_(expand()) {}
83+
~block_pool() noexcept {
84+
central_cache_pool_t::instance().release(cursor_);
85+
}
86+
87+
block_pool(block_pool const &) = delete;
88+
block_pool& operator=(block_pool const &) = delete;
89+
90+
block_pool(block_pool &&rhs) noexcept
91+
: cursor_(std::exchange(rhs.cursor_, nullptr)) {}
92+
block_pool &operator=(block_pool &&) noexcept = delete;
93+
94+
/// \brief Used to take all memory blocks from within a general-purpose block pool.
95+
/// \note Of course, the actual memory blocks they manage must be the same size.
96+
block_pool(block_pool<0, 0> &&rhs) noexcept
97+
: cursor_(reinterpret_cast<block_t *>(std::exchange(rhs.cursor_, nullptr))) {}
98+
99+
void *allocate() noexcept {
100+
if (cursor_ == nullptr) {
101+
cursor_ = expand();
102+
if (cursor_ == nullptr) return nullptr;
103+
}
104+
block_t *p = cursor_;
105+
cursor_ = cursor_->next;
106+
return p->storage.data();
107+
}
108+
109+
void deallocate(void *p) noexcept {
110+
if (p == nullptr) return;
111+
block_t *b = static_cast<block_t *>(p);
112+
b->next = cursor_;
113+
cursor_ = b;
114+
}
115+
116+
private:
117+
block_t *cursor_;
118+
};
119+
120+
} // namespace mem
121+
} // namespace ipc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* \file libipc/central_cache_allocator.h
3+
* \author mutouyun ([email protected])
4+
* \brief The central cache allocator getter.
5+
*/
6+
#pragma once
7+
8+
#include "libipc/imp/export.h"
9+
#include "libipc/mem/polymorphic_allocator.h"
10+
11+
namespace ipc {
12+
namespace mem {
13+
14+
/// \brief Get the central cache allocator.
15+
/// \note The central cache allocator is used to allocate memory for the central cache pool.
16+
/// The underlying memory resource is a `monotonic_buffer_resource` with a fixed-size buffer.
17+
LIBIPC_EXPORT bytes_allocator &central_cache_allocator() noexcept;
18+
19+
} // namespace mem
20+
} // namespace ipc
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* \file libipc/central_cache_pool.h
3+
* \author mutouyun ([email protected])
4+
* \brief The fixed-length memory block central cache pool.
5+
*/
6+
#pragma once
7+
8+
#include <cstddef>
9+
#include <deque>
10+
#include <utility>
11+
#include <array>
12+
13+
#include "libipc/imp/byte.h"
14+
#include "libipc/concur/intrusive_stack.h"
15+
#include "libipc/mem/central_cache_allocator.h"
16+
17+
namespace ipc {
18+
namespace mem {
19+
20+
/**
21+
* \brief The block type.
22+
* \tparam BlockSize specifies the memory block size
23+
*/
24+
template <std::size_t BlockSize>
25+
union block {
26+
block *next;
27+
alignas(std::max_align_t) std::array<byte, BlockSize> storage;
28+
};
29+
30+
/**
31+
* \brief A fixed-length memory block central cache pool.
32+
* \tparam BlockT specifies the memory block type
33+
*/
34+
template <typename BlockT, std::size_t BlockPoolExpansion>
35+
class central_cache_pool {
36+
37+
/// \brief The block type, which should be a union of a pointer and a storage.
38+
using block_t = BlockT;
39+
/// \brief The chunk type, which is an array of blocks.
40+
using chunk_t = std::array<block_t, BlockPoolExpansion>;
41+
/// \brief The node type, which is used to store the block pointer.
42+
using node_t = typename concur::intrusive_stack<block_t *>::node;
43+
44+
/// \brief The central cache stack.
45+
concur::intrusive_stack<block_t *> cached_;
46+
concur::intrusive_stack<block_t *> aqueired_;
47+
48+
central_cache_pool() noexcept = default;
49+
50+
public:
51+
block_t *aqueire() noexcept {
52+
auto *n = cached_.pop();
53+
if (n != nullptr) {
54+
aqueired_.push(n);
55+
return n->value;
56+
}
57+
auto *chunk = central_cache_allocator().construct<chunk_t>();
58+
if (chunk == nullptr) {
59+
return nullptr;
60+
}
61+
for (std::size_t i = 0; i < BlockPoolExpansion - 1; ++i) {
62+
(*chunk)[i].next = &(*chunk)[i + 1];
63+
}
64+
chunk->back().next = nullptr;
65+
return chunk->data();
66+
}
67+
68+
void release(block_t *p) noexcept {
69+
if (p == nullptr) return;
70+
auto *a = aqueired_.pop();
71+
if (a == nullptr) {
72+
a = central_cache_allocator().construct<node_t>();
73+
if (a == nullptr) return;
74+
}
75+
a->value = p;
76+
cached_.push(a);
77+
}
78+
79+
/// \brief Get the singleton instance.
80+
static central_cache_pool &instance() noexcept {
81+
static central_cache_pool pool;
82+
return pool;
83+
}
84+
};
85+
86+
/// \brief A fixed-length memory block central cache pool with no default expansion size.
87+
template <typename BlockT>
88+
class central_cache_pool<BlockT, 0> {
89+
90+
/// \brief The block type, which should be a union of a pointer and a storage.
91+
using block_t = BlockT;
92+
/// \brief The node type, which is used to store the block pointer.
93+
using node_t = typename concur::intrusive_stack<block_t *>::node;
94+
95+
/// \brief The central cache stack.
96+
concur::intrusive_stack<block_t *> cached_;
97+
concur::intrusive_stack<block_t *> aqueired_;
98+
99+
central_cache_pool() noexcept = default;
100+
101+
public:
102+
block_t *aqueire() noexcept {
103+
auto *n = cached_.pop();
104+
if (n != nullptr) {
105+
aqueired_.push(n);
106+
return n->value;
107+
}
108+
// For pools with no default expansion size,
109+
// the central cache pool is only buffered, not allocated.
110+
return nullptr;
111+
}
112+
113+
void release(block_t *p) noexcept {
114+
if (p == nullptr) return;
115+
auto *a = aqueired_.pop();
116+
if (a == nullptr) {
117+
a = central_cache_allocator().construct<node_t>();
118+
if (a == nullptr) return;
119+
}
120+
a->value = p;
121+
cached_.push(a);
122+
}
123+
124+
/// \brief Get the singleton instance.
125+
static central_cache_pool &instance() noexcept {
126+
static central_cache_pool pool;
127+
return pool;
128+
}
129+
};
130+
131+
} // namespace mem
132+
} // namespace ipc
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
#include <mutex>
3+
#include <array>
4+
#include <cstddef>
5+
6+
#include "libipc/def.h"
7+
#include "libipc/imp/detect_plat.h"
8+
#include "libipc/imp/byte.h"
9+
#include "libipc/mem/polymorphic_allocator.h"
10+
#include "libipc/mem/memory_resource.h"
11+
12+
namespace ipc {
13+
namespace mem {
14+
15+
class thread_safe_resource : public monotonic_buffer_resource {
16+
public:
17+
thread_safe_resource(span<byte> buffer) noexcept
18+
: monotonic_buffer_resource(buffer) {}
19+
20+
~thread_safe_resource() noexcept {
21+
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
22+
monotonic_buffer_resource::release();
23+
}
24+
25+
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
26+
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
27+
return monotonic_buffer_resource::allocate(bytes, alignment);
28+
}
29+
30+
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
31+
LIBIPC_UNUSED std::lock_guard<std::mutex> lock(mutex_);
32+
monotonic_buffer_resource::deallocate(p, bytes, alignment);
33+
}
34+
35+
private:
36+
std::mutex mutex_;
37+
};
38+
39+
bytes_allocator &central_cache_allocator() noexcept {
40+
static std::array<byte, central_cache_default_size> buf;
41+
static thread_safe_resource res(buf);
42+
static bytes_allocator a(&res);
43+
return a;
44+
}
45+
46+
} // namespace mem
47+
} // namespace ipc

test/mem/test_mem_block_pool.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
#include "test.h"
3+
4+
#include <utility>
5+
6+
#include "libipc/mem/block_pool.h"
7+
8+
TEST(block_pool, ctor) {
9+
ASSERT_TRUE ((std::is_default_constructible<ipc::mem::block_pool<1, 1>>::value));
10+
ASSERT_FALSE((std::is_copy_constructible<ipc::mem::block_pool<1, 1>>::value));
11+
ASSERT_TRUE ((std::is_move_constructible<ipc::mem::block_pool<1, 1>>::value));
12+
ASSERT_FALSE((std::is_copy_assignable<ipc::mem::block_pool<1, 1>>::value));
13+
ASSERT_FALSE((std::is_move_assignable<ipc::mem::block_pool<1, 1>>::value));
14+
}
15+
16+
TEST(block_pool, allocate) {
17+
std::vector<void *> v;
18+
ipc::mem::block_pool<1, 1> pool;
19+
for (int i = 0; i < 100; ++i) {
20+
v.push_back(pool.allocate());
21+
}
22+
for (void *p: v) {
23+
ASSERT_FALSE(nullptr == p);
24+
pool.deallocate(p);
25+
}
26+
for (int i = 0; i < 100; ++i) {
27+
ASSERT_EQ(v[v.size() - i - 1], pool.allocate());
28+
}
29+
for (void *p: v) {
30+
pool.deallocate(p);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
#include "test.h"
3+
4+
#include <utility>
5+
6+
#include "libipc/mem/central_cache_allocator.h"
7+
8+
TEST(central_cache_allocator, allocate) {
9+
auto &a = ipc::mem::central_cache_allocator();
10+
ASSERT_FALSE(nullptr == a.allocate(1));
11+
ASSERT_FALSE(nullptr == a.allocate(10));
12+
ASSERT_FALSE(nullptr == a.allocate(100));
13+
ASSERT_FALSE(nullptr == a.allocate(1000));
14+
ASSERT_FALSE(nullptr == a.allocate(10000));
15+
}

0 commit comments

Comments
 (0)