Nebula
Loading...
Searching...
No Matches
rangeallocator.h
Go to the documentation of this file.
1#pragma once
2//------------------------------------------------------------------------------
9//------------------------------------------------------------------------------
10#include "core/types.h"
11#include "util/bit.h"
12#include "util/array.h"
13#include <utility>
14
15namespace Memory
16{
17
18//------------------------------------------------------------------------------
25{
29
30 static constexpr uint OOM = 0xFFFFFFFF;
31};
32
33//------------------------------------------------------------------------------
46{
47public:
51 RangeAllocator(uint size, SizeT maxNumAllocs);
54
56 void Clear();
57
59 bool Empty();
60
62 RangeAllocation Alloc(uint size, uint alignment = 1);
64 void Dealloc(const RangeAllocation& allocation);
65
66private:
68 uint InsertNode(uint size, uint offset);
70 void RemoveNode(uint nodeIndex);
71
73 {
75 resident(false)
76 , size(0)
77 , offset(0)
78 , binPrev(END)
79 , binNext(END)
80 , blockPrev(END)
81 , blockNext(END)
82 {};
86
87 static constexpr uint END = 0xFFFFFFFF;
90 };
91
92 struct BinIndex
93 {
94 BinIndex() : bin(0), bucket(0) {};
98 };
99
101 static BinIndex IndexFromSize(uint size, bool round = false);
105 static uint BinFromSize(uint size, uint bucket);
106
107public:
112
113 static constexpr uint NUM_BUCKETS = 32u;
114 static constexpr uint NUM_BINS_PER_BUCKET = 8u;
115
116
120
123};
124
125
126static constexpr uint BUCKET_INDEX_SHIFT = 3u;
127static constexpr uint BIN_INDEX_MASK = 0x7;
128
129//------------------------------------------------------------------------------
132inline
137
138//------------------------------------------------------------------------------
141inline
143{
144 this->size = size;
145 this->freeNodes.Resize(maxNumAllocs + 1);
146 this->nodes.Resize(maxNumAllocs + 1);
147 this->numAllocs = 0;
148
149 // Clear the allocator
150 this->Clear();
151}
152
153//------------------------------------------------------------------------------
156inline
160
161//------------------------------------------------------------------------------
164inline void
166{
167 // Reset bit masks
168 this->bucketUsageMask = 0x0;
169 this->numAllocs = 0;
170 memset(this->binMasks, 0x0, sizeof(this->binMasks));
171
172 // Reset bin heads
173 for (uint i = 0; i < NUM_BUCKETS * NUM_BINS_PER_BUCKET; i++)
174 {
176 }
177
178 // Clear nodes
179 for (uint i = 0; i < this->nodes.Size(); i++)
180 {
181 this->nodes[i] = RangeAllocatorNode();
182 this->freeNodes[i] = this->nodes.Size() - i - 1;
183 }
184 this->freeNodeIterator = this->nodes.Size() - 1;
185 this->freeStorage = 0;
186
187 // Insert node covering the whole range
188 if (this->size > 0)
189 this->InsertNode(this->size, 0);
190}
191
192//------------------------------------------------------------------------------
195inline bool
197{
198 return this->freeStorage == this->size;
199}
200
201//------------------------------------------------------------------------------
204inline uint
205FindLowestSetBitAfter(uint bitMask, uint startBitIndex)
206{
207 uint maskBeforeStartIndex = (1 << startBitIndex) - 1;
208 uint maskAfterStartIndex = ~maskBeforeStartIndex;
209 uint bitsAfter = bitMask & maskAfterStartIndex;
210 if (bitsAfter == 0) return RangeAllocation::OOM;
211 return Util::CountTrailingZeroes(bitsAfter);
212}
213
214//------------------------------------------------------------------------------
222inline RangeAllocation
224{
225 // We are not allowed any more allocations
226 uint alignedSize = size + alignment - 1;
228 {
229 return RangeAllocation{ .offset = RangeAllocation::OOM, .size = 0, .node = RangeAllocatorNode::END };
230 }
231
232 BinIndex minIndex = IndexFromSize(alignedSize, true);
234 uint bucket = minIndex.bucket;
235
236 if (this->bucketUsageMask & (1 << bucket))
237 {
238 bin = FindLowestSetBitAfter(this->binMasks[bucket], minIndex.bin);
239 }
240
241 if (bin == RangeAllocation::OOM)
242 {
243 // Find next bit set after bucket in the usage mask
244 bucket = FindLowestSetBitAfter(this->bucketUsageMask, bucket + 1);
245
246 // If this means we get 32, it means we're out of buckets that fit
247 if (bucket == RangeAllocation::OOM)
248 {
249 return RangeAllocation{ .offset = RangeAllocation::OOM, .size = 0, .node = RangeAllocatorNode::END };
250 }
251
252 // Find any bit, since this bucket has to fit
253 bin = Util::CountTrailingZeroes(this->binMasks[bucket]);
254 }
255
256 BinIndex binIndex;
257 binIndex.bucket = bucket;
258 binIndex.bin = bin;
259 uint16_t index = (bucket << BUCKET_INDEX_SHIFT) | bin;
260
261 // Get linked list head
262 uint nodeIndex = this->binHeads[index];
263 RangeAllocatorNode& node = this->nodes[nodeIndex];
264 n_assert(!node.resident);
265
266 // Save total size of node
267 uint totalSize = node.size;
268 //n_assert(totalSize >= alignedSize);
269 uint alignedOffset = Math::align(node.offset, alignment);
270 node.size = alignedSize;
271 node.resident = true;
272 this->numAllocs++;
273
274 // Bump head of bin to next node
275 this->binHeads[index] = node.binNext;
277 this->nodes[node.binNext].binPrev = RangeAllocatorNode::END;
278
279 // If bin head is empty after we grab the node, unmark the bin
280 if (this->binHeads[index] == RangeAllocatorNode::END)
281 {
282 this->binMasks[binIndex.bucket] &= ~(1 << binIndex.bin);
283
284 // If all bins are busy, unmark the bucket bits
285 if (this->binMasks[binIndex.bucket] == 0)
286 {
287 this->bucketUsageMask &= ~(1 << binIndex.bucket);
288 }
289 }
290
291 // We subtract the entire size of the node
292 this->freeStorage -= totalSize;
293
294 // The remainder in the front is then added back
295 uint remainder = totalSize - node.size;
296 if (remainder > 0)
297 {
298 uint newNodeIndex = this->InsertNode(remainder, node.offset + node.size);
299 if (newNodeIndex != RangeAllocatorNode::END)
300 {
301 RangeAllocatorNode& newNode = this->nodes[newNodeIndex];
302
303 // If the current node has a next, repoint it to the new block
305 {
306 this->nodes[node.blockNext].blockPrev = newNodeIndex;
307 }
308
309 newNode.blockPrev = nodeIndex;
310 newNode.blockNext = node.blockNext;
311 node.blockNext = newNodeIndex;
312 n_assert((this->nodes[nodeIndex].offset + this->nodes[nodeIndex].size) == newNode.offset);
313 }
314 }
315
316 return RangeAllocation{ .offset = alignedOffset, .size = alignedSize, .node = nodeIndex };
317}
318
319//------------------------------------------------------------------------------
322inline void
324{
325 RangeAllocatorNode& node = this->nodes[allocation.node];
326 n_assert(node.resident);
327 node.resident = false;
328 this->numAllocs--;
329 uint size = node.size;
330 uint offset = node.offset;
331
332 // Try to merge with left
334 {
335 RangeAllocatorNode& prev = this->nodes[node.blockPrev];
336 if (!prev.resident)
337 {
338 offset = prev.offset;
339 size += prev.size;
340
341 this->RemoveNode(node.blockPrev);
342 n_assert(prev.blockNext == allocation.node);
343 node.blockPrev = prev.blockPrev;
344 }
345 }
346
348 {
349 RangeAllocatorNode& next = this->nodes[node.blockNext];
350 if (!next.resident)
351 {
352 size += next.size;
353 this->RemoveNode(node.blockNext);
354 n_assert(next.blockPrev == allocation.node);
355 node.blockNext = next.blockNext;
356 }
357 }
358
359
360 uint leftNode = node.blockPrev;
361 uint rightNode = node.blockNext;
362 this->freeNodes[++this->freeNodeIterator] = allocation.node;
363
364 // Add merged node
365 uint mergedNode = this->InsertNode(size, offset);
366
367 // Connect adjacent nodes with newly inserted node
368 if (leftNode != RangeAllocatorNode::END)
369 {
370 this->nodes[mergedNode].blockPrev = leftNode;
371 this->nodes[leftNode].blockNext = mergedNode;
372 }
373
374 if (rightNode != RangeAllocatorNode::END)
375 {
376 this->nodes[mergedNode].blockNext = rightNode;
377 this->nodes[rightNode].blockPrev = mergedNode;
378 }
379}
380
381//------------------------------------------------------------------------------
384inline uint
386{
387 if (this->freeNodeIterator == 0xFFFFFFFF)
389
390 BinIndex binIndex = IndexFromSize(size);
391 uint16_t index = binIndex.index;
392
393 if (this->binHeads[index] == RangeAllocatorNode::END)
394 {
395 // Bin isn't used, flip bit for bucket and bin
396 this->binMasks[binIndex.bucket] |= (1 << binIndex.bin);
397 this->bucketUsageMask |= (1 << binIndex.bucket);
398 }
399
400 // Get new node
401 uint headNodeIndex = this->binHeads[index];
402 uint newNodeIndex = this->freeNodes[this->freeNodeIterator--];
403 RangeAllocatorNode& newNode = this->nodes[newNodeIndex];
404 n_assert(!newNode.resident);
405
406 // Set the size and offset of the node memory
407 newNode.size = size;
408 newNode.offset = offset;
409 newNode.binNext = headNodeIndex;
413 newNode.resident = false;
414
415 this->freeStorage += size;
416
417 // Get node at head of bin
418 if (headNodeIndex != RangeAllocatorNode::END)
419 {
420 // If valid, repoint head node to point to the new node in the bin
421 RangeAllocatorNode& headNode = this->nodes[headNodeIndex];
422 headNode.binPrev = newNodeIndex;
423 }
424 this->binHeads[index] = newNodeIndex;
425
426 return newNodeIndex;
427}
428
429//------------------------------------------------------------------------------
432inline void
434{
435 // Get node to free
436 RangeAllocatorNode& node = this->nodes[nodeIndex];
437 node.resident = false;
438
439 // Add back the storage to the allocator
440 this->freeStorage -= node.size;
441
443 {
444 BinIndex binIndex = IndexFromSize(node.size);
445 uint16_t index = binIndex.index;
446
447 // Point head of
448 this->binHeads[index] = node.binNext;
450 {
451 this->nodes[node.binNext].binPrev = RangeAllocatorNode::END;
452 }
453
455 {
456 n_assert(this->binMasks[binIndex.bucket] != 0x0);
457 // If we are deleting the last node, make sure to unmark the bin
458 this->binMasks[binIndex.bucket] &= ~(1 << binIndex.bin);
459
460 if (this->binMasks[binIndex.bucket] == 0x0)
461 {
462 // If all bins have been cleared, clear the entire bucket
463 this->bucketUsageMask &= ~(1 << binIndex.bucket);
464 }
465 }
466 }
467 else
468 {
469 this->nodes[node.binPrev].binNext = node.binNext;
471 {
472 this->nodes[node.binNext].binPrev = node.binPrev;
473 }
474 }
475
476 this->freeNodes[++this->freeNodeIterator] = nodeIndex;
477}
478
479static constexpr uint MANTISSA_BITS = 3;
480static constexpr uint MANTISSA_VALUE = 1 << MANTISSA_BITS;
481static constexpr uint MANTISSA_MASK = MANTISSA_VALUE - 1;
482
483
484//------------------------------------------------------------------------------
488inline uint
490{
491 uint exp = 0;
492 uint mantissa = 0;
493
494 if (size < MANTISSA_VALUE)
495 {
496 // Denorm: 0..(MANTISSA_VALUE-1)
497 mantissa = size;
498 }
499 else
500 {
501 // Normalized: Hidden high bit always 1. Not stored. Just like float.
502 uint highestSetBit = Util::LastBitSetIndex(size);
503
504 uint mantissaStartBit = highestSetBit - MANTISSA_BITS;
505 exp = mantissaStartBit + 1;
506 mantissa = (size >> mantissaStartBit) & MANTISSA_MASK;
507
508 uint lowBitsMask = (1 << mantissaStartBit) - 1;
509
510 // Round up!
511 if ((size & lowBitsMask) != 0)
512 mantissa++;
513 }
514
515 return (exp << MANTISSA_BITS) + mantissa; // + allows mantissa->exp overflow for round up
516}
517
518//------------------------------------------------------------------------------
522inline uint
524{
525 uint exp = 0;
526 uint mantissa = 0;
527
528 if (size < MANTISSA_VALUE)
529 {
530 // Denorm: 0..(MANTISSA_VALUE-1)
531 mantissa = size;
532 }
533 else
534 {
535 // Normalized: Hidden high bit always 1. Not stored. Just like float.
536 uint highestSetBit = Util::LastBitSetIndex(size);
537
538 uint mantissaStartBit = highestSetBit - MANTISSA_BITS;
539 exp = mantissaStartBit + 1;
540 mantissa = (size >> mantissaStartBit) & MANTISSA_MASK;
541 }
542
543 return (exp << MANTISSA_BITS) | mantissa;
544}
545
546//------------------------------------------------------------------------------
549inline uint
551{
552 n_assert(size < (1 << bucket));
553 uint mask = (size >> (bucket - 4)) & (NUM_BINS_PER_BUCKET - 1u);
554 return mask;
555}
556
557//------------------------------------------------------------------------------
562{
564 uint index;
565 if (round)
566 index = BinMaskRoundedUp(size);
567 else
568 index = BinMask(size);
569 ret.bucket = index >> BUCKET_INDEX_SHIFT;
570 ret.bin = index & BIN_INDEX_MASK;
571 ret.index = index;
572 return ret;
573}
574
575} // namespace Memory
Implements helper functions for checking bits.
~RangeAllocator()
Destructor.
Definition rangeallocator.h:157
RangeAllocator()
Default constructor.
Definition rangeallocator.h:133
void Clear()
Clears the allocator.
Definition rangeallocator.h:165
uint size
Definition rangeallocator.h:108
static uint BinFromSize(uint size, uint bucket)
Get bin from index.
Definition rangeallocator.h:550
Util::Array< RangeAllocatorNode > nodes
Definition rangeallocator.h:121
void RemoveNode(uint nodeIndex)
Remove node from bin.
Definition rangeallocator.h:433
uint InsertNode(uint size, uint offset)
Insert node at bin location, return node index.
Definition rangeallocator.h:385
static constexpr uint NUM_BUCKETS
Definition rangeallocator.h:113
static constexpr uint NUM_BINS_PER_BUCKET
Definition rangeallocator.h:114
static uint BucketFromSize(uint size)
Get bucket from index.
uint bucketUsageMask
Definition rangeallocator.h:117
uint numAllocs
Definition rangeallocator.h:111
bool Empty()
Checks if the allocator is empty.
Definition rangeallocator.h:196
uint freeNodeIterator
Definition rangeallocator.h:110
static BinIndex IndexFromSize(uint size, bool round=false)
Get bin index from size.
Definition rangeallocator.h:561
RangeAllocation Alloc(uint size, uint alignment=1)
Allocate range.
Definition rangeallocator.h:223
uint binMasks[NUM_BUCKETS]
Definition rangeallocator.h:118
uint freeStorage
Definition rangeallocator.h:109
Util::Array< uint > freeNodes
Definition rangeallocator.h:122
void Dealloc(const RangeAllocation &allocation)
Deallocate range.
Definition rangeallocator.h:323
uint binHeads[NUM_BUCKETS *NUM_BINS_PER_BUCKET]
Definition rangeallocator.h:119
Nebula's dynamic array class.
Definition array.h:60
#define n_assert(exp)
Definition debug.h:50
__forceinline unsigned int align(unsigned int alignant, unsigned int alignment)
Definition scalar.h:741
Definition arenaallocator.h:31
static constexpr uint MANTISSA_VALUE
Definition rangeallocator.h:480
uint BinMask(uint size)
Calculate bin mask using 16 bit float distribution, and round up to highest value.
Definition rangeallocator.h:523
static constexpr uint BUCKET_INDEX_SHIFT
Definition rangeallocator.h:126
uint FindLowestSetBitAfter(uint bitMask, uint startBitIndex)
Definition rangeallocator.h:205
static constexpr uint MANTISSA_MASK
Definition rangeallocator.h:481
static constexpr uint MANTISSA_BITS
Definition rangeallocator.h:479
uint BinMaskRoundedUp(uint size)
Calculate bin mask using 16 bit float distribution.
Definition rangeallocator.h:489
static constexpr uint BIN_INDEX_MASK
Definition rangeallocator.h:127
uint CountTrailingZeroes(uint value)
Definition bit.h:261
uint LastBitSetIndex(uint value)
Definition bit.h:199
Describes a range allocated by the Memory::RangeAllocator.
Definition rangeallocator.h:25
uint offset
Definition rangeallocator.h:26
static constexpr uint OOM
Definition rangeallocator.h:30
uint size
Definition rangeallocator.h:27
uint node
Definition rangeallocator.h:28
Definition rangeallocator.h:93
uint bin
Definition rangeallocator.h:95
uint index
Definition rangeallocator.h:97
uint bucket
Definition rangeallocator.h:96
BinIndex()
Definition rangeallocator.h:94
Definition rangeallocator.h:73
uint offset
Definition rangeallocator.h:85
bool resident
Definition rangeallocator.h:83
static constexpr uint END
Definition rangeallocator.h:87
uint blockNext
Definition rangeallocator.h:89
uint size
Definition rangeallocator.h:84
RangeAllocatorNode()
Definition rangeallocator.h:74
uint blockPrev
Definition rangeallocator.h:89
uint binPrev
Definition rangeallocator.h:88
uint binNext
Definition rangeallocator.h:88
int SizeT
Definition types.h:42
unsigned int uint
Definition types.h:33