Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
11 : #define BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 :
15 : #include <cstddef>
16 : #include <memory_resource>
17 : #include <mutex>
18 :
19 : namespace boost {
20 : namespace capy {
21 :
22 : /** Recycling memory resource with thread-local and global pools.
23 :
24 : This memory resource recycles memory blocks to reduce allocation
25 : overhead for coroutine frames. It maintains a thread-local pool
26 : for fast lock-free access and a global pool for cross-thread
27 : block sharing.
28 :
29 : Blocks are tracked by size to avoid returning undersized blocks.
30 :
31 : This is the default allocator used by run_async when no allocator
32 : is specified.
33 :
34 : @par Thread Safety
35 : Thread-safe. The thread-local pool requires no synchronization.
36 : The global pool uses a mutex for cross-thread access.
37 :
38 : @see get_recycling_memory_resource
39 : @see run_async
40 : */
41 : class recycling_memory_resource : public std::pmr::memory_resource
42 : {
43 : struct block
44 : {
45 : block* next;
46 : std::size_t size;
47 : };
48 :
49 : struct global_pool
50 : {
51 : std::mutex mtx;
52 : block* head = nullptr;
53 :
54 23 : ~global_pool()
55 : {
56 23 : while(head)
57 : {
58 0 : auto p = head;
59 0 : head = head->next;
60 0 : ::operator delete(p);
61 : }
62 23 : }
63 :
64 : void push(block* b)
65 : {
66 : std::lock_guard<std::mutex> lock(mtx);
67 : b->next = head;
68 : head = b;
69 : }
70 :
71 91 : block* pop(std::size_t n)
72 : {
73 91 : std::lock_guard<std::mutex> lock(mtx);
74 91 : block** pp = &head;
75 91 : while(*pp)
76 : {
77 0 : if((*pp)->size >= n + sizeof(block))
78 : {
79 0 : block* p = *pp;
80 0 : *pp = p->next;
81 0 : return p;
82 : }
83 0 : pp = &(*pp)->next;
84 : }
85 91 : return nullptr;
86 91 : }
87 : };
88 :
89 : struct local_pool
90 : {
91 : block* head = nullptr;
92 :
93 24 : ~local_pool()
94 : {
95 115 : while(head)
96 : {
97 91 : auto p = head;
98 91 : head = head->next;
99 91 : ::operator delete(p);
100 : }
101 24 : }
102 :
103 4362 : void push(block* b)
104 : {
105 4362 : b->next = head;
106 4362 : head = b;
107 4362 : }
108 :
109 4362 : block* pop(std::size_t n)
110 : {
111 4362 : block** pp = &head;
112 5922 : while(*pp)
113 : {
114 5831 : if((*pp)->size >= n + sizeof(block))
115 : {
116 4271 : block* p = *pp;
117 4271 : *pp = p->next;
118 4271 : return p;
119 : }
120 1560 : pp = &(*pp)->next;
121 : }
122 91 : return nullptr;
123 : }
124 : };
125 :
126 8724 : static local_pool& local()
127 : {
128 8724 : static thread_local local_pool pool;
129 8724 : return pool;
130 : }
131 :
132 91 : static global_pool& global()
133 : {
134 91 : static global_pool pool;
135 91 : return pool;
136 : }
137 :
138 : protected:
139 : void*
140 4362 : do_allocate(std::size_t bytes, std::size_t) override
141 : {
142 4362 : std::size_t total = bytes + sizeof(block);
143 :
144 4362 : if(auto* b = local().pop(bytes))
145 4271 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
146 :
147 91 : if(auto* b = global().pop(bytes))
148 0 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
149 :
150 91 : auto* b = static_cast<block*>(::operator new(total));
151 91 : b->next = nullptr;
152 91 : b->size = total;
153 91 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
154 : }
155 :
156 : void
157 4362 : do_deallocate(void* p, std::size_t, std::size_t) override
158 : {
159 4362 : auto* b = static_cast<block*>(
160 : static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
161 4362 : b->next = nullptr;
162 4362 : local().push(b);
163 4362 : }
164 :
165 : bool
166 0 : do_is_equal(const memory_resource& other) const noexcept override
167 : {
168 0 : return this == &other;
169 : }
170 : };
171 :
172 : /** Returns pointer to the default recycling memory resource.
173 :
174 : The returned pointer is valid for the lifetime of the program.
175 : This is the default allocator used by run_async.
176 :
177 : @return Pointer to the recycling memory resource.
178 :
179 : @see recycling_memory_resource
180 : @see run_async
181 : */
182 : BOOST_CAPY_DECL
183 : std::pmr::memory_resource*
184 : get_recycling_memory_resource() noexcept;
185 :
186 : } // namespace capy
187 : } // namespace boost
188 :
189 : #endif
|