scc  2022.4.0
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 <algorithm>
21 #include <array>
22 #include <deque>
23 #include <mutex>
24 #include <unordered_map>
25 #include <vector>
26 #ifdef HAVE_GETENV
27 #include <cstdlib>
28 #endif
29 
30 #ifdef _MSC_VER
31 #define NOEXCEPT
32 #else
33 #define NOEXCEPT _GLIBCXX_USE_NOEXCEPT
34 #endif
40 namespace util {
42 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE = 4096> class pool_allocator {
43 public:
50  void* allocate(uint64_t id = 0);
57  void free(void* p);
63  void resize();
65  pool_allocator(const pool_allocator&) = delete;
75  static pool_allocator& get();
77  size_t get_capacity();
80 
81 private:
82  pool_allocator() = default;
83  using chunk_type = uint8_t[ELEM_SIZE];
84  std::vector<std::array<chunk_type, CHUNK_SIZE>*> chunks{};
85  std::deque<void*> free_list{};
86  std::unordered_map<void*, uint64_t> used_blocks{};
87 #ifdef HAVE_GETENV
88  const bool debug_memory{getenv("TLM_MM_CHECK") != nullptr};
89 #else
90  const bool debug_memory{false};
91 #endif
92 };
93 
94 template <typename T> class stl_pool_allocator {
95 public:
96  typedef T value_type;
97  typedef value_type* pointer;
98  typedef const value_type* const_pointer;
99  typedef value_type& reference;
100  typedef const value_type& const_reference;
101  typedef std::size_t size_type;
102  typedef std::ptrdiff_t difference_type;
103  // convert an allocator<T> to allocator<U> e.g. for std::map from A to _Node<A>
104  template <typename U> struct rebind { typedef stl_pool_allocator<U> other; };
105 
106  stl_pool_allocator(const stl_pool_allocator&) noexcept {}
107 
108  template <typename T2> stl_pool_allocator(const stl_pool_allocator<T2>&) noexcept {}
109 
110  ~stl_pool_allocator() NOEXCEPT {}
111 
112  // address
113  pointer address(reference r) { return std::addressof(r); }
114  const_pointer address(const_reference r) { return std::addressof(r); }
115 
116  pointer allocate(size_type n, const void* = 0) {
117  size_type value = 16;
118  while(value < n)
119  value <<= 2;
120  switch(n) {
121  case 16:
122  return static_cast<T*>(util::pool_allocator<16 * sizeof(T), 65536>::get().allocate());
123  case 64:
124  return static_cast<T*>(util::pool_allocator<64 * sizeof(T), 16384>::get().allocate());
125  case 256:
126  return static_cast<T*>(util::pool_allocator<256 * sizeof(T), 4096>::get().allocate());
127  case 1024:
128  return static_cast<T*>(util::pool_allocator<1024 * sizeof(T), 1024>::get().allocate());
129  case 4096:
130  return static_cast<T*>(util::pool_allocator<4096 * sizeof(T), 256>::get().allocate());
131  case 16384:
132  return static_cast<T*>(util::pool_allocator<16384 * sizeof(T), 64>::get().allocate());
133  default:
134  return static_cast<T*>(::operator new(n * sizeof(T)));
135  }
136  }
137 
138  void deallocate(T* p, size_type n) noexcept {
139  size_type value = 16;
140  while(value < n)
141  value <<= 2;
142  switch(n) {
143  case 16:
144  util::pool_allocator<16 * sizeof(T), 65536>::get().free(p);
145  break;
146  case 64:
147  util::pool_allocator<64 * sizeof(T), 16384>::get().free(p);
148  break;
149  case 256:
150  util::pool_allocator<256 * sizeof(T), 4096>::get().free(p);
151  break;
152  case 1024:
153  util::pool_allocator<1024 * sizeof(T), 1024>::get().free(p);
154  break;
155  case 4096:
156  util::pool_allocator<4096 * sizeof(T), 256>::get().free(p);
157  break;
158  case 16384:
159  util::pool_allocator<16384 * sizeof(T), 64>::get().free(p);
160  break;
161  default:
162  ::operator delete(p);
163  }
164  }
165  size_type max_size() const noexcept { return std::numeric_limits<size_type>::max() / sizeof(T); }
166 
167  bool operator==(stl_pool_allocator const&) { return true; }
168  bool operator!=(stl_pool_allocator const& oAllocator) { return !operator==(oAllocator); }
169 };
170 
171 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> pool_allocator<ELEM_SIZE, CHUNK_SIZE>& pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get() {
172  thread_local pool_allocator inst;
173  return inst;
174 }
175 
176 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> pool_allocator<ELEM_SIZE, CHUNK_SIZE>::~pool_allocator() {
177 #ifdef HAVE_GETENV
178  if(debug_memory) {
179  auto* check = getenv("TLM_MM_CHECK");
180  auto diff = get_capacity() - get_free_entries_count();
181  if(diff) {
182  std::cerr << __FUNCTION__ << ": detected memory leak upon destruction, " << diff << " of " << get_capacity()
183  << " entries are not free'd" << std::endl;
184 #ifdef _MSC_VER
185  if(check && _stricmp(check, "DEBUG") == 0) {
186 #else
187  if(check && strcasecmp(check, "DEBUG") == 0) {
188 #endif
189  std::vector<std::pair<void*, uint64_t>> elems(used_blocks.begin(), used_blocks.end());
190  std::sort(elems.begin(), elems.end(), [](std::pair<void*, uint64_t> const& a, std::pair<void*, uint64_t> const& b) -> bool {
191  return a.second == b.second ? a.first < b.first : a.second < b.second;
192  });
193  std::cerr << "The 10 blocks with smallest id are:\n";
194  for(size_t i = 0; i < std::min<decltype(i)>(10UL, elems.size()); ++i) {
195  std::cerr << "\taddr=" << elems[i].first << ", id=" << elems[i].second << "\n";
196  }
197  }
198  }
199  }
200 #endif
201  for(auto p : chunks)
202  delete p;
203 }
204 
205 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void* pool_allocator<ELEM_SIZE, CHUNK_SIZE>::allocate(uint64_t id) {
206  if(!free_list.size())
207  resize();
208  auto ret = free_list.back();
209  free_list.pop_back();
210  memset(ret, 0, ELEM_SIZE);
211  if(debug_memory)
212  used_blocks.insert({ret, id});
213  return ret;
214 }
215 
216 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void pool_allocator<ELEM_SIZE, CHUNK_SIZE>::free(void* p) {
217  if(p) {
218  free_list.push_back(p);
219  if(debug_memory)
220  used_blocks.erase(p);
221  }
222 }
223 
224 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline void pool_allocator<ELEM_SIZE, CHUNK_SIZE>::resize() {
225  auto* chunk = new std::array<chunk_type, CHUNK_SIZE>();
226  chunks.push_back(chunk);
227  for(auto& p : *chunk)
228  free_list.push_back(&p[0]);
229 }
230 
231 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline size_t pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get_capacity() {
232  return chunks.size() * CHUNK_SIZE;
233 }
234 
235 template <size_t ELEM_SIZE, unsigned CHUNK_SIZE> inline size_t pool_allocator<ELEM_SIZE, CHUNK_SIZE>::get_free_entries_count() {
236  return free_list.size();
237 }
238 } // namespace util
240 #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