scc  2024.06
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
41 namespace util {
43 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE = 4096> class pool_allocator {
44 public:
51  void* allocate(uint64_t id = 0);
58  void free(void* p);
64  void resize();
66  pool_allocator(const pool_allocator&) = delete;
76  static pool_allocator& get();
78  size_t get_capacity();
81 
82 private:
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 
95 template <typename T> class stl_pool_allocator {
96 public:
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 
195 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> pool_allocator<ELEM_SIZE, CHUNK_SIZE>& pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get() {
196  thread_local pool_allocator inst;
197  return inst;
198 }
199 
200 template <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 
229 template <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 
240 template <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 
248 template <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 
255 template <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 
259 template <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_ */
a generic pool allocator singleton not being MT-safe
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(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
pool_allocator & operator=(pool_allocator &&)=delete
deleted assignment operator
size_t get_free_entries_count()
get the number of free elements
~pool_allocator()
deleted destructor
pool_allocator & operator=(const pool_allocator &)=delete
deleted assignment operator
SCC common utilities.
Definition: bit_field.h:30
CONSTEXPR unsigned ilog2(uint32_t val)
Definition: ities.h:194