1 // Modified version of std/experimental/allocator/building_blocks/_free_list.d 2 // Orginal version did not support deallocateAll without parent allocator supporting it 3 // This version supports deallocateAll by using parents Allocator.deallocate to deallocate whole support buffer 4 module mutils.allocator.free_list; 5 6 import std.experimental.allocator.common; 7 import std.typecons : Flag, Yes, No; 8 9 /* 10 Returns `true` if `ptr` is aligned at `alignment`. 11 */ 12 @nogc nothrow pure 13 package bool alignedAt(T)(T* ptr, uint alignment) 14 { 15 return cast(size_t) ptr % alignment == 0; 16 } 17 18 struct FreeList(ParentAllocator, 19 size_t minSize, size_t maxSize = minSize, 20 Flag!"adaptive" adaptive = No.adaptive) 21 { 22 import std.conv : text; 23 import std.exception : enforce; 24 import std.traits : hasMember; 25 import std.typecons : Ternary; 26 import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; 27 28 static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); 29 static assert(maxSize >= (void*).sizeof, 30 "Maximum size must accommodate a pointer."); 31 32 private enum unchecked = minSize == 0 && maxSize == unbounded; 33 34 private enum hasTolerance = !unchecked && (minSize != maxSize 35 || maxSize == chooseAtRuntime); 36 37 static if (minSize == chooseAtRuntime) 38 { 39 /** 40 Returns the smallest allocation size eligible for allocation from the 41 freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias 42 for `minSize`.) 43 */ 44 @property size_t min() const 45 { 46 assert(_min != chooseAtRuntime); 47 return _min; 48 } 49 /** 50 If `FreeList` has been instantiated with $(D minSize == 51 chooseAtRuntime), then the `min` property is writable. Setting it 52 must precede any allocation. 53 54 Params: 55 low = new value for `min` 56 57 Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and 58 `max` has not yet been initialized. Also, no allocation has been 59 yet done with this allocator. 60 61 Postcondition: $(D min == low) 62 */ 63 @property void min(size_t low) 64 { 65 assert(low <= max || max == chooseAtRuntime); 66 minimize; 67 _min = low; 68 } 69 } 70 else 71 { 72 alias min = minSize; 73 } 74 75 static if (maxSize == chooseAtRuntime) 76 { 77 /** 78 Returns the largest allocation size eligible for allocation from the 79 freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias 80 for `maxSize`.) All allocation requests for sizes greater than or 81 equal to `min` and less than or equal to `max` are rounded to $(D 82 max) and forwarded to the parent allocator. When the block fitting the 83 same constraint gets deallocated, it is put in the freelist with the 84 allocated size assumed to be `max`. 85 */ 86 @property size_t max() const { return _max; } 87 88 /** 89 If `FreeList` has been instantiated with $(D maxSize == 90 chooseAtRuntime), then the `max` property is writable. Setting it 91 must precede any allocation. 92 93 Params: 94 high = new value for `max` 95 96 Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and 97 `min` has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator. 98 99 Postcondition: $(D max == high) 100 */ 101 @property void max(size_t high) 102 { 103 assert((high >= min || min == chooseAtRuntime) 104 && high >= (void*).sizeof); 105 minimize; 106 _max = high; 107 } 108 109 @system unittest 110 { 111 import std.experimental.allocator.common : chooseAtRuntime; 112 import std.experimental.allocator.mallocator : Mallocator; 113 114 FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; 115 a.min = 64; 116 a.max = 128; 117 assert(a.min == 64); 118 assert(a.max == 128); 119 } 120 } 121 else 122 { 123 alias max = maxSize; 124 } 125 126 private bool tooSmall(size_t n) const 127 { 128 static if (minSize == 0) return false; 129 else return n < min; 130 } 131 132 private bool tooLarge(size_t n) const 133 { 134 static if (maxSize == unbounded) return false; 135 else return n > max; 136 } 137 138 private bool freeListEligible(size_t n) const 139 { 140 static if (unchecked) 141 { 142 return true; 143 } 144 else 145 { 146 static if (minSize == 0) 147 { 148 if (!n) return false; 149 } 150 static if (minSize == maxSize && minSize != chooseAtRuntime) 151 return n == maxSize; 152 else 153 return !tooSmall(n) && !tooLarge(n); 154 } 155 } 156 157 static if (!unchecked) 158 private void[] blockFor(Node* p) 159 { 160 assert(p); 161 return (cast(void*) p)[0 .. max]; 162 } 163 164 // statistics 165 static if (adaptive == Yes.adaptive) 166 { 167 private enum double windowLength = 1000.0; 168 private enum double tooFewMisses = 0.01; 169 private double probMiss = 1.0; // start with a high miss probability 170 private uint accumSamples, accumMisses; 171 172 void updateStats() 173 { 174 assert(accumSamples >= accumMisses); 175 /* 176 Given that for the past windowLength samples we saw misses with 177 estimated probability probMiss, and assuming the new sample wasMiss or 178 not, what's the new estimated probMiss? 179 */ 180 probMiss = (probMiss * windowLength + accumMisses) 181 / (windowLength + accumSamples); 182 assert(probMiss <= 1.0); 183 accumSamples = 0; 184 accumMisses = 0; 185 // If probability to miss is under x%, yank one off the freelist 186 static if (!unchecked) 187 { 188 if (probMiss < tooFewMisses && _root) 189 { 190 auto b = blockFor(_root); 191 _root = _root.next; 192 parent.deallocate(b); 193 } 194 } 195 } 196 } 197 198 private struct Node { Node* next; } 199 static assert(ParentAllocator.alignment >= Node.alignof); 200 201 // state 202 /** 203 The parent allocator. Depending on whether `ParentAllocator` holds state 204 or not, this is a member variable or an alias for 205 `ParentAllocator.instance`. 206 */ 207 static if (stateSize!ParentAllocator) ParentAllocator parent; 208 else alias parent = ParentAllocator.instance; 209 private Node* root; 210 static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime; 211 static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime; 212 213 /** 214 Alignment offered. 215 */ 216 alias alignment = ParentAllocator.alignment; 217 218 /** 219 If $(D maxSize == unbounded), returns `parent.goodAllocSize(bytes)`. 220 Otherwise, returns `max` for sizes in the interval $(D [min, max]), and 221 `parent.goodAllocSize(bytes)` otherwise. 222 223 Precondition: 224 If set at runtime, `min` and/or `max` must be initialized 225 appropriately. 226 227 Postcondition: 228 $(D result >= bytes) 229 */ 230 size_t goodAllocSize(size_t bytes) 231 { 232 assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime); 233 static if (maxSize != unbounded) 234 { 235 if (freeListEligible(bytes)) 236 { 237 assert(parent.goodAllocSize(max) == max, 238 text("Wrongly configured freelist: maximum should be ", 239 parent.goodAllocSize(max), " instead of ", max)); 240 return max; 241 } 242 } 243 return parent.goodAllocSize(bytes); 244 } 245 246 private void[] allocateEligible(size_t bytes) 247 { 248 assert(bytes); 249 if (root) 250 { 251 // faster 252 auto result = (cast(ubyte*) root)[0 .. bytes]; 253 root = root.next; 254 return result; 255 } 256 // slower 257 static if (hasTolerance) 258 { 259 immutable toAllocate = max; 260 } 261 else 262 { 263 alias toAllocate = bytes; 264 } 265 assert(toAllocate == max || max == unbounded); 266 auto result = parent.allocate(toAllocate); 267 static if (hasTolerance) 268 { 269 if (result) result = result.ptr[0 .. bytes]; 270 } 271 static if (adaptive == Yes.adaptive) 272 { 273 ++accumMisses; 274 updateStats; 275 } 276 return result; 277 } 278 279 /** 280 Allocates memory either off of the free list or from the parent allocator. 281 If `n` is within $(D [min, max]) or if the free list is unchecked 282 ($(D minSize == 0 && maxSize == size_t.max)), then the free list is 283 consulted first. If not empty (hit), the block at the front of the free 284 list is removed from the list and returned. Otherwise (miss), a new block 285 of `max` bytes is allocated, truncated to `n` bytes, and returned. 286 287 Params: 288 n = number of bytes to allocate 289 290 Returns: 291 The allocated block, or `null`. 292 293 Precondition: 294 If set at runtime, `min` and/or `max` must be initialized 295 appropriately. 296 297 Postcondition: $(D result.length == bytes || result is null) 298 */ 299 void[] allocate(size_t n) 300 { 301 static if (adaptive == Yes.adaptive) ++accumSamples; 302 assert(n < size_t.max / 2); 303 // fast path 304 if (freeListEligible(n)) 305 { 306 return allocateEligible(n); 307 } 308 // slower 309 static if (adaptive == Yes.adaptive) 310 { 311 updateStats; 312 } 313 return parent.allocate(n); 314 } 315 316 // Forwarding methods 317 mixin(forwardToMember("parent", 318 "expand", "owns", "reallocate")); 319 320 /** 321 If `block.length` is within $(D [min, max]) or if the free list is 322 unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the 323 block at the front of the free list. For all others, forwards to $(D 324 parent.deallocate) if `Parent.deallocate` is defined. 325 326 Params: 327 block = Block to deallocate. 328 329 Precondition: 330 If set at runtime, `min` and/or `max` must be initialized 331 appropriately. The block must have been allocated with this 332 freelist, and no dynamic changing of `min` or `max` is allowed to 333 occur between allocation and deallocation. 334 */ 335 bool deallocate(void[] block) 336 { 337 if (freeListEligible(block.length)) 338 { 339 if (min == 0) 340 { 341 // In this case a null pointer might have made it this far. 342 if (block is null) return true; 343 } 344 auto t = root; 345 root = cast(Node*) block.ptr; 346 root.next = t; 347 return true; 348 } 349 static if (hasMember!(ParentAllocator, "deallocate")) 350 return parent.deallocate(block); 351 else 352 return false; 353 } 354 355 /** 356 Defined only if `ParentAllocator` defines `deallocateAll`. If so, 357 forwards to it and resets the freelist. 358 */ 359 static if (hasMember!(ParentAllocator, "deallocateAll")) 360 bool deallocateAll() 361 { 362 root = null; 363 return parent.deallocateAll(); 364 } 365 366 /** 367 Nonstandard function that minimizes the memory usage of the freelist by 368 freeing each element in turn. Defined only if `ParentAllocator` defines 369 `deallocate`. $(D FreeList!(0, unbounded)) does not have this function. 370 */ 371 static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) 372 void minimize() 373 { 374 while (root) 375 { 376 auto nuke = blockFor(root); 377 root = root.next; 378 parent.deallocate(nuke); 379 } 380 } 381 382 /** 383 If `ParentAllocator` defines `deallocate`, the list frees all nodes 384 on destruction. $(D FreeList!(0, unbounded)) does not deallocate the memory 385 on destruction. 386 */ 387 static if (!is(ParentAllocator == NullAllocator) && 388 hasMember!(ParentAllocator, "deallocate") && !unchecked) 389 ~this() 390 { 391 minimize(); 392 } 393 } 394 395 396 /** 397 Free list built on top of exactly one contiguous block of memory. The block is 398 assumed to have been allocated with `ParentAllocator`, and is released in 399 `ContiguousFreeList`'s destructor (unless `ParentAllocator` is $(D 400 NullAllocator)). 401 402 `ContiguousFreeList` has most advantages of `FreeList` but fewer 403 disadvantages. It has better cache locality because items are closer to one 404 another. It imposes less fragmentation on its parent allocator. 405 406 The disadvantages of `ContiguousFreeList` over `FreeList` are its pay 407 upfront model (as opposed to `FreeList`'s pay-as-you-go approach), and a 408 hard limit on the number of nodes in the list. Thus, a large number of long- 409 lived objects may occupy the entire block, making it unavailable for serving 410 allocations from the free list. However, an absolute cap on the free list size 411 may be beneficial. 412 413 The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not 414 available for `ContiguousFreeList`. 415 */ 416 struct ContiguousFreeList(ParentAllocator, 417 size_t minSize, size_t maxSize = minSize) 418 { 419 import std.experimental.allocator.building_blocks.null_allocator 420 : NullAllocator; 421 import std.experimental.allocator.building_blocks.stats_collector 422 : StatsCollector, Options; 423 import std.traits : hasMember; 424 import std.typecons : Ternary; 425 426 alias Impl = FreeList!(NullAllocator, minSize, maxSize); 427 enum unchecked = minSize == 0 && maxSize == unbounded; 428 alias Node = Impl.Node; 429 430 alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed); 431 432 // state 433 /** 434 The parent allocator. Depending on whether `ParentAllocator` holds state 435 or not, this is a member variable or an alias for 436 `ParentAllocator.instance`. 437 */ 438 SParent parent; 439 FreeList!(NullAllocator, minSize, maxSize) fl; 440 void[] support; 441 size_t allocated; 442 443 /// Alignment offered. 444 enum uint alignment = (void*).alignof; 445 446 private void initialize(ubyte[] buffer, size_t itemSize = fl.max) 447 { 448 assert(itemSize != unbounded && itemSize != chooseAtRuntime); 449 assert(buffer.ptr.alignedAt(alignment)); 450 immutable available = buffer.length / itemSize; 451 if (available == 0) return; 452 support = buffer; 453 fl.root = cast(Node*) buffer.ptr; 454 auto past = cast(Node*) (buffer.ptr + available * itemSize); 455 for (auto n = fl.root; ; ) 456 { 457 auto next = cast(Node*) (cast(ubyte*) n + itemSize); 458 if (next == past) 459 { 460 n.next = null; 461 break; 462 } 463 assert(next < past); 464 assert(n < next); 465 n.next = next; 466 n = next; 467 } 468 } 469 470 /** 471 Constructors setting up the memory structured as a free list. 472 473 Params: 474 buffer = Buffer to structure as a free list. If `ParentAllocator` is not 475 `NullAllocator`, the buffer is assumed to be allocated by `parent` 476 and will be freed in the destructor. 477 parent = Parent allocator. For construction from stateless allocators, use 478 their `instance` static member. 479 bytes = Bytes (not items) to be allocated for the free list. Memory will be 480 allocated during construction and deallocated in the destructor. 481 max = Maximum size eligible for freelisting. Construction with this 482 parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize 483 == unbounded). 484 min = Minimum size eligible for freelisting. Construction with this 485 parameter is defined only if $(D minSize == chooseAtRuntime). If this 486 condition is met and no `min` parameter is present, `min` is 487 initialized with `max`. 488 */ 489 static if (!stateSize!ParentAllocator) 490 this(ubyte[] buffer) 491 { 492 initialize(buffer); 493 } 494 495 /// ditto 496 static if (stateSize!ParentAllocator) 497 this(ParentAllocator parent, ubyte[] buffer) 498 { 499 initialize(buffer); 500 this.parent = SParent(parent); 501 } 502 503 /// ditto 504 static if (!stateSize!ParentAllocator) 505 this(size_t bytes) 506 { 507 initialize(cast(ubyte[])(ParentAllocator.instance.allocate(bytes))); 508 } 509 510 /// ditto 511 static if (stateSize!ParentAllocator) 512 this(ParentAllocator parent, size_t bytes) 513 { 514 initialize(cast(ubyte[])(parent.allocate(bytes))); 515 this.parent = SParent(parent); 516 } 517 518 /// ditto 519 static if (!stateSize!ParentAllocator 520 && (maxSize == chooseAtRuntime || maxSize == unbounded)) 521 this(size_t bytes, size_t max) 522 { 523 static if (maxSize == chooseAtRuntime) fl.max = max; 524 static if (minSize == chooseAtRuntime) fl.min = max; 525 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 526 } 527 528 /// ditto 529 static if (stateSize!ParentAllocator 530 && (maxSize == chooseAtRuntime || maxSize == unbounded)) 531 this(ParentAllocator parent, size_t bytes, size_t max) 532 { 533 static if (maxSize == chooseAtRuntime) fl.max = max; 534 static if (minSize == chooseAtRuntime) fl.min = max; 535 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 536 this.parent = SParent(parent); 537 } 538 539 /// ditto 540 static if (!stateSize!ParentAllocator 541 && (maxSize == chooseAtRuntime || maxSize == unbounded) 542 && minSize == chooseAtRuntime) 543 this(size_t bytes, size_t min, size_t max) 544 { 545 static if (maxSize == chooseAtRuntime) fl.max = max; 546 fl.min = min; 547 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 548 static if (stateSize!ParentAllocator) 549 this.parent = SParent(parent); 550 } 551 552 /// ditto 553 static if (stateSize!ParentAllocator 554 && (maxSize == chooseAtRuntime || maxSize == unbounded) 555 && minSize == chooseAtRuntime) 556 this(ParentAllocator parent, size_t bytes, size_t min, size_t max) 557 { 558 static if (maxSize == chooseAtRuntime) fl.max = max; 559 fl.min = min; 560 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 561 static if (stateSize!ParentAllocator) 562 this.parent = SParent(parent); 563 } 564 565 /** 566 If `n` is eligible for freelisting, returns `max`. Otherwise, returns 567 `parent.goodAllocSize(n)`. 568 569 Precondition: 570 If set at runtime, `min` and/or `max` must be initialized 571 appropriately. 572 573 Postcondition: 574 $(D result >= bytes) 575 */ 576 size_t goodAllocSize(size_t n) 577 { 578 if (fl.freeListEligible(n)) return fl.max; 579 return parent.goodAllocSize(n); 580 } 581 582 /** 583 Allocate `n` bytes of memory. If `n` is eligible for freelist and the 584 freelist is not empty, pops the memory off the free list. In all other 585 cases, uses the parent allocator. 586 */ 587 void[] allocate(size_t n) 588 { 589 auto result = fl.allocate(n); 590 if (result) 591 { 592 // Only case we care about: eligible sizes allocated from us 593 ++allocated; 594 return result; 595 } 596 // All others, allocate from parent 597 return parent.allocate(n); 598 } 599 600 /** 601 Defined if `ParentAllocator` defines it. Checks whether the block 602 belongs to this allocator. 603 */ 604 static if (hasMember!(SParent, "owns") || unchecked) 605 // Ternary owns(const void[] b) const ? 606 Ternary owns(void[] b) 607 { 608 if ((() @trusted => support && b 609 && (&support[0] <= &b[0]) 610 && (&b[0] < &support[0] + support.length) 611 )()) 612 return Ternary.yes; 613 static if (unchecked) 614 return Ternary.no; 615 else 616 return parent.owns(b); 617 } 618 619 /** 620 Deallocates `b`. If it's of eligible size, it's put on the free list. 621 Otherwise, it's returned to `parent`. 622 623 Precondition: `b` has been allocated with this allocator, or is $(D 624 null). 625 */ 626 bool deallocate(void[] b) 627 { 628 if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length) 629 { 630 // we own this guy 631 assert(fl.freeListEligible(b.length)); 632 assert(allocated); 633 --allocated; 634 // Put manually in the freelist 635 auto t = fl.root; 636 fl.root = cast(Node*) b.ptr; 637 fl.root.next = t; 638 return true; 639 } 640 return parent.deallocate(b); 641 } 642 643 /** 644 Deallocates everything from the parent. 645 */ 646 static if (hasMember!(ParentAllocator, "deallocateAll") 647 && stateSize!ParentAllocator) 648 bool deallocateAll() 649 { 650 bool result = fl.deallocateAll && parent.deallocateAll; 651 allocated = 0; 652 return result; 653 } 654 655 /** 656 Deallocates everything(support) using the parent. 657 */ 658 static if (!stateSize!ParentAllocator) 659 bool deallocateAll() 660 { 661 bool result = parent.deallocate(support); 662 allocated = 0; 663 return result; 664 } 665 /** 666 Returns `Ternary.yes` if no memory is currently allocated with this 667 allocator, `Ternary.no` otherwise. This method never returns 668 `Ternary.unknown`. 669 */ 670 Ternary empty() 671 { 672 return Ternary(allocated == 0 && parent.bytesUsed == 0); 673 } 674 }