libs/capy/include/boost/capy/ex/recycling_memory_resource.hpp

81.4% Lines (48/59) 90.0% Functions (9/10) 73.9% Branches (17/23)
libs/capy/include/boost/capy/ex/recycling_memory_resource.hpp
Line Branch Hits 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
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 while(head)
57 {
58 auto p = head;
59 head = head->next;
60 ::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
1/1
✓ Branch 1 taken 91 times.
91 std::lock_guard<std::mutex> lock(mtx);
74 91 block** pp = &head;
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
91 while(*pp)
76 {
77 if((*pp)->size >= n + sizeof(block))
78 {
79 block* p = *pp;
80 *pp = p->next;
81 return p;
82 }
83 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
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 24 times.
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
2/2
✓ Branch 0 taken 5831 times.
✓ Branch 1 taken 91 times.
5922 while(*pp)
113 {
114
2/2
✓ Branch 0 taken 4271 times.
✓ Branch 1 taken 1560 times.
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
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 8700 times.
8724 static thread_local local_pool pool;
129 8724 return pool;
130 }
131
132 91 static global_pool& global()
133 {
134
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 68 times.
✓ Branch 3 taken 23 times.
✗ Branch 4 not taken.
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
2/2
✓ Branch 2 taken 4271 times.
✓ Branch 3 taken 91 times.
4362 if(auto* b = local().pop(bytes))
145 4271 return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
146
147
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 91 times.
91 if(auto* b = global().pop(bytes))
148 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 do_is_equal(const memory_resource& other) const noexcept override
167 {
168 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
190