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/corosio
8 : //
9 :
10 : #ifndef BOOST_CAPY_DETAIL_RECYCLING_FRAME_ALLOCATOR_HPP
11 : #define BOOST_CAPY_DETAIL_RECYCLING_FRAME_ALLOCATOR_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/ex/frame_allocator.hpp>
15 :
16 : #include <cstddef>
17 : #include <mutex>
18 :
19 : namespace boost {
20 : namespace capy {
21 : namespace detail {
22 :
23 : /** Recycling frame allocator with thread-local and global pools.
24 :
25 : This allocator recycles memory blocks to reduce allocation overhead.
26 : It maintains a thread-local pool for fast lock-free access and a
27 : global pool for cross-thread block sharing.
28 :
29 : Blocks are tracked by size to avoid returning undersized blocks.
30 :
31 : This type satisfies the frame_allocator concept and is cheaply
32 : copyable (all instances share the same static pools).
33 : */
34 : class recycling_frame_allocator
35 : {
36 : struct block
37 : {
38 : block* next;
39 : std::size_t size;
40 : };
41 :
42 : struct global_pool
43 : {
44 : std::mutex mtx;
45 : block* head = nullptr;
46 :
47 0 : ~global_pool()
48 : {
49 0 : while(head)
50 : {
51 0 : auto p = head;
52 0 : head = head->next;
53 0 : ::operator delete(p);
54 : }
55 0 : }
56 :
57 : void push(block* b)
58 : {
59 : std::lock_guard<std::mutex> lock(mtx);
60 : b->next = head;
61 : head = b;
62 : }
63 :
64 0 : block* pop(std::size_t n)
65 : {
66 0 : std::lock_guard<std::mutex> lock(mtx);
67 0 : block** pp = &head;
68 0 : while(*pp)
69 : {
70 : // block->size stores total allocated size (including header)
71 0 : if((*pp)->size >= n + sizeof(block))
72 : {
73 0 : block* p = *pp;
74 0 : *pp = p->next;
75 0 : return p;
76 : }
77 0 : pp = &(*pp)->next;
78 : }
79 0 : return nullptr;
80 0 : }
81 : };
82 :
83 : struct local_pool
84 : {
85 : block* head = nullptr;
86 :
87 0 : ~local_pool()
88 : {
89 0 : while(head)
90 : {
91 0 : auto p = head;
92 0 : head = head->next;
93 0 : ::operator delete(p);
94 : }
95 0 : }
96 :
97 0 : void push(block* b)
98 : {
99 0 : b->next = head;
100 0 : head = b;
101 0 : }
102 :
103 0 : block* pop(std::size_t n)
104 : {
105 0 : block** pp = &head;
106 0 : while(*pp)
107 : {
108 : // block->size stores total allocated size (including header)
109 0 : if((*pp)->size >= n + sizeof(block))
110 : {
111 0 : block* p = *pp;
112 0 : *pp = p->next;
113 0 : return p;
114 : }
115 0 : pp = &(*pp)->next;
116 : }
117 0 : return nullptr;
118 : }
119 : };
120 :
121 0 : static local_pool& local()
122 : {
123 0 : static thread_local local_pool local;
124 0 : return local;
125 : }
126 :
127 0 : static global_pool& global()
128 : {
129 0 : static global_pool pool;
130 0 : return pool;
131 : }
132 :
133 : public:
134 0 : void* allocate(std::size_t n)
135 : {
136 0 : std::size_t total = n + sizeof(block);
137 :
138 0 : if(auto* b = local().pop(n))
139 0 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
140 :
141 0 : if(auto* b = global().pop(n))
142 0 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
143 :
144 0 : auto* b = static_cast<block*>(::operator new(total));
145 0 : b->next = nullptr;
146 0 : b->size = total;
147 0 : return static_cast<char*>(static_cast<void*>(b)) + sizeof(block);
148 : }
149 :
150 0 : void deallocate(void* p, std::size_t)
151 : {
152 0 : auto* b = static_cast<block*>(static_cast<void*>(static_cast<char*>(p) - sizeof(block)));
153 0 : b->next = nullptr;
154 0 : local().push(b);
155 0 : }
156 : };
157 :
158 : static_assert(frame_allocator<recycling_frame_allocator>);
159 :
160 : } // namespace detail
161 : } // namespace capy
162 : } // namespace boost
163 :
164 : #endif
|