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