Allocator Builder
Policy Based C++ Template Allocator Library
 All Classes Functions Variables Enumerations Enumerator Groups Pages
heap.hpp
1 //
3 // Copyright 2014 Felix Petriconi
4 //
5 // License: http://boost.org/LICENSE_1_0.txt, Boost License 1.0
6 //
7 // Authors: http://petriconi.net, Felix Petriconi
8 //
10 #pragma once
11 
12 #include "allocator_base.hpp"
13 
14 #include "internal/dynastic.hpp"
15 #include "internal/reallocator.hpp"
16 #include "internal/heap_helpers.hpp"
17 
18 #include <algorithm>
19 #include <cassert>
20 
21 #ifdef min
22 #undef min
23 #endif
24 
25 #ifdef max
26 #undef max
27 #endif
28 
29 namespace alb {
30  inline namespace v_100 {
39  template <class Allocator, size_t NumberOfChunks, size_t ChunkSize>
40  class heap
41  {
43  numberOfChunks_;
44 
46 
47  block buffer_;
48  block controlBuffer_;
49 
50  // bit field where 0 means used and 1 means free block
51  static const uint64_t all_set = std::numeric_limits<uint64_t>::max();
52  static const uint64_t all_zero = uint64_t(0);
53 
54  uint64_t *control_;
55  size_t controlSize_;
56 
57  Allocator allocator_;
58 
59  void shrink() noexcept {
60  allocator_.deallocate(controlBuffer_);
61  allocator_.deallocate(buffer_);
62  control_ = nullptr;
63  }
64 
65  heap(const heap &) = delete;
66  heap &operator=(const heap &) = delete;
67 
68  public:
69  using allocator = Allocator;
70 
71  static constexpr bool supports_truncated_deallocation = true;
72  static constexpr unsigned alignment = Allocator::alignment;
73 
74  heap() noexcept {
75  init();
76  }
77 
78  heap(size_t numberOfChunks, size_t chunkSize) noexcept {
79  numberOfChunks_.value(internal::round_to_alignment(64, numberOfChunks));
80  chunk_size_.value(internal::round_to_alignment(4, chunkSize));
81  init();
82  }
83 
84  heap(heap &&x) noexcept {
85  *this = std::move(x);
86  }
87 
88  heap &operator=(heap &&x) noexcept {
89  if (this == &x) {
90  return *this;
91  }
92  shrink();
93  numberOfChunks_ = std::move(x.numberOfChunks_);
94  chunk_size_ = std::move(x.chunk_size_);
95  buffer_ = std::move(x.buffer_);
96  controlBuffer_ = std::move(x.controlBuffer_);
97  control_ = std::move(x.control_);
98  controlSize_ = std::move(x.controlSize_);
99  allocator_ = std::move(x.allocator_);
100 
101  x.control_ = nullptr;
102 
103  return *this;
104  }
105 
106  ~heap() {
107  shrink();
108  }
109 
110  size_t number_of_chunk() const noexcept {
111  return numberOfChunks_.value();
112  }
113 
114  size_t chunk_size() const noexcept {
115  return chunk_size_.value();
116  }
117 
118  bool owns(const block &b) const noexcept {
119  return b && buffer_.ptr <= b.ptr &&
120  b.ptr < (static_cast<char *>(buffer_.ptr) + buffer_.length);
121  }
122 
123  block allocate(size_t n) noexcept {
124  block result;
125  if (n == 0) {
126  return result;
127  }
128 
129  // The heap cannot handle such a big request
130  if (n > chunk_size_.value() * numberOfChunks_.value()) {
131  return result;
132  }
133 
134  size_t numberOfAlignedBytes = internal::round_to_alignment(chunk_size_.value(), n);
135  size_t numberOfBlocks = numberOfAlignedBytes / chunk_size_.value();
136  numberOfBlocks = std::max(size_t(1), numberOfBlocks);
137 
138  if (numberOfBlocks < 64) {
139  result = allocate_within_single_control_register(numberOfBlocks);
140  if (result) {
141  return result;
142  }
143  }
144  else if (numberOfBlocks == 64) {
145  result = allocate_within_complete_control_register(numberOfBlocks);
146  if (result) {
147  return result;
148  }
149  }
150  else if ((numberOfBlocks % 64) == 0) {
151  result = allocate_multiple_complete_control_registers(numberOfBlocks);
152  if (result) {
153  return result;
154  }
155  }
156 
157  result = allocate_with_register_overlap(numberOfBlocks);
158  return result;
159  }
160 
161  void deallocate(block &b) noexcept {
162  if (!b) {
163  return;
164  }
165 
166  if (!owns(b)) {
167  return;
168  }
169 
170  const auto context = block_to_context(b);
171 
172  if (context.subIndex + context.usedChunks <= 64) {
173  set_within_single_register<true>(context);
174  }
175  else if ((context.usedChunks % 64) == 0) {
176  deallocate_for_multiple_complete_control_register(context);
177  }
178  else {
179  deallocate_with_control_register_overlap(context);
180  }
181  b.reset();
182  }
183 
184  void deallocate_all() noexcept {
185  std::fill(control_, control_ + controlSize_, all_set);
186  }
187 
188  bool reallocate(block &b, size_t n) noexcept {
189  if (internal::is_reallocation_handled_default(*this, b, n)) {
190  return true;
191  }
192 
193  const auto numberOfBlocks = static_cast<int>(b.length / chunk_size_.value());
194  const auto numberOfNewNeededBlocks =
195  static_cast<int>(internal::round_to_alignment(chunk_size_.value(), n) / chunk_size_.value());
196 
197  if (numberOfBlocks == numberOfNewNeededBlocks) {
198  return true;
199  }
200  if (b.length > n) {
201  auto context = block_to_context(b);
202  if (context.subIndex + context.usedChunks <= 64) {
203  set_within_single_register<true>(BlockContext{ context.registerIndex,
204  context.subIndex + numberOfNewNeededBlocks,
205  context.usedChunks - numberOfNewNeededBlocks });
206  }
207  else {
208  deallocate_with_control_register_overlap(
209  BlockContext{ context.registerIndex, context.subIndex + numberOfNewNeededBlocks,
210  context.usedChunks - numberOfNewNeededBlocks });
211  }
212  b.length = numberOfNewNeededBlocks * chunk_size_.value();
213  return true;
214  }
215  return internal::reallocate_with_copy(*this, *this, b, n);
216  }
217 
218  bool expand(block &b, size_t delta) noexcept {
219  if (delta == 0) {
220  return true;
221  }
222 
223  const auto context = block_to_context(b);
224  const auto numberOfAdditionalNeededBlocks = static_cast<int>(
225  internal::round_to_alignment(chunk_size_.value(), delta) / chunk_size_.value());
226 
227  if (context.subIndex + context.usedChunks + numberOfAdditionalNeededBlocks <= 64) {
228  if (test_and_set_within_single_register<false>(
229  BlockContext{ context.registerIndex, context.subIndex + context.usedChunks,
230  numberOfAdditionalNeededBlocks })) {
231  b.length += numberOfAdditionalNeededBlocks * chunk_size_.value();
232  return true;
233  }
234  return false;
235  }
236  else {
237  if (test_and_set_over_multiple_registers<false>(
238  BlockContext{ context.registerIndex, context.subIndex + context.usedChunks,
239  numberOfAdditionalNeededBlocks })) {
240  b.length += numberOfAdditionalNeededBlocks * chunk_size_.value();
241  return true;
242  }
243  }
244  return false;
245  }
246 
247  private:
248  void init() noexcept {
249  assert(chunk_size_.value() > alignment);
250  assert(chunk_size_.value() % alignment == 0);
251 
252  controlSize_ = numberOfChunks_.value() / 64;
253  controlBuffer_ = allocator_.allocate(sizeof(uint64_t) * controlSize_);
254  assert((bool)controlBuffer_);
255 
256  control_ = static_cast<uint64_t *>(controlBuffer_.ptr);
257  buffer_ = allocator_.allocate(chunk_size_.value() * numberOfChunks_.value());
258  assert((bool)buffer_);
259 
260  deallocate_all();
261  }
262 
263  struct BlockContext
264  {
265  int registerIndex;
266  int subIndex;
267  int usedChunks;
268  };
269 
270  BlockContext block_to_context(const block &b) noexcept {
271  const auto blockIndex = static_cast<int>(
272  (static_cast<char *>(b.ptr) - static_cast<char *>(buffer_.ptr)) / chunk_size_.value());
273 
274  return{ blockIndex / 64, blockIndex % 64, static_cast<int>(b.length / chunk_size_.value()) };
275  }
276 
277  template <bool Used>
278  bool test_and_set_within_single_register(const BlockContext &context) noexcept {
279  assert(context.subIndex + context.usedChunks <= 64);
280 
281  uint64_t mask = (context.usedChunks == 64)
282  ? all_set
283  : (((uint64_t(1) << context.usedChunks) - 1) << context.subIndex);
284 
285  uint64_t currentRegister, newRegister;
286  currentRegister = control_[context.registerIndex];
287  if ((currentRegister & mask) != mask) {
288  return false;
289  }
290  newRegister = helpers::set_used<Used>(currentRegister, mask);
291  control_[context.registerIndex] = newRegister;
292  return true;
293  }
294 
295  template <bool Used>
296  void set_over_multiple_registers(const BlockContext &context) noexcept {
297  size_t chunksToTest = context.usedChunks;
298  size_t subIndexStart = context.subIndex;
299  size_t registerIndex = context.registerIndex;
300  do {
301  size_t mask;
302  if (subIndexStart > 0)
303  mask = ((uint64_t(1) << (64 - subIndexStart)) - 1) << subIndexStart;
304  else
305  mask = (chunksToTest >= 64) ? all_set : ((uint64_t(1) << chunksToTest) - 1);
306 
307  assert(registerIndex < controlSize_);
308 
309  uint64_t currentRegister, newRegister;
310 
311  currentRegister = control_[registerIndex];
312  newRegister = helpers::set_used<Used>(currentRegister, mask);
313  control_[registerIndex] = newRegister;
314 
315  if (subIndexStart + chunksToTest > 64) {
316  chunksToTest = subIndexStart + chunksToTest - 64;
317  subIndexStart = 0;
318  }
319  else {
320  chunksToTest = 0;
321  }
322  registerIndex++;
323  } while (chunksToTest > 0);
324  }
325 
326  template <bool Used>
327  bool test_and_set_over_multiple_registers(const BlockContext &context) noexcept {
328  // This branch works on multiple chunks at the same time and so a real lock
329  // is necessary.
330  size_t chunksToTest = context.usedChunks;
331  size_t subIndexStart = context.subIndex;
332  size_t registerIndex = context.registerIndex;
333 
334  do {
335  uint64_t mask;
336  if (subIndexStart > 0)
337  mask = ((uint64_t(1) << (64 - subIndexStart)) - 1) << subIndexStart;
338  else
339  mask = (chunksToTest >= 64) ? all_set : ((uint64_t(1) << chunksToTest) - 1);
340 
341  auto currentRegister = control_[registerIndex];
342 
343  if ((currentRegister & mask) != mask) {
344  return false;
345  }
346 
347  if (subIndexStart + chunksToTest > 64) {
348  chunksToTest = subIndexStart + chunksToTest - 64;
349  subIndexStart = 0;
350  }
351  else {
352  chunksToTest = 0;
353  }
354  registerIndex++;
355  if (registerIndex > controlSize_) {
356  return false;
357  }
358  } while (chunksToTest > 0);
359 
360  set_over_multiple_registers<Used>(context);
361 
362  return true;
363  }
364 
365  template <bool Used>
366  void set_within_single_register(const BlockContext &context) noexcept {
367  assert(context.subIndex + context.usedChunks <= 64);
368 
369  uint64_t mask = (context.usedChunks == 64)
370  ? all_set
371  : (((uint64_t(1) << context.usedChunks) - 1) << context.subIndex);
372 
373  uint64_t currentRegister, newRegister;
374  currentRegister = control_[context.registerIndex];
375  newRegister = helpers::set_used<Used>(currentRegister, mask);
376  control_[context.registerIndex] = newRegister;
377  }
378 
379  block allocate_within_single_control_register(size_t numberOfBlocks) noexcept {
380  block result;
381 
382  // first we have to look for at least one free block
383  size_t controlIndex = 0;
384  while (controlIndex < controlSize_) {
385  auto currentControlRegister = control_[controlIndex];
386 
387  // == 0 means that all blocks are in use and no need to search further
388  if (currentControlRegister != 0) {
389  uint64_t mask = (numberOfBlocks == 64) ?
390  all_set : ((uint64_t(1) << numberOfBlocks) - 1);
391 
392  size_t i = 0;
393  // Search for numberOfBlock bits that are set to one
394  while (i <= 64 - numberOfBlocks) {
395  if ((currentControlRegister & mask) == mask) {
396  auto newControlRegister = helpers::set_used<false>(currentControlRegister, mask);
397 
398  control_[controlIndex] = newControlRegister;
399 
400  size_t ptrOffset = (controlIndex * 64 + i) * chunk_size_.value();
401 
402  result.ptr = static_cast<char *>(buffer_.ptr) + ptrOffset;
403  result.length = numberOfBlocks * chunk_size_.value();
404 
405  return result;
406  }
407  i++;
408  mask <<= 1;
409  };
410  }
411  controlIndex++;
412  }
413 
414  return result;
415  }
416 
417  block allocate_within_complete_control_register(size_t numberOfBlocks) noexcept {
418  block result;
419 
420  // first we have to look for at least full free block
421  auto freeChunk = std::find_if(control_, control_ + controlSize_,
422  [this](const uint64_t &v) { return v == all_set; });
423 
424  if (freeChunk == control_ + controlSize_) {
425  return result;
426  }
427 
428  *freeChunk = 0;
429  size_t ptrOffset = ((freeChunk - control_) * 64) * chunk_size_.value();
430 
431  result.ptr = static_cast<char *>(buffer_.ptr) + ptrOffset;
432  result.length = numberOfBlocks * chunk_size_.value();
433 
434  return result;
435  }
436 
437  block allocate_multiple_complete_control_registers(size_t numberOfBlocks) noexcept {
438  block result;
439 
440  const auto neededChunks = static_cast<int>(numberOfBlocks / 64);
441  auto freeFirstChunk =
442  std::search_n(control_, control_ + controlSize_, neededChunks, all_set,
443  [](uint64_t const &v, uint64_t const &p) { return v == p; });
444 
445  if (freeFirstChunk == control_ + controlSize_) {
446  return result;
447  }
448  auto p = freeFirstChunk;
449  while (p < freeFirstChunk + neededChunks) {
450  *p = 0;
451  ++p;
452  }
453 
454  size_t ptrOffset = ((freeFirstChunk - control_) * 64) * chunk_size_.value();
455 
456  result.ptr = static_cast<char *>(buffer_.ptr) + ptrOffset;
457  result.length = numberOfBlocks * chunk_size_.value();
458 
459  return result;
460  }
461 
462  block allocate_with_register_overlap(size_t numberOfBlocks) noexcept {
463  block result;
464 
465  // search for free area
466  auto p = reinterpret_cast<unsigned char *>(control_);
467  const auto lastp =
468  reinterpret_cast<unsigned char *>(control_) + controlSize_ * sizeof(uint64_t);
469 
470  size_t freeBlocksCount(0);
471  unsigned char *chunkStart = nullptr;
472 
473  while (p < lastp) {
474  if (*p == 0xff) { // free
475  if (!chunkStart) {
476  chunkStart = p;
477  }
478 
479  freeBlocksCount += 8;
480  if (freeBlocksCount >= numberOfBlocks) {
481  break;
482  }
483  }
484  else {
485  freeBlocksCount = 0;
486  chunkStart = nullptr;
487  }
488  p++;
489  };
490 
491  if (p != lastp && freeBlocksCount >= numberOfBlocks) {
492  size_t ptrOffset =
493  (chunkStart - reinterpret_cast<unsigned char *>(control_)) * 8 * chunk_size_.value();
494 
495  result.ptr = static_cast<char *>(buffer_.ptr) + ptrOffset;
496  result.length = numberOfBlocks * chunk_size_.value();
497 
498  set_over_multiple_registers<false>(block_to_context(result));
499  return result;
500  }
501 
502  return result;
503  }
504 
505  void deallocate_for_multiple_complete_control_register(const BlockContext &context) noexcept
506  {
507  const auto registerToFree = context.registerIndex + context.usedChunks / 64;
508  for (auto i = context.registerIndex; i < registerToFree; ++i) {
509  control_[i] = all_set;
510  }
511  }
512 
513  void deallocate_with_control_register_overlap(const BlockContext &context) noexcept
514  {
515  set_over_multiple_registers<true>(context);
516  }
517  };
518  }
519  using namespace v_100;
520 }
void reset() noexcept
size_t length
This describes the length of the reserved bytes.
bool reallocate_with_copy(OldAllocator &oldAllocator, NewAllocator &newAllocator, block &b, size_t n) noexcept
Definition: reallocator.hpp:35
constexpr size_t round_to_alignment(size_t basis, size_t n) noexcept
void * ptr
This points to the start address of the described memory block.