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 : @par Example
39 : @code
40 : auto* mr = get_recycling_memory_resource();
41 : run_async(ex, mr)(my_task());
42 : @endcode
43 :
44 : @see get_recycling_memory_resource
45 : @see run_async
46 : */
47 : class recycling_memory_resource : public std::pmr::memory_resource
48 : {
49 : struct block
50 : {
51 : block* next;
52 : std::size_t size;
53 : };
54 :
55 : struct global_pool
56 : {
57 : std::mutex mtx;
58 : block* head = nullptr;
59 :
60 23 : ~global_pool()
61 : {
62 23 : while(head)
63 : {
64 0 : auto p = head;
65 0 : head = head->next;
66 0 : ::operator delete(p);
67 : }
68 23 : }
69 :
70 : void push(block* b)
71 : {
72 : std::lock_guard<std::mutex> lock(mtx);
73 : b->next = head;
74 : head = b;
75 : }
76 :
77 94 : block* pop(std::size_t n)
78 : {
79 94 : std::lock_guard<std::mutex> lock(mtx);
80 94 : block** pp = &head;
81 94 : while(*pp)
82 : {
83 0 : if((*pp)->size >= n + sizeof(block))
84 : {
85 0 : block* p = *pp;
86 0 : *pp = p->next;
87 0 : return p;
88 : }
89 0 : pp = &(*pp)->next;
90 : }
91 94 : return nullptr;
92 94 : }
93 : };
94 :
95 : struct local_pool
96 : {
97 : block* head = nullptr;
98 :
99 24 : ~local_pool()
100 : {
101 118 : while(head)
102 : {
103 94 : auto p = head;
104 94 : head = head->next;
105 94 : ::operator delete(p);
106 : }
107 24 : }
108 :
109 4389 : void push(block* b)
110 : {
111 4389 : b->next = head;
112 4389 : head = b;
113 4389 : }
114 :
115 4389 : block* pop(std::size_t n)
116 : {
117 4389 : block** pp = &head;
118 5960 : while(*pp)
119 : {
120 5866 : if((*pp)->size >= n + sizeof(block))
121 : {
122 4295 : block* p = *pp;
123 4295 : *pp = p->next;
124 4295 : return p;
125 : }
126 1571 : pp = &(*pp)->next;
127 : }
128 94 : return nullptr;
129 : }
130 : };
131 :
132 8778 : static local_pool& local()
133 : {
134 8778 : static thread_local local_pool pool;
135 8778 : return pool;
136 : }
137 :
138 94 : static global_pool& global()
139 : {
140 94 : static global_pool pool;
141 94 : return pool;
142 : }
143 :
144 : protected:
145 : void*
146 4389 : do_allocate(std::size_t bytes, std::size_t) override
147 : {
148 4389 : std::size_t total = bytes + sizeof(block);
149 :
150 4389 : if(auto* b = local().pop(bytes))
151 4295 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
152 :
153 94 : if(auto* b = global().pop(bytes))
154 0 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
155 :
156 94 : auto* b = static_cast<block*>(::operator new(total));
157 94 : b->next = nullptr;
158 94 : b->size = total;
159 94 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
160 : }
161 :
162 : void
163 4389 : do_deallocate(void* p, std::size_t, std::size_t) override
164 : {
165 4389 : auto* b = static_cast<block*>(
166 : static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
167 4389 : b->next = nullptr;
168 4389 : local().push(b);
169 4389 : }
170 :
171 : bool
172 0 : do_is_equal(const memory_resource& other) const noexcept override
173 : {
174 0 : return this == &other;
175 : }
176 : };
177 :
178 : /** Returns pointer to the default recycling memory resource.
179 :
180 : The returned pointer is valid for the lifetime of the program.
181 : This is the default allocator used by run_async.
182 :
183 : @return Pointer to the recycling memory resource.
184 :
185 : @see recycling_memory_resource
186 : @see run_async
187 : */
188 : BOOST_CAPY_DECL
189 : std::pmr::memory_resource*
190 : get_recycling_memory_resource() noexcept;
191 :
192 : } // namespace capy
193 : } // namespace boost
194 :
195 : #endif
|