Bitcoin ABC  0.26.3
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  static_assert(ALIGN_BYTES > 0, "ALIGN_BYTES must be nonzero");
72  static_assert((ALIGN_BYTES & (ALIGN_BYTES - 1)) == 0,
73  "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>,
84  "Make sure we don't need to manually call a destructor");
85 
90  static constexpr std::size_t ELEM_ALIGN_BYTES =
91  std::max(alignof(ListNode), ALIGN_BYTES);
92  static_assert((ELEM_ALIGN_BYTES & (ELEM_ALIGN_BYTES - 1)) == 0,
93  "ELEM_ALIGN_BYTES must be a power of two");
94  static_assert(
95  sizeof(ListNode) <= ELEM_ALIGN_BYTES,
96  "Units of size ELEM_SIZE_ALIGN need to be able to store a ListNode");
97  static_assert(
98  (MAX_BLOCK_SIZE_BYTES & (ELEM_ALIGN_BYTES - 1)) == 0,
99  "MAX_BLOCK_SIZE_BYTES needs to be a multiple of the alignment.");
100 
104  const size_t m_chunk_size_bytes;
105 
110  std::list<std::byte *> m_allocated_chunks{};
111 
116  std::array<ListNode *, MAX_BLOCK_SIZE_BYTES / ELEM_ALIGN_BYTES + 1>
118 
122  std::byte *m_available_memory_it = nullptr;
123 
132  std::byte *m_available_memory_end = nullptr;
133 
139  [[nodiscard]] static constexpr std::size_t
140  NumElemAlignBytes(std::size_t bytes) {
141  return (bytes + ELEM_ALIGN_BYTES - 1) / ELEM_ALIGN_BYTES + (bytes == 0);
142  }
143 
147  [[nodiscard]] static constexpr bool
148  IsFreeListUsable(std::size_t bytes, std::size_t alignment) {
149  return alignment <= ELEM_ALIGN_BYTES && bytes <= MAX_BLOCK_SIZE_BYTES;
150  }
151 
156  void PlacementAddToList(void *p, ListNode *&node) {
157  node = new (p) ListNode{node};
158  }
159 
167  void AllocateChunk() {
168  // if there is still any available memory left, put it into the
169  // freelist.
170  size_t remaining_available_bytes =
172  if (0 != remaining_available_bytes) {
175  m_free_lists[remaining_available_bytes / ELEM_ALIGN_BYTES]);
176  }
177 
178  void *storage = ::operator new (m_chunk_size_bytes,
179  std::align_val_t{ELEM_ALIGN_BYTES});
180  m_available_memory_it = new (storage) std::byte[m_chunk_size_bytes];
183  }
184 
188  friend class PoolResourceTester;
189 
190 public:
195  explicit PoolResource(std::size_t chunk_size_bytes)
196  : m_chunk_size_bytes(NumElemAlignBytes(chunk_size_bytes) *
198  assert(m_chunk_size_bytes >= MAX_BLOCK_SIZE_BYTES);
199  AllocateChunk();
200  }
201 
205  PoolResource() : PoolResource(262144) {}
206 
210  PoolResource(const PoolResource &) = delete;
211  PoolResource &operator=(const PoolResource &) = delete;
214 
219  for (std::byte *chunk : m_allocated_chunks) {
220  std::destroy(chunk, chunk + m_chunk_size_bytes);
221  ::operator delete ((void *)chunk,
222  std::align_val_t{ELEM_ALIGN_BYTES});
223  }
224  }
225 
230  void *Allocate(std::size_t bytes, std::size_t alignment) {
231  if (IsFreeListUsable(bytes, alignment)) {
232  const std::size_t num_alignments = NumElemAlignBytes(bytes);
233  if (nullptr != m_free_lists[num_alignments]) {
234  // we've already got data in the pool's freelist, unlink one
235  // element and return the pointer to the unlinked memory. Since
236  // FreeList is trivially destructible we can just treat it as
237  // uninitialized memory.
238  return std::exchange(m_free_lists[num_alignments],
239  m_free_lists[num_alignments]->m_next);
240  }
241 
242  // freelist is empty: get one allocation from allocated chunk
243  // memory.
244  const std::ptrdiff_t round_bytes =
245  static_cast<std::ptrdiff_t>(num_alignments * ELEM_ALIGN_BYTES);
246  if (round_bytes > m_available_memory_end - m_available_memory_it) {
247  // slow path, only happens when a new chunk needs to be
248  // allocated
249  AllocateChunk();
250  }
251 
252  // Make sure we use the right amount of bytes for that freelist
253  // (might be rounded up),
254  return std::exchange(m_available_memory_it,
255  m_available_memory_it + round_bytes);
256  }
257 
258  // Can't use the pool => use operator new()
259  return ::operator new (bytes, std::align_val_t{alignment});
260  }
261 
266  void Deallocate(void *p, std::size_t bytes,
267  std::size_t alignment) noexcept {
268  if (IsFreeListUsable(bytes, alignment)) {
269  const std::size_t num_alignments = NumElemAlignBytes(bytes);
270  // put the memory block into the linked list. We can placement
271  // construct the FreeList into the memory since we can be sure the
272  // alignment is correct.
273  PlacementAddToList(p, m_free_lists[num_alignments]);
274  } else {
275  // Can't use the pool => forward deallocation to ::operator
276  // delete().
277  ::operator delete (p, std::align_val_t{alignment});
278  }
279  }
280 
284  [[nodiscard]] std::size_t NumAllocatedChunks() const {
285  return m_allocated_chunks.size();
286  }
287 
291  [[nodiscard]] size_t ChunkSizeBytes() const { return m_chunk_size_bytes; }
292 };
293 
297 template <class T, std::size_t MAX_BLOCK_SIZE_BYTES,
298  std::size_t ALIGN_BYTES = alignof(T)>
301 
302  template <typename U, std::size_t M, std::size_t A>
303  friend class PoolAllocator;
304 
305 public:
306  using value_type = T;
308 
312  PoolAllocator(ResourceType *resource) noexcept : m_resource(resource) {}
313 
314  PoolAllocator(const PoolAllocator &other) noexcept = default;
315  PoolAllocator &operator=(const PoolAllocator &other) noexcept = default;
316 
317  template <class U>
319  &other) noexcept
320  : m_resource(other.resource()) {}
321 
327  template <typename U> struct rebind {
329  };
330 
334  T *allocate(size_t n) {
335  return static_cast<T *>(
336  m_resource->Allocate(n * sizeof(T), alignof(T)));
337  }
338 
342  void deallocate(T *p, size_t n) noexcept {
343  m_resource->Deallocate(p, n * sizeof(T), alignof(T));
344  }
345 
346  ResourceType *resource() const noexcept { return m_resource; }
347 };
348 
349 template <class T1, class T2, std::size_t MAX_BLOCK_SIZE_BYTES,
350  std::size_t ALIGN_BYTES>
354  return a.resource() == b.resource();
355 }
356 
357 template <class T1, class T2, std::size_t MAX_BLOCK_SIZE_BYTES,
358  std::size_t ALIGN_BYTES>
362  return !(a == b);
363 }
364 
365 #endif // BITCOIN_SUPPORT_ALLOCATORS_POOL_H
Forwards all allocations/deallocations to the PoolResource.
Definition: pool.h:299
PoolAllocator & operator=(const PoolAllocator &other) noexcept=default
PoolResource< MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > * m_resource
Definition: pool.h:300
T * allocate(size_t n)
Forwards each call to the resource.
Definition: pool.h:334
T value_type
Definition: pool.h:306
friend class PoolAllocator
Definition: pool.h:303
void deallocate(T *p, size_t n) noexcept
Forwards each call to the resource.
Definition: pool.h:342
PoolAllocator(const PoolAllocator &other) noexcept=default
ResourceType * resource() const noexcept
Definition: pool.h:346
PoolAllocator(ResourceType *resource) noexcept
Not explicit so we can easily construct it with the correct resource.
Definition: pool.h:312
PoolAllocator(const PoolAllocator< U, MAX_BLOCK_SIZE_BYTES, ALIGN_BYTES > &other) noexcept
Definition: pool.h:318
A memory resource similar to std::pmr::unsynchronized_pool_resource, but optimized for node-based con...
Definition: pool.h:70
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:117
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:156
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:140
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:291
std::byte * m_available_memory_it
Points to the beginning of available memory for carving out allocations.
Definition: pool.h:122
std::size_t NumAllocatedChunks() const
Number of allocated chunks.
Definition: pool.h:284
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:148
friend class PoolResourceTester
Access to internals for testing purpose only.
Definition: pool.h:188
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:266
std::byte * m_available_memory_end
Points to the end of available memory for carving out allocations.
Definition: pool.h:132
PoolResource(std::size_t chunk_size_bytes)
Construct a new PoolResource object which allocates the first chunk.
Definition: pool.h:195
~PoolResource()
Deallocates all memory allocated associated with the memory resource.
Definition: pool.h:218
static constexpr std::size_t ELEM_ALIGN_BYTES
Internal alignment value.
Definition: pool.h:90
const size_t m_chunk_size_bytes
Size in bytes to allocate per chunk.
Definition: pool.h:93
PoolResource()
Construct a new Pool Resource object, defaults to 2^18=262144 chunk size.
Definition: pool.h:205
PoolResource(PoolResource &&)=delete
void * Allocate(std::size_t bytes, std::size_t alignment)
Allocates a block of bytes.
Definition: pool.h:230
std::list< std::byte * > m_allocated_chunks
Contains all allocated pools of memory, used to free the data in the destructor.
Definition: pool.h:110
PoolResource & operator=(const PoolResource &)=delete
void AllocateChunk()
Allocate one full memory chunk which will be used to carve out allocations.
Definition: pool.h:167
Definition: init.h:28
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:359
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:351
The rebind struct here is mandatory because we use non type template arguments for PoolAllocator.
Definition: pool.h:327
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())