scc 2025.09
SystemC components library
pool_allocator.h
1/*******************************************************************************
2 * Copyright 2020-2022 MINRES Technologies GmbH
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *******************************************************************************/
16
17#ifndef _UTIL_POOL_ALLOCATOR_H_
18#define _UTIL_POOL_ALLOCATOR_H_
19
20#include "ities.h"
21#include <algorithm>
22#include <array>
23#include <deque>
24#include <mutex>
25#include <unordered_map>
26#include <vector>
27#ifdef HAVE_GETENV
28#include <cstdlib>
29#endif
30
31#if defined(_MSC_VER) || defined(__APPLE__)
32#define NOEXCEPT
33#else
34#define NOEXCEPT _GLIBCXX_USE_NOEXCEPT
35#endif
41namespace util {
43template <size_t ELEM_SIZE, unsigned CHUNK_SIZE = 4096> class pool_allocator {
44public:
51 void* allocate(uint64_t id = 0);
58 void free(void* p);
64 void resize();
78 size_t get_capacity();
81
82private:
83 pool_allocator() = default;
84 using chunk_type = uint8_t[ELEM_SIZE];
85 std::vector<std::array<chunk_type, CHUNK_SIZE>*> chunks{};
86 std::deque<void*> free_list{};
87 std::unordered_map<void*, uint64_t> used_blocks{};
88#ifdef HAVE_GETENV
89 const bool debug_memory{getenv("TLM_MM_CHECK") != nullptr};
90#else
91 const bool debug_memory{false};
92#endif
93};
94
95template <typename T> class stl_pool_allocator {
96public:
97 typedef T value_type;
98 typedef value_type* pointer;
99 typedef const value_type* const_pointer;
100 typedef value_type& reference;
101 typedef const value_type& const_reference;
102 typedef std::size_t size_type;
103 typedef std::ptrdiff_t difference_type;
104 // convert an allocator<T> to allocator<U> e.g. for std::map from A to _Node<A>
105 template <typename U> struct rebind { typedef stl_pool_allocator<U> other; };
106
107 stl_pool_allocator() = default;
108
109 stl_pool_allocator(stl_pool_allocator const&) noexcept {}
110
111 template <typename T2> stl_pool_allocator(stl_pool_allocator<T2> const&) noexcept {}
112
113 ~stl_pool_allocator() NOEXCEPT {}
114
115 // address
116 pointer address(reference r) { return std::addressof(r); }
117 const_pointer address(const_reference r) { return std::addressof(r); }
118
119 pointer allocate(size_type n, const void* = 0) {
120 if(n > std::numeric_limits<std::size_t>::max() / sizeof(value_type))
121 throw std::bad_array_new_length();
122 switch(util::ilog2(n)) {
123 case 0:
124 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type)>::get().allocate());
125 case 1:
126 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 2>::get().allocate());
127 case 2:
128 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 4>::get().allocate());
129 case 3:
130 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 8>::get().allocate());
131 case 4:
132 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 16>::get().allocate());
133 case 5:
134 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 32>::get().allocate());
135 case 6:
136 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 64>::get().allocate());
137 case 7:
138 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 128>::get().allocate());
139 case 8:
140 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 256>::get().allocate());
141 case 9:
142 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 512, 2048>::get().allocate());
143 case 10:
144 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 1024, 1024>::get().allocate());
145 case 11:
146 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 2048, 512>::get().allocate());
147 case 12:
148 return static_cast<value_type*>(util::pool_allocator<sizeof(value_type) * 4096, 256>::get().allocate());
149 default:
150 if(auto p = static_cast<value_type*>(std::malloc(n * sizeof(value_type))))
151 return p;
152 throw std::bad_alloc();
153 }
154 }
155
156 void deallocate(T* p, size_type n) noexcept {
157 switch(util::ilog2(n)) {
158 case 0:
159 return util::pool_allocator<sizeof(value_type)>::get().free(p);
160 case 1:
161 return util::pool_allocator<sizeof(value_type) * 2>::get().free(p);
162 case 2:
163 return util::pool_allocator<sizeof(value_type) * 4>::get().free(p);
164 case 3:
165 return util::pool_allocator<sizeof(value_type) * 8>::get().free(p);
166 case 4:
167 return util::pool_allocator<sizeof(value_type) * 16>::get().free(p);
168 case 5:
169 return util::pool_allocator<sizeof(value_type) * 32>::get().free(p);
170 case 6:
171 return util::pool_allocator<sizeof(value_type) * 64>::get().free(p);
172 case 7:
173 return util::pool_allocator<sizeof(value_type) * 128>::get().free(p);
174 case 8:
175 return util::pool_allocator<sizeof(value_type) * 256>::get().free(p);
176 case 9:
177 return util::pool_allocator<sizeof(value_type) * 512>::get().free(p);
178 case 10:
179 return util::pool_allocator<sizeof(value_type) * 1024>::get().free(p);
180 case 11:
181 return util::pool_allocator<sizeof(value_type) * 2048>::get().free(p);
182 case 12:
183 return util::pool_allocator<sizeof(value_type) * 4096>::get().free(p);
184 default:
185 std::free(p);
186 }
187 }
188
189 size_type max_size() const noexcept { return std::numeric_limits<size_type>::max() / sizeof(T); }
190
191 bool operator==(stl_pool_allocator const&) { return true; }
192 bool operator!=(stl_pool_allocator const& oAllocator) { return !operator==(oAllocator); }
193};
194
196 thread_local pool_allocator inst;
197 return inst;
198}
199
200template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> pool_allocator<ELEM_SIZE, CHUNK_SIZE>::~pool_allocator() {
201#ifdef HAVE_GETENV
202 if(debug_memory) {
203 auto* check = getenv("TLM_MM_CHECK");
204 auto diff = get_capacity() - get_free_entries_count();
205 if(diff) {
206 std::cerr << __FUNCTION__ << ": detected memory leak upon destruction, " << diff << " of " << get_capacity()
207 << " entries are not free'd" << std::endl;
208#ifdef _MSC_VER
209 if(check && _stricmp(check, "DEBUG") == 0) {
210#else
211 if(check && strcasecmp(check, "DEBUG") == 0) {
212#endif
213 std::vector<std::pair<void*, uint64_t>> elems(used_blocks.begin(), used_blocks.end());
214 std::sort(elems.begin(), elems.end(), [](std::pair<void*, uint64_t> const& a, std::pair<void*, uint64_t> const& b) -> bool {
215 return a.second == b.second ? a.first < b.first : a.second < b.second;
216 });
217 std::cerr << "The 10 blocks with smallest id are:\n";
218 for(size_t i = 0; i < std::min<decltype(i)>(10UL, elems.size()); ++i) {
219 std::cerr << "\taddr=" << elems[i].first << ", id=" << elems[i].second << "\n";
220 }
221 }
222 }
223 }
224#endif
225 for(auto p : chunks)
226 delete p;
227}
228
229template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void* pool_allocator<ELEM_SIZE, CHUNK_SIZE>::allocate(uint64_t id) {
230 if(!free_list.size())
231 resize();
232 auto ret = free_list.back();
233 free_list.pop_back();
234 memset(ret, 0, ELEM_SIZE);
235 if(debug_memory)
236 used_blocks.insert({ret, id});
237 return ret;
238}
239
240template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void pool_allocator<ELEM_SIZE, CHUNK_SIZE>::free(void* p) {
241 if(p) {
242 free_list.push_back(p);
243 if(debug_memory)
244 used_blocks.erase(p);
245 }
246}
247
248template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void pool_allocator<ELEM_SIZE, CHUNK_SIZE>::resize() {
249 auto* chunk = new std::array<chunk_type, CHUNK_SIZE>();
250 chunks.push_back(chunk);
251 for(auto& p : *chunk)
252 free_list.push_back(&p[0]);
253}
254
255template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline size_t pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get_capacity() {
256 return chunks.size() * CHUNK_SIZE;
257}
258
259template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline size_t pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get_free_entries_count() {
260 return free_list.size();
261}
262} // namespace util
264#endif /* _UTIL_POOL_ALLOCATOR_H_ */
pool_allocator & operator=(const pool_allocator &)=delete
deleted assignment operator
pool_allocator(const pool_allocator &)=delete
deleted constructor
void resize()
add CHUNK_SIZE elements to the pool
static pool_allocator & get()
pool allocator getter
pool_allocator & operator=(pool_allocator &&)=delete
deleted assignment operator
pool_allocator(pool_allocator &&)=delete
deleted constructor
size_t get_capacity()
get the number of allocated bytes
void free(void *p)
pit the memory back into the pool
size_t get_free_entries_count()
get the number of free elements
~pool_allocator()
deleted destructor
SCC common utilities.
Definition bit_field.h:30
CONSTEXPR unsigned ilog2(uint32_t val)
Definition ities.h:235