Bitcoin Core  27.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 
69 template <std::size_t MAX_BLOCK_SIZE_BYTES, std::size_t ALIGN_BYTES>
70 class 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 
172 public:
177  explicit PoolResource(std::size_t chunk_size_bytes)
179  {
180  assert(m_chunk_size_bytes >= MAX_BLOCK_SIZE_BYTES);
181  AllocateChunk();
182  }
183 
187  PoolResource() : PoolResource(262144) {}
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
227  AllocateChunk();
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 
275 template <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 
283 public:
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 
335 template <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 
342 template <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
PoolAllocator & operator=(const PoolAllocator &other) noexcept=default
PoolResource< MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > * m_resource
Definition: pool.h:278
T * allocate(size_t n)
Forwards each call to the resource.
Definition: pool.h:316
T value_type
Definition: pool.h:284
void deallocate(T *p, size_t n) noexcept
Forwards each call to the resource.
Definition: pool.h:324
PoolAllocator(const PoolAllocator &other) noexcept=default
ResourceType * resource() const noexcept
Definition: pool.h:329
PoolAllocator(ResourceType *resource) noexcept
Not explicit so we can easily construct it with the correct resource.
Definition: pool.h:290
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=(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
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:89
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
PoolResource & operator=(const PoolResource &)=delete
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: init.h:25
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())