libcopp  2.2.0
stack_pool.h
Go to the documentation of this file.
1 // Copyright 2023 owent
2 
3 #pragma once
4 
5 #include <libcopp/utils/config/libcopp_build_features.h>
6 
10 
13 
14 // clang-format off
15 #include <libcopp/utils/config/stl_include_prefix.h> // NOLINT(build/include_order)
16 // clang-format on
17 #include <assert.h>
18 #include <cstring>
19 #include <list>
20 #include <memory>
21 // clang-format off
22 #include <libcopp/utils/config/stl_include_suffix.h> // NOLINT(build/include_order)
23 // clang-format on
24 
25 LIBCOPP_COPP_NAMESPACE_BEGIN
26 template <typename TAlloc>
27 class LIBCOPP_COPP_API_HEAD_ONLY stack_pool {
28  public:
29  using allocator_type = TAlloc;
30  using ptr_type = std::shared_ptr<stack_pool<TAlloc> >;
31 
32  // Compability with libcopp-1.x
34  using ptr_t = ptr_type;
35 
36  struct limit_t {
41  };
42 
43  struct configure_t {
44  size_t stack_size;
45  size_t stack_offset;
46  size_t gc_number;
51  bool auto_gc;
52  };
53 
54  private:
56 
57  stack_pool() = delete;
58  stack_pool(const stack_pool &) = delete;
59 
60  public:
61  static ptr_type create() { return std::make_shared<stack_pool>(constructor_delegator()); }
62 
64  memset(&limits_, 0, sizeof(limits_));
65  memset(&conf_, 0, sizeof(conf_));
66  conf_.stack_size = LIBCOPP_COPP_NAMESPACE_ID::stack_traits::default_size();
67  conf_.auto_gc = true;
68  }
69  ~stack_pool() { clear(); }
70 
71  inline const limit_t &get_limit() const { return limits_; }
72 
73  // configure
74  inline allocator_type &get_origin_allocator() LIBCOPP_MACRO_NOEXCEPT { return alloc_; }
75  inline const allocator_type &get_origin_allocator() const LIBCOPP_MACRO_NOEXCEPT { return alloc_; }
76 
77  size_t set_stack_size(size_t sz) {
78  if (sz <= LIBCOPP_COPP_NAMESPACE_ID::stack_traits::minimum_size()) {
79  sz = LIBCOPP_COPP_NAMESPACE_ID::stack_traits::minimum_size();
80  } else {
81  sz = LIBCOPP_COPP_NAMESPACE_ID::stack_traits::round_to_page_size(sz);
82  }
83 
84  if (sz != conf_.stack_size) {
85  clear();
86  }
87 
88  return conf_.stack_size = sz;
89  }
90  size_t get_stack_size() const { return conf_.stack_size; }
91  size_t get_stack_size_offset() const { return conf_.stack_offset; }
92 
93  inline void set_max_stack_size(size_t sz) LIBCOPP_MACRO_NOEXCEPT { conf_.max_stack_size = sz; }
94  inline size_t get_max_stack_size() const LIBCOPP_MACRO_NOEXCEPT { return conf_.max_stack_size; }
95  inline void set_max_stack_number(size_t sz) LIBCOPP_MACRO_NOEXCEPT { conf_.max_stack_number = sz; }
96  inline size_t get_max_stack_number() const LIBCOPP_MACRO_NOEXCEPT { return conf_.max_stack_number; }
97 
98  inline void set_min_stack_size(size_t sz) LIBCOPP_MACRO_NOEXCEPT { conf_.min_stack_size = sz; }
99  inline size_t get_min_stack_size() const LIBCOPP_MACRO_NOEXCEPT { return conf_.min_stack_size; }
100  inline void set_min_stack_number(size_t sz) LIBCOPP_MACRO_NOEXCEPT { conf_.min_stack_number = sz; }
101  inline size_t get_min_stack_number() const LIBCOPP_MACRO_NOEXCEPT { return conf_.min_stack_number; }
102 
103  inline void set_auto_gc(bool v) LIBCOPP_MACRO_NOEXCEPT { conf_.auto_gc = v; }
104  inline bool is_auto_gc() const LIBCOPP_MACRO_NOEXCEPT { return conf_.auto_gc; }
105 
106  inline void set_gc_once_number(size_t v) LIBCOPP_MACRO_NOEXCEPT { conf_.gc_number = v; }
107  inline size_t get_gc_once_number() const LIBCOPP_MACRO_NOEXCEPT { return conf_.gc_number; }
108 
109  // actions
110 
117  void allocate(stack_context &ctx) LIBCOPP_MACRO_NOEXCEPT {
118 #if !defined(LIBCOPP_DISABLE_ATOMIC_LOCK) || !(LIBCOPP_DISABLE_ATOMIC_LOCK)
119  LIBCOPP_COPP_NAMESPACE_ID::util::lock::lock_holder<LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock> lock_guard(
120  action_lock_);
121 #endif
122  // check limit
123  if (0 != conf_.max_stack_number && limits_.used_stack_number >= conf_.max_stack_number) {
124  ctx.sp = nullptr;
125  ctx.size = 0;
126  return;
127  }
128 
129  if (0 != conf_.max_stack_size && limits_.used_stack_size + conf_.stack_size > conf_.max_stack_size) {
130  ctx.sp = nullptr;
131  ctx.size = 0;
132  return;
133  }
134 
135  // get from pool, in order to max reuse cache, we use FILO to allocate stack
136  if (!free_list_.empty()) {
137  typename std::list<stack_context>::reverse_iterator iter = free_list_.rbegin();
138  assert(iter != free_list_.rend());
139 
140  // free limit
141  COPP_LIKELY_IF (limits_.free_stack_number > 0) {
142  --limits_.free_stack_number;
143  } else {
144  limits_.free_stack_number = free_list_.size() - 1;
145  }
146 
147  COPP_LIKELY_IF (limits_.free_stack_size >= (*iter).size) {
148  limits_.free_stack_size -= (*iter).size;
149  } else {
150  limits_.free_stack_size = 0;
151  }
152 
153  // make sure the stack must be greater or equal than configure after reset
154  COPP_LIKELY_IF (iter->size >= conf_.stack_size) {
155  ctx = *iter;
156  free_list_.pop_back();
157 
158  // used limit
159  ++limits_.used_stack_number;
160  limits_.used_stack_size += ctx.size;
161  return;
162  } else {
163  // just pop cache
164  free_list_.pop_back();
165  }
166  }
167 
168  // get from origin allocator
169  alloc_.allocate(ctx, conf_.stack_size);
170  if (nullptr != ctx.sp && ctx.size > 0) {
171  // used limit
172  ++limits_.used_stack_number;
173  limits_.used_stack_size += ctx.size;
174 
175  conf_.stack_offset = ctx.size - conf_.stack_size;
176  }
177  }
178 
183  void deallocate(stack_context &ctx) LIBCOPP_MACRO_NOEXCEPT {
184  assert(ctx.sp && ctx.size > 0);
185  do {
186 #if !defined(LIBCOPP_DISABLE_ATOMIC_LOCK) || !(LIBCOPP_DISABLE_ATOMIC_LOCK)
187  LIBCOPP_COPP_NAMESPACE_ID::util::lock::lock_holder<LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock> lock_guard(
188  action_lock_);
189 #endif
190  // check ctx
191  if (ctx.sp == nullptr || 0 == ctx.size) {
192  break;
193  }
194 
195  // limits
196  COPP_LIKELY_IF (limits_.used_stack_size >= ctx.size) {
197  limits_.used_stack_size -= ctx.size;
198  } else {
199  limits_.used_stack_size = 0;
200  }
201 
202  COPP_LIKELY_IF (limits_.used_stack_number > 0) {
203  --limits_.used_stack_number;
204  }
205 
206  // check size
207  if (ctx.size != conf_.stack_size + conf_.stack_offset) {
208  alloc_.deallocate(ctx);
209  break;
210  }
211 
212  // push to free list
213  free_list_.push_back(ctx);
214 
215  // limits
216  ++limits_.free_stack_number;
217  limits_.free_stack_size += ctx.size;
218  } while (false);
219 
220  // check GC
221  if (conf_.auto_gc) {
222  gc();
223  }
224  }
225 
226  size_t gc() {
227  size_t ret = 0;
228  // gc only if free stacks is greater than used
229  if (limits_.used_stack_size >= limits_.free_stack_size && limits_.used_stack_number >= limits_.free_stack_number) {
230  return ret;
231  }
232 
233  // gc when stack is too large
234  if (0 != conf_.min_stack_size || 0 != conf_.min_stack_number) {
235  bool min_stack_size =
236  conf_.min_stack_size == 0 || limits_.used_stack_size + limits_.free_stack_size <= conf_.min_stack_size;
237  bool min_stack_number = conf_.min_stack_number == 0 ||
238  limits_.free_stack_number + limits_.used_stack_number <= conf_.min_stack_number;
239  if (min_stack_size && min_stack_number) {
240  return ret;
241  }
242  }
243 
244 #if !defined(LIBCOPP_DISABLE_ATOMIC_LOCK) || !(LIBCOPP_DISABLE_ATOMIC_LOCK)
245  LIBCOPP_COPP_NAMESPACE_ID::util::lock::lock_holder<LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock> lock_guard(
246  action_lock_);
247 #endif
248 
249  size_t keep_size = limits_.free_stack_size >> 1;
250  size_t keep_number = limits_.free_stack_number >> 1;
251  size_t left_gc = conf_.gc_number;
252  while (limits_.free_stack_size > keep_size || limits_.free_stack_number > keep_number) {
253  if (free_list_.empty()) {
254  limits_.free_stack_size = 0;
255  limits_.free_stack_number = 0;
256  break;
257  }
258 
259  typename std::list<stack_context>::iterator iter = free_list_.begin();
260  assert(iter != free_list_.end());
261 
262  COPP_LIKELY_IF (limits_.free_stack_number > 0) {
263  --limits_.free_stack_number;
264  } else {
265  limits_.free_stack_number = free_list_.size() - 1;
266  }
267 
268  COPP_LIKELY_IF (limits_.free_stack_size >= (*iter).size) {
269  limits_.free_stack_size -= (*iter).size;
270  } else {
271  limits_.free_stack_size = 0;
272  }
273 
274  alloc_.deallocate(*iter);
275  free_list_.pop_front();
276  ++ret;
277 
278  // gc max stacks once
279  if (0 != left_gc) {
280  --left_gc;
281  if (0 == left_gc) {
282  break;
283  }
284  }
285  }
286 
288 
289  return ret;
290  }
291 
292  void clear() {
293 #if !defined(LIBCOPP_DISABLE_ATOMIC_LOCK) || !(LIBCOPP_DISABLE_ATOMIC_LOCK)
294  LIBCOPP_COPP_NAMESPACE_ID::util::lock::lock_holder<LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock> lock_guard(
295  action_lock_);
296 #endif
297 
298  limits_.free_stack_size = 0;
299  limits_.free_stack_number = 0;
300 
301  for (typename std::list<stack_context>::iterator iter = free_list_.begin(); iter != free_list_.end(); ++iter) {
302  alloc_.deallocate(*iter);
303  }
304 
306  }
307 
308  private:
312 #if !defined(LIBCOPP_DISABLE_ATOMIC_LOCK) || !(LIBCOPP_DISABLE_ATOMIC_LOCK)
313  LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock action_lock_;
314 #endif
315  std::list<stack_context> free_list_;
316 };
317 LIBCOPP_COPP_NAMESPACE_END
#define LIBCOPP_UTIL_LOCK_ATOMIC_THREAD_FENCE(x)
size_t get_stack_size_offset() const
Definition: stack_pool.h:91
size_t get_min_stack_number() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:101
void set_gc_once_number(size_t v) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:106
std::shared_ptr< stack_pool< TAlloc > > ptr_type
Definition: stack_pool.h:30
void set_min_stack_size(size_t sz) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:98
allocator_type allocator_t
Definition: stack_pool.h:33
allocator_type alloc_
Definition: stack_pool.h:311
const allocator_type & get_origin_allocator() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:75
size_t get_stack_size() const
Definition: stack_pool.h:90
void set_min_stack_number(size_t sz) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:100
ptr_type ptr_t
Definition: stack_pool.h:34
void set_auto_gc(bool v) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:103
size_t get_max_stack_size() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:94
size_t get_gc_once_number() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:107
size_t get_max_stack_number() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:96
size_t set_stack_size(size_t sz)
Definition: stack_pool.h:77
limit_t limits_
Definition: stack_pool.h:309
bool is_auto_gc() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:104
allocator_type & get_origin_allocator() LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:74
stack_pool(constructor_delegator)
Definition: stack_pool.h:63
size_t gc()
Definition: stack_pool.h:226
std::list< stack_context > free_list_
Definition: stack_pool.h:315
configure_t conf_
Definition: stack_pool.h:310
void allocate(stack_context &ctx) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:117
stack_pool()=delete
void set_max_stack_number(size_t sz) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:95
stack_pool(const stack_pool &)=delete
const limit_t & get_limit() const
Definition: stack_pool.h:71
LIBCOPP_COPP_NAMESPACE_ID::util::lock::spin_lock action_lock_
Definition: stack_pool.h:313
TAlloc allocator_type
Definition: stack_pool.h:29
void clear()
Definition: stack_pool.h:292
static ptr_type create()
Definition: stack_pool.h:61
size_t get_min_stack_size() const LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:99
void set_max_stack_size(size_t sz) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:93
void deallocate(stack_context &ctx) LIBCOPP_MACRO_NOEXCEPT
Definition: stack_pool.h:183
#define COPP_LIKELY_IF(...)
Definition: features.h:102
锁管理器 Licensed under the MIT licenses.
自旋锁 Licensed under the MIT licenses.
size_t used_stack_number
Definition: stack_pool.h:37
size_t free_stack_number
Definition: stack_pool.h:39
size_t used_stack_size
Definition: stack_pool.h:38
size_t free_stack_size
Definition: stack_pool.h:40