Bitcoin Core 28.99.0
P2P Digital Currency
pool.h
Go to the documentation of this file.
1// Copyright (c) 2022 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#ifndef BITCOIN_SUPPORT_ALLOCATORS_POOL_H
6#define BITCOIN_SUPPORT_ALLOCATORS_POOL_H
7
8#include <array>
9#include <cassert>
10#include <cstddef>
11#include <list>
12#include <memory>
13#include <new>
14#include <type_traits>
15#include <utility>
16
69template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
70class PoolResource final
71{
72 static_assert(ALIGN_BYTES > 0, "ALIGN_BYTES must be nonzero");
73 static_assert((ALIGN_BYTES & (ALIGN_BYTES - 1)) == 0, "ALIGN_BYTES must be a power of two");
74
78 struct ListNode {
80
81 explicit ListNode(ListNode* next) : m_next(next) {}
82 };
83 static_assert(std::is_trivially_destructible_v<ListNode>, "Make sure we don't need to manually call a destructor");
84
88 static constexpr std::size_t ELEM_ALIGN_BYTES = std::max(alignof(ListNode), ALIGN_BYTES);
89 static_assert((ELEM_ALIGN_BYTES & (ELEM_ALIGN_BYTES - 1)) == 0, "ELEM_ALIGN_BYTES must be a power of two");
90 static_assert(sizeof(ListNode) <= ELEM_ALIGN_BYTES, "Units of size ELEM_SIZE_ALIGN need to be able to store a ListNode");
91 static_assert((MAX_BLOCK_SIZE_BYTES & (ELEM_ALIGN_BYTES - 1)) == 0, "MAX_BLOCK_SIZE_BYTES needs to be a multiple of the alignment.");
92
96 const size_t m_chunk_size_bytes;
97
101 std::list<std::byte*> m_allocated_chunks{};
102
107 std::array<ListNode*, MAX_BLOCK_SIZE_BYTES / ELEM_ALIGN_BYTES + 1> m_free_lists{};
108
112 std::byte* m_available_memory_it = nullptr;
113
120 std::byte* m_available_memory_end = nullptr;
121
126 [[nodiscard]] static constexpr std::size_t NumElemAlignBytes(std::size_t bytes)
127 {
128 return (bytes + ELEM_ALIGN_BYTES - 1) / ELEM_ALIGN_BYTES + (bytes == 0);
129 }
130
134 [[nodiscard]] static constexpr bool IsFreeListUsable(std::size_t bytes, std::size_t alignment)
135 {
136 return alignment <= ELEM_ALIGN_BYTES && bytes <= MAX_BLOCK_SIZE_BYTES;
137 }
138
143 {
144 node = new (p) ListNode{node};
145 }
146
154 {
155 // if there is still any available memory left, put it into the freelist.
156 size_t remaining_available_bytes = std::distance(m_available_memory_it, m_available_memory_end);
157 if (0 != remaining_available_bytes) {
159 }
160
161 void* storage = ::operator new (m_chunk_size_bytes, std::align_val_t{ELEM_ALIGN_BYTES});
162 m_available_memory_it = new (storage) std::byte[m_chunk_size_bytes];
165 }
166
170 friend class PoolResourceTester;
171
172public:
177 explicit PoolResource(std::size_t chunk_size_bytes)
179 {
180 assert(m_chunk_size_bytes >= MAX_BLOCK_SIZE_BYTES);
182 }
183
188
192 PoolResource(const PoolResource&) = delete;
196
201 {
202 for (std::byte* chunk : m_allocated_chunks) {
203 std::destroy(chunk, chunk + m_chunk_size_bytes);
204 ::operator delete ((void*)chunk, std::align_val_t{ELEM_ALIGN_BYTES});
205 }
206 }
207
212 void* Allocate(std::size_t bytes, std::size_t alignment)
213 {
214 if (IsFreeListUsable(bytes, alignment)) {
215 const std::size_t num_alignments = NumElemAlignBytes(bytes);
216 if (nullptr != m_free_lists[num_alignments]) {
217 // we've already got data in the pool's freelist, unlink one element and return the pointer
218 // to the unlinked memory. Since FreeList is trivially destructible we can just treat it as
219 // uninitialized memory.
220 return std::exchange(m_free_lists[num_alignments], m_free_lists[num_alignments]->m_next);
221 }
222
223 // freelist is empty: get one allocation from allocated chunk memory.
224 const std::ptrdiff_t round_bytes = static_cast<std::ptrdiff_t>(num_alignments * ELEM_ALIGN_BYTES);
225 if (round_bytes > m_available_memory_end - m_available_memory_it) {
226 // slow path, only happens when a new chunk needs to be allocated
228 }
229
230 // Make sure we use the right amount of bytes for that freelist (might be rounded up),
231 return std::exchange(m_available_memory_it, m_available_memory_it + round_bytes);
232 }
233
234 // Can't use the pool => use operator new()
235 return ::operator new (bytes, std::align_val_t{alignment});
236 }
237
241 void Deallocate(void* p, std::size_t bytes, std::size_t alignment) noexcept
242 {
243 if (IsFreeListUsable(bytes, alignment)) {
244 const std::size_t num_alignments = NumElemAlignBytes(bytes);
245 // put the memory block into the linked list. We can placement construct the FreeList
246 // into the memory since we can be sure the alignment is correct.
247 PlacementAddToList(p, m_free_lists[num_alignments]);
248 } else {
249 // Can't use the pool => forward deallocation to ::operator delete().
250 ::operator delete (p, std::align_val_t{alignment});
251 }
252 }
253
257 [[nodiscard]] std::size_t NumAllocatedChunks() const
258 {
259 return m_allocated_chunks.size();
260 }
261
265 [[nodiscard]] size_t ChunkSizeBytes() const
266 {
267 return m_chunk_size_bytes;
268 }
269};
270
271
275template <class T, std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES = alignof(T)>
277{
279
280 template <typename U, std::size_t M, std::size_t A>
281 friend class PoolAllocator;
282
283public:
284 using value_type = T;
286
292 {
293 }
294
295 PoolAllocator(const PoolAllocator& other) noexcept = default;
296 PoolAllocator& operator=(const PoolAllocator& other) noexcept = default;
297
298 template <class U>
300 : m_resource(other.resource())
301 {
302 }
303
308 template <typename U>
309 struct rebind {
311 };
312
316 T* allocate(size_t n)
317 {
318 return static_cast<T*>(m_resource->Allocate(n * sizeof(T), alignof(T)));
319 }
320
324 void deallocate(T* p, size_t n) noexcept
325 {
326 m_resource->Deallocate(p, n * sizeof(T), alignof(T));
327 }
328
329 ResourceType* resource() const noexcept
330 {
331 return m_resource;
332 }
333};
334
335template <class T1, class T2, std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
338{
339 return a.resource() == b.resource();
340}
341
342template <class T1, class T2, std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
345{
346 return !(a == b);
347}
348
349#endif // BITCOIN_SUPPORT_ALLOCATORS_POOL_H
Forwards all allocations/deallocations to the PoolResource.
Definition: pool.h:277
PoolResource< MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > * m_resource
Definition: pool.h:278
ResourceType * resource() const noexcept
Definition: pool.h:329
T value_type
Definition: pool.h:284
PoolAllocator & operator=(const PoolAllocator &other) noexcept=default
void deallocate(T *p, size_t n) noexcept
Forwards each call to the resource.
Definition: pool.h:324
PoolAllocator(const PoolAllocator &other) noexcept=default
PoolAllocator(ResourceType *resource) noexcept
Not explicit so we can easily construct it with the correct resource.
Definition: pool.h:290
T * allocate(size_t n)
Forwards each call to the resource.
Definition: pool.h:316
PoolAllocator(const PoolAllocator< U, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &other) noexcept
Definition: pool.h:299
A memory resource similar to std::pmr::unsynchronized_pool_resource, but optimized for node-based con...
Definition: pool.h:71
PoolResource & operator=(const PoolResource &)=delete
void PlacementAddToList(void *p, ListNode *&node)
Replaces node with placement constructed ListNode that points to the previous node.
Definition: pool.h:142
static constexpr std::size_t NumElemAlignBytes(std::size_t bytes)
How many multiple of ELEM_ALIGN_BYTES are necessary to fit bytes.
Definition: pool.h:126
PoolResource(const PoolResource &)=delete
Disable copy & move semantics, these are not supported for the resource.
size_t ChunkSizeBytes() const
Size in bytes to allocate per chunk, currently hardcoded to a fixed size.
Definition: pool.h:265
std::array< ListNode *, MAX_BLOCK_SIZE_BYTES/ELEM_ALIGN_BYTES+1 > m_free_lists
Single linked lists of all data that came from deallocating.
Definition: pool.h:107
std::byte * m_available_memory_it
Points to the beginning of available memory for carving out allocations.
Definition: pool.h:112
PoolResource & operator=(PoolResource &&)=delete
std::size_t NumAllocatedChunks() const
Number of allocated chunks.
Definition: pool.h:257
static constexpr bool IsFreeListUsable(std::size_t bytes, std::size_t alignment)
True when it is possible to make use of the freelist.
Definition: pool.h:134
void Deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept
Returns a block to the freelists, or deletes the block when it did not come from the chunks.
Definition: pool.h:241
std::byte * m_available_memory_end
Points to the end of available memory for carving out allocations.
Definition: pool.h:120
PoolResource(std::size_t chunk_size_bytes)
Construct a new PoolResource object which allocates the first chunk.
Definition: pool.h:177
~PoolResource()
Deallocates all memory allocated associated with the memory resource.
Definition: pool.h:200
static constexpr std::size_t ELEM_ALIGN_BYTES
Internal alignment value.
Definition: pool.h:88
const size_t m_chunk_size_bytes
Size in bytes to allocate per chunk.
Definition: pool.h:96
std::list< std::byte * > m_allocated_chunks
Contains all allocated pools of memory, used to free the data in the destructor.
Definition: pool.h:101
PoolResource()
Construct a new Pool Resource object, defaults to 2^18=262144 chunk size.
Definition: pool.h:187
PoolResource(PoolResource &&)=delete
void * Allocate(std::size_t bytes, std::size_t alignment)
Allocates a block of bytes.
Definition: pool.h:212
void AllocateChunk()
Allocate one full memory chunk which will be used to carve out allocations.
Definition: pool.h:153
Helper to get access to private parts of PoolResource.
Definition: messages.h:20
bool operator!=(const PoolAllocator< T1, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &a, const PoolAllocator< T2, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &b) noexcept
Definition: pool.h:343
bool operator==(const PoolAllocator< T1, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &a, const PoolAllocator< T2, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &b) noexcept
Definition: pool.h:336
The rebind struct here is mandatory because we use non type template arguments for PoolAllocator.
Definition: pool.h:309
In-place linked list of the allocations, used for the freelist.
Definition: pool.h:78
ListNode * m_next
Definition: pool.h:79
ListNode(ListNode *next)
Definition: pool.h:81
assert(!tx.IsCoinBase())