1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
10  
#ifndef BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
11  
#define BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
11  
#define BOOST_CAPY_RECYCLING_MEMORY_RESOURCE_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  

14  

15  
#include <cstddef>
15  
#include <cstddef>
16  
#include <memory_resource>
16  
#include <memory_resource>
17  
#include <mutex>
17  
#include <mutex>
18  

18  

19  
namespace boost {
19  
namespace boost {
20  
namespace capy {
20  
namespace capy {
21  

21  

22  
/** Recycling memory resource with thread-local and global pools.
22  
/** Recycling memory resource with thread-local and global pools.
23  

23  

24  
    This memory resource recycles memory blocks to reduce allocation
24  
    This memory resource recycles memory blocks to reduce allocation
25  
    overhead for coroutine frames. It maintains a thread-local pool
25  
    overhead for coroutine frames. It maintains a thread-local pool
26  
    for fast lock-free access and a global pool for cross-thread
26  
    for fast lock-free access and a global pool for cross-thread
27  
    block sharing.
27  
    block sharing.
28  

28  

29  
    Blocks are tracked by size to avoid returning undersized blocks.
29  
    Blocks are tracked by size to avoid returning undersized blocks.
30  

30  

31  
    This is the default allocator used by run_async when no allocator
31  
    This is the default allocator used by run_async when no allocator
32  
    is specified.
32  
    is specified.
33  

33  

34  
    @par Thread Safety
34  
    @par Thread Safety
35  
    Thread-safe. The thread-local pool requires no synchronization.
35  
    Thread-safe. The thread-local pool requires no synchronization.
36  
    The global pool uses a mutex for cross-thread access.
36  
    The global pool uses a mutex for cross-thread access.
37  

37  

38  
    @par Example
38  
    @par Example
39  
    @code
39  
    @code
40  
    auto* mr = get_recycling_memory_resource();
40  
    auto* mr = get_recycling_memory_resource();
41  
    run_async(ex, mr)(my_task());
41  
    run_async(ex, mr)(my_task());
42  
    @endcode
42  
    @endcode
43  

43  

44  
    @see get_recycling_memory_resource
44  
    @see get_recycling_memory_resource
45  
    @see run_async
45  
    @see run_async
46  
*/
46  
*/
47  
class recycling_memory_resource : public std::pmr::memory_resource
47  
class recycling_memory_resource : public std::pmr::memory_resource
48  
{
48  
{
49  
    struct block
49  
    struct block
50  
    {
50  
    {
51  
        block* next;
51  
        block* next;
52  
        std::size_t size;
52  
        std::size_t size;
53  
    };
53  
    };
54  

54  

55  
    struct global_pool
55  
    struct global_pool
56  
    {
56  
    {
57  
        std::mutex mtx;
57  
        std::mutex mtx;
58  
        block* head = nullptr;
58  
        block* head = nullptr;
59  

59  

60  
        ~global_pool()
60  
        ~global_pool()
61  
        {
61  
        {
62  
            while(head)
62  
            while(head)
63  
            {
63  
            {
64  
                auto p = head;
64  
                auto p = head;
65  
                head = head->next;
65  
                head = head->next;
66  
                ::operator delete(p);
66  
                ::operator delete(p);
67  
            }
67  
            }
68  
        }
68  
        }
69  

69  

70  
        void push(block* b)
70  
        void push(block* b)
71  
        {
71  
        {
72  
            std::lock_guard<std::mutex> lock(mtx);
72  
            std::lock_guard<std::mutex> lock(mtx);
73  
            b->next = head;
73  
            b->next = head;
74  
            head = b;
74  
            head = b;
75  
        }
75  
        }
76  

76  

77  
        block* pop(std::size_t n)
77  
        block* pop(std::size_t n)
78  
        {
78  
        {
79  
            std::lock_guard<std::mutex> lock(mtx);
79  
            std::lock_guard<std::mutex> lock(mtx);
80  
            block** pp = &head;
80  
            block** pp = &head;
81  
            while(*pp)
81  
            while(*pp)
82  
            {
82  
            {
83  
                if((*pp)->size >= n + sizeof(block))
83  
                if((*pp)->size >= n + sizeof(block))
84  
                {
84  
                {
85  
                    block* p = *pp;
85  
                    block* p = *pp;
86  
                    *pp = p->next;
86  
                    *pp = p->next;
87  
                    return p;
87  
                    return p;
88  
                }
88  
                }
89  
                pp = &(*pp)->next;
89  
                pp = &(*pp)->next;
90  
            }
90  
            }
91  
            return nullptr;
91  
            return nullptr;
92  
        }
92  
        }
93  
    };
93  
    };
94  

94  

95  
    struct local_pool
95  
    struct local_pool
96  
    {
96  
    {
97  
        block* head = nullptr;
97  
        block* head = nullptr;
98  

98  

99  
        ~local_pool()
99  
        ~local_pool()
100  
        {
100  
        {
101  
            while(head)
101  
            while(head)
102  
            {
102  
            {
103  
                auto p = head;
103  
                auto p = head;
104  
                head = head->next;
104  
                head = head->next;
105  
                ::operator delete(p);
105  
                ::operator delete(p);
106  
            }
106  
            }
107  
        }
107  
        }
108  

108  

109  
        void push(block* b)
109  
        void push(block* b)
110  
        {
110  
        {
111  
            b->next = head;
111  
            b->next = head;
112  
            head = b;
112  
            head = b;
113  
        }
113  
        }
114  

114  

115  
        block* pop(std::size_t n)
115  
        block* pop(std::size_t n)
116  
        {
116  
        {
117  
            block** pp = &head;
117  
            block** pp = &head;
118  
            while(*pp)
118  
            while(*pp)
119  
            {
119  
            {
120  
                if((*pp)->size >= n + sizeof(block))
120  
                if((*pp)->size >= n + sizeof(block))
121  
                {
121  
                {
122  
                    block* p = *pp;
122  
                    block* p = *pp;
123  
                    *pp = p->next;
123  
                    *pp = p->next;
124  
                    return p;
124  
                    return p;
125  
                }
125  
                }
126  
                pp = &(*pp)->next;
126  
                pp = &(*pp)->next;
127  
            }
127  
            }
128  
            return nullptr;
128  
            return nullptr;
129  
        }
129  
        }
130  
    };
130  
    };
131  

131  

132  
    static local_pool& local()
132  
    static local_pool& local()
133  
    {
133  
    {
134  
        static thread_local local_pool pool;
134  
        static thread_local local_pool pool;
135  
        return pool;
135  
        return pool;
136  
    }
136  
    }
137  

137  

138  
    static global_pool& global()
138  
    static global_pool& global()
139  
    {
139  
    {
140  
        static global_pool pool;
140  
        static global_pool pool;
141  
        return pool;
141  
        return pool;
142  
    }
142  
    }
143  

143  

144  
protected:
144  
protected:
145  
    void*
145  
    void*
146  
    do_allocate(std::size_t bytes, std::size_t) override
146  
    do_allocate(std::size_t bytes, std::size_t) override
147  
    {
147  
    {
148  
        std::size_t total = bytes + sizeof(block);
148  
        std::size_t total = bytes + sizeof(block);
149  

149  

150  
        if(auto* b = local().pop(bytes))
150  
        if(auto* b = local().pop(bytes))
151  
            return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
151  
            return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
152  

152  

153  
        if(auto* b = global().pop(bytes))
153  
        if(auto* b = global().pop(bytes))
154  
            return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
154  
            return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
155  

155  

156  
        auto* b = static_cast<block*>(::operator new(total));
156  
        auto* b = static_cast<block*>(::operator new(total));
157  
        b->next = nullptr;
157  
        b->next = nullptr;
158  
        b->size = total;
158  
        b->size = total;
159  
        return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
159  
        return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
160  
    }
160  
    }
161  

161  

162  
    void
162  
    void
163  
    do_deallocate(void* p, std::size_t, std::size_t) override
163  
    do_deallocate(void* p, std::size_t, std::size_t) override
164  
    {
164  
    {
165  
        auto* b = static_cast<block*>(
165  
        auto* b = static_cast<block*>(
166  
            static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
166  
            static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
167  
        b->next = nullptr;
167  
        b->next = nullptr;
168  
        local().push(b);
168  
        local().push(b);
169  
    }
169  
    }
170  

170  

171  
    bool
171  
    bool
172  
    do_is_equal(const memory_resource& other) const noexcept override
172  
    do_is_equal(const memory_resource& other) const noexcept override
173  
    {
173  
    {
174  
        return this == &other;
174  
        return this == &other;
175  
    }
175  
    }
176  
};
176  
};
177  

177  

178  
/** Returns pointer to the default recycling memory resource.
178  
/** Returns pointer to the default recycling memory resource.
179  

179  

180  
    The returned pointer is valid for the lifetime of the program.
180  
    The returned pointer is valid for the lifetime of the program.
181  
    This is the default allocator used by run_async.
181  
    This is the default allocator used by run_async.
182  

182  

183  
    @return Pointer to the recycling memory resource.
183  
    @return Pointer to the recycling memory resource.
184  

184  

185  
    @see recycling_memory_resource
185  
    @see recycling_memory_resource
186  
    @see run_async
186  
    @see run_async
187  
*/
187  
*/
188  
BOOST_CAPY_DECL
188  
BOOST_CAPY_DECL
189  
std::pmr::memory_resource*
189  
std::pmr::memory_resource*
190  
get_recycling_memory_resource() noexcept;
190  
get_recycling_memory_resource() noexcept;
191  

191  

192  
} // namespace capy
192  
} // namespace capy
193  
} // namespace boost
193  
} // namespace boost
194  

194  

195  
#endif
195  
#endif