libstdc++
memory_resource
Go to the documentation of this file.
1 // <memory_resource> -*- C++ -*-
2 
3 // Copyright (C) 2018-2024 Free Software Foundation, Inc.
4 //
5 // This file is part of the GNU ISO C++ Library. This library is free
6 // software; you can redistribute it and/or modify it under the
7 // terms of the GNU General Public License as published by the
8 // Free Software Foundation; either version 3, or (at your option)
9 // any later version.
10 
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 
16 // Under Section 7 of GPL version 3, you are granted additional
17 // permissions described in the GCC Runtime Library Exception, version
18 // 3.1, as published by the Free Software Foundation.
19 
20 // You should have received a copy of the GNU General Public License and
21 // a copy of the GCC Runtime Library Exception along with this program;
22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
23 // <http://www.gnu.org/licenses/>.
24 
25 /** @file include/memory_resource
26  * This is a Standard C++ Library header.
27  *
28  * This header declares the @ref pmr (std::pmr) memory resources.
29  * @ingroup pmr
30  */
31 
32 #ifndef _GLIBCXX_MEMORY_RESOURCE
33 #define _GLIBCXX_MEMORY_RESOURCE 1
34 
35 #pragma GCC system_header
36 
37 #include <bits/requires_hosted.h> // polymorphic allocation
38 
39 #define __glibcxx_want_polymorphic_allocator
40 #define __glibcxx_want_memory_resource
41 #include <bits/version.h>
42 
43 #if __cplusplus >= 201703L
44 
45 /**
46  * @defgroup pmr Polymorphic memory resources
47  *
48  * @anchor pmr
49  * @ingroup memory
50  * @since C++17
51  *
52  * Memory resources are classes that implement the `std::pmr::memory_resource`
53  * interface for allocating and deallocating memory. Unlike traditional C++
54  * allocators, memory resources are not value types and are used via pointers
55  * to the abstract base class. They are only responsible for allocating and
56  * deallocating, not for construction and destruction of objects. As a result,
57  * memory resources just allocate raw memory as type `void*` and are not
58  * templates that allocate/deallocate and construct/destroy a specific type.
59  *
60  * The class template `std::pmr::polymorphic_allocator` is an allocator that
61  * uses a memory resource for its allocations.
62  */
63 
64 #include <bits/memory_resource.h>
65 #include <vector> // vector
66 #include <shared_mutex> // shared_mutex
67 #include <bits/align.h> // align
68 #include <debug/assertions.h>
69 
70 namespace std _GLIBCXX_VISIBILITY(default)
71 {
72 _GLIBCXX_BEGIN_NAMESPACE_VERSION
73 namespace pmr
74 {
75 
76 #ifdef __cpp_lib_polymorphic_allocator // C++ >= 20 && HOSTED
77  template<typename _Tp = std::byte>
78  class polymorphic_allocator;
79 #endif
80 
81  // Global memory resources
82 
83  /// A pmr::memory_resource that uses `new` to allocate memory
84  /**
85  * @ingroup pmr
86  * @headerfile memory_resource
87  * @since C++17
88  */
89  [[nodiscard, __gnu__::__returns_nonnull__, __gnu__::__const__]]
90  memory_resource*
91  new_delete_resource() noexcept;
92 
93  /// A pmr::memory_resource that always throws `bad_alloc`
94  [[nodiscard, __gnu__::__returns_nonnull__, __gnu__::__const__]]
95  memory_resource*
96  null_memory_resource() noexcept;
97 
98  /// Replace the default memory resource pointer
99  [[__gnu__::__returns_nonnull__]]
100  memory_resource*
101  set_default_resource(memory_resource* __r) noexcept;
102 
103  /// Get the current default memory resource pointer
104  [[__gnu__::__returns_nonnull__]]
105  memory_resource*
106  get_default_resource() noexcept;
107 
108  // Pool resource classes
109  struct pool_options;
110 #if __cpp_lib_memory_resource >= 201603L // C++ >= 17 && hosted && gthread
111  class synchronized_pool_resource;
112 #endif
113  class unsynchronized_pool_resource;
114  class monotonic_buffer_resource;
115 
116  /// Parameters for tuning a pool resource's behaviour.
117  /**
118  * @ingroup pmr
119  * @headerfile memory_resource
120  * @since C++17
121  */
122  struct pool_options
123  {
124  /** @brief Upper limit on number of blocks in a chunk.
125  *
126  * A lower value prevents allocating huge chunks that could remain mostly
127  * unused, but means pools will need to replenished more frequently.
128  */
129  size_t max_blocks_per_chunk = 0;
130 
131  /* @brief Largest block size (in bytes) that should be served from pools.
132  *
133  * Larger allocations will be served directly by the upstream resource,
134  * not from one of the pools managed by the pool resource.
135  */
136  size_t largest_required_pool_block = 0;
137  };
138 
139  // Common implementation details for un-/synchronized pool resources.
140  class __pool_resource
141  {
142  friend class synchronized_pool_resource;
143  friend class unsynchronized_pool_resource;
144 
145  __pool_resource(const pool_options& __opts, memory_resource* __upstream);
146 
147  ~__pool_resource();
148 
149  __pool_resource(const __pool_resource&) = delete;
150  __pool_resource& operator=(const __pool_resource&) = delete;
151 
152  // Allocate a large unpooled block.
153  void*
154  allocate(size_t __bytes, size_t __alignment);
155 
156  // Deallocate a large unpooled block.
157  void
158  deallocate(void* __p, size_t __bytes, size_t __alignment);
159 
160 
161  // Deallocate unpooled memory.
162  void release() noexcept;
163 
164  memory_resource* resource() const noexcept
165  { return _M_unpooled.get_allocator().resource(); }
166 
167  struct _Pool;
168 
169  _Pool* _M_alloc_pools();
170 
171  const pool_options _M_opts;
172 
173  struct _BigBlock;
174  // Collection of blocks too big for any pool, sorted by address.
175  // This also stores the only copy of the upstream memory resource pointer.
176  _GLIBCXX_STD_C::pmr::vector<_BigBlock> _M_unpooled;
177 
178  const int _M_npools;
179  };
180 
181 #if __cpp_lib_memory_resource >= 201603L // C++ >= 17 && hosted && gthread
182  /// A thread-safe memory resource that manages pools of fixed-size blocks.
183  /**
184  * @ingroup pmr
185  * @headerfile memory_resource
186  * @since C++17
187  */
188  class synchronized_pool_resource : public memory_resource
189  {
190  public:
191  synchronized_pool_resource(const pool_options& __opts,
192  memory_resource* __upstream)
193  __attribute__((__nonnull__));
194 
195  synchronized_pool_resource()
196  : synchronized_pool_resource(pool_options(), get_default_resource())
197  { }
198 
199  explicit
200  synchronized_pool_resource(memory_resource* __upstream)
201  __attribute__((__nonnull__))
202  : synchronized_pool_resource(pool_options(), __upstream)
203  { }
204 
205  explicit
206  synchronized_pool_resource(const pool_options& __opts)
207  : synchronized_pool_resource(__opts, get_default_resource()) { }
208 
209  synchronized_pool_resource(const synchronized_pool_resource&) = delete;
210 
211  virtual ~synchronized_pool_resource();
212 
213  synchronized_pool_resource&
214  operator=(const synchronized_pool_resource&) = delete;
215 
216  void release();
217 
218  memory_resource*
219  upstream_resource() const noexcept
220  __attribute__((__returns_nonnull__))
221  { return _M_impl.resource(); }
222 
223  pool_options options() const noexcept { return _M_impl._M_opts; }
224 
225  protected:
226  void*
227  do_allocate(size_t __bytes, size_t __alignment) override;
228 
229  void
230  do_deallocate(void* __p, size_t __bytes, size_t __alignment) override;
231 
232  bool
233  do_is_equal(const memory_resource& __other) const noexcept override
234  { return this == &__other; }
235 
236  public:
237  // Thread-specific pools (only public for access by implementation details)
238  struct _TPools;
239 
240  private:
241  _TPools* _M_alloc_tpools(lock_guard<shared_mutex>&);
242  _TPools* _M_alloc_shared_tpools(lock_guard<shared_mutex>&);
243  auto _M_thread_specific_pools() noexcept;
244 
245  __pool_resource _M_impl;
246  __gthread_key_t _M_key;
247  // Linked list of thread-specific pools. All threads share _M_tpools[0].
248  _TPools* _M_tpools = nullptr;
249  mutable shared_mutex _M_mx;
250  };
251 #endif // __cpp_lib_memory_resource >= 201603L
252 
253  /// A non-thread-safe memory resource that manages pools of fixed-size blocks.
254  /**
255  * @ingroup pmr
256  * @headerfile memory_resource
257  * @since C++17
258  */
259  class unsynchronized_pool_resource : public memory_resource
260  {
261  public:
262  [[__gnu__::__nonnull__]]
263  unsynchronized_pool_resource(const pool_options& __opts,
264  memory_resource* __upstream);
265 
266  unsynchronized_pool_resource()
267  : unsynchronized_pool_resource(pool_options(), get_default_resource())
268  { }
269 
270  [[__gnu__::__nonnull__]]
271  explicit
272  unsynchronized_pool_resource(memory_resource* __upstream)
273  : unsynchronized_pool_resource(pool_options(), __upstream)
274  { }
275 
276  explicit
277  unsynchronized_pool_resource(const pool_options& __opts)
278  : unsynchronized_pool_resource(__opts, get_default_resource()) { }
279 
280  unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete;
281 
282  virtual ~unsynchronized_pool_resource();
283 
284  unsynchronized_pool_resource&
285  operator=(const unsynchronized_pool_resource&) = delete;
286 
287  void release();
288 
289  [[__gnu__::__returns_nonnull__]]
290  memory_resource*
291  upstream_resource() const noexcept
292  { return _M_impl.resource(); }
293 
294  pool_options options() const noexcept { return _M_impl._M_opts; }
295 
296  protected:
297  void*
298  do_allocate(size_t __bytes, size_t __alignment) override;
299 
300  void
301  do_deallocate(void* __p, size_t __bytes, size_t __alignment) override;
302 
303  bool
304  do_is_equal(const memory_resource& __other) const noexcept override
305  { return this == &__other; }
306 
307  private:
308  using _Pool = __pool_resource::_Pool;
309 
310  auto _M_find_pool(size_t) noexcept;
311 
312  __pool_resource _M_impl;
313  _Pool* _M_pools = nullptr;
314  };
315 
316  /// A memory resource that allocates from a fixed-size buffer.
317  /**
318  * The main feature of a `pmr::monotonic_buffer_resource` is that its
319  * `do_deallocate` does nothing. This makes it very fast because there is no
320  * need to manage a free list, and every allocation simply returns a new
321  * block of memory, rather than searching for a suitably-sized free block.
322  * Because deallocating is a no-op, the amount of memory used by the resource
323  * only grows until `release()` (or the destructor) is called to return all
324  * memory to upstream.
325  *
326  * A `monotonic_buffer_resource` can be initialized with a buffer that
327  * will be used to satisfy all allocation requests, until the buffer is full.
328  * After that a new buffer will be allocated from the upstream resource.
329  * By using a stack buffer and `pmr::null_memory_resource()` as the upstream
330  * you can get a memory resource that only uses the stack and never
331  * dynamically allocates.
332  *
333  * @ingroup pmr
334  * @headerfile memory_resource
335  * @since C++17
336  */
337  class monotonic_buffer_resource : public memory_resource
338  {
339  public:
340  explicit
341  monotonic_buffer_resource(memory_resource* __upstream) noexcept
342  __attribute__((__nonnull__))
343  : _M_upstream(__upstream)
344  { _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr); }
345 
346  monotonic_buffer_resource(size_t __initial_size,
347  memory_resource* __upstream) noexcept
348  __attribute__((__nonnull__))
349  : _M_next_bufsiz(__initial_size),
350  _M_upstream(__upstream)
351  {
352  _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr);
353  _GLIBCXX_DEBUG_ASSERT(__initial_size > 0);
354  }
355 
356  monotonic_buffer_resource(void* __buffer, size_t __buffer_size,
357  memory_resource* __upstream) noexcept
358  __attribute__((__nonnull__(4)))
359  : _M_current_buf(__buffer), _M_avail(__buffer_size),
360  _M_next_bufsiz(_S_next_bufsize(__buffer_size)),
361  _M_upstream(__upstream),
362  _M_orig_buf(__buffer), _M_orig_size(__buffer_size)
363  {
364  _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr);
365  _GLIBCXX_DEBUG_ASSERT(__buffer != nullptr || __buffer_size == 0);
366  }
367 
368  monotonic_buffer_resource() noexcept
369  : monotonic_buffer_resource(get_default_resource())
370  { }
371 
372  explicit
373  monotonic_buffer_resource(size_t __initial_size) noexcept
374  : monotonic_buffer_resource(__initial_size, get_default_resource())
375  { }
376 
377  monotonic_buffer_resource(void* __buffer, size_t __buffer_size) noexcept
378  : monotonic_buffer_resource(__buffer, __buffer_size, get_default_resource())
379  { }
380 
381  monotonic_buffer_resource(const monotonic_buffer_resource&) = delete;
382 
383  virtual ~monotonic_buffer_resource(); // key function
384 
385  monotonic_buffer_resource&
386  operator=(const monotonic_buffer_resource&) = delete;
387 
388  void
389  release() noexcept
390  {
391  if (_M_head)
392  _M_release_buffers();
393 
394  // reset to initial state at contruction:
395  if ((_M_current_buf = _M_orig_buf))
396  {
397  _M_avail = _M_orig_size;
398  _M_next_bufsiz = _S_next_bufsize(_M_orig_size);
399  }
400  else
401  {
402  _M_avail = 0;
403  _M_next_bufsiz = _M_orig_size;
404  }
405  }
406 
407  memory_resource*
408  upstream_resource() const noexcept
409  __attribute__((__returns_nonnull__))
410  { return _M_upstream; }
411 
412  protected:
413  void*
414  do_allocate(size_t __bytes, size_t __alignment) override
415  {
416  if (__builtin_expect(__bytes == 0, false))
417  __bytes = 1; // Ensures we don't return the same pointer twice.
418 
419  void* __p = std::align(__alignment, __bytes, _M_current_buf, _M_avail);
420  if (__builtin_expect(__p == nullptr, false))
421  {
422  _M_new_buffer(__bytes, __alignment);
423  __p = _M_current_buf;
424  }
425  _M_current_buf = (char*)_M_current_buf + __bytes;
426  _M_avail -= __bytes;
427  return __p;
428  }
429 
430  void
431  do_deallocate(void*, size_t, size_t) override
432  { }
433 
434  bool
435  do_is_equal(const memory_resource& __other) const noexcept override
436  { return this == &__other; }
437 
438  private:
439  // Update _M_current_buf and _M_avail to refer to a new buffer with
440  // at least the specified size and alignment, allocated from upstream.
441  void
442  _M_new_buffer(size_t __bytes, size_t __alignment);
443 
444  // Deallocate all buffers obtained from upstream.
445  void
446  _M_release_buffers() noexcept;
447 
448  static size_t
449  _S_next_bufsize(size_t __buffer_size) noexcept
450  {
451  if (__builtin_expect(__buffer_size == 0, false))
452  __buffer_size = 1;
453  return __buffer_size * _S_growth_factor;
454  }
455 
456  static constexpr size_t _S_init_bufsize = 128 * sizeof(void*);
457  static constexpr float _S_growth_factor = 1.5;
458 
459  void* _M_current_buf = nullptr;
460  size_t _M_avail = 0;
461  size_t _M_next_bufsiz = _S_init_bufsize;
462 
463  // Initial values set at construction and reused by release():
464  memory_resource* const _M_upstream;
465  void* const _M_orig_buf = nullptr;
466  size_t const _M_orig_size = _M_next_bufsiz;
467 
468  class _Chunk;
469  _Chunk* _M_head = nullptr;
470  };
471 
472 } // namespace pmr
473 _GLIBCXX_END_NAMESPACE_VERSION
474 } // namespace std
475 
476 #endif // C++17
477 #endif // _GLIBCXX_MEMORY_RESOURCE