1 // Written in the D programming language. 2 /** 3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/free_list.d) 4 */ 5 module std.experimental.allocator.building_blocks.free_list; 6 7 import std.experimental.allocator.common; 8 import std.typecons : Flag, Yes, No; 9 10 /** 11 12 $(HTTP en.wikipedia.org/wiki/Free_list, Free list allocator), stackable on top of 13 another allocator. Allocation requests between `min` and `max` bytes are 14 rounded up to `max` and served from a singly-linked list of buffers 15 deallocated in the past. All other allocations are directed to $(D 16 ParentAllocator). Due to the simplicity of free list management, allocations 17 from the free list are fast. If `adaptive` is set to `Yes.adaptive`, 18 the free list gradually reduces its size if allocations tend to use the parent 19 allocator much more than the lists' available nodes. 20 21 One instantiation is of particular interest: $(D FreeList!(0, unbounded)) puts 22 every deallocation in the freelist, and subsequently serves any allocation from 23 the freelist (if not empty). There is no checking of size matching, which would 24 be incorrect for a freestanding allocator but is both correct and fast when an 25 owning allocator on top of the free list allocator (such as `Segregator`) is 26 already in charge of handling size checking. 27 28 The following methods are defined if `ParentAllocator` defines them, and 29 forward to it: `expand`, `owns`, `reallocate`. 30 31 */ 32 struct FreeList(ParentAllocator, 33 size_t minSize, size_t maxSize = minSize, 34 Flag!"adaptive" adaptive = No.adaptive) 35 { 36 import std.conv : text; 37 import std.exception : enforce; 38 import std.traits : hasMember; 39 import std.typecons : Ternary; 40 import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; 41 42 static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); 43 static assert(maxSize >= (void*).sizeof, 44 "Maximum size must accommodate a pointer."); 45 46 private enum unchecked = minSize == 0 && maxSize == unbounded; 47 48 private enum hasTolerance = !unchecked && (minSize != maxSize 49 || maxSize == chooseAtRuntime); 50 51 static if (minSize == chooseAtRuntime) 52 { 53 /** 54 Returns the smallest allocation size eligible for allocation from the 55 freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias 56 for `minSize`.) 57 */ 58 @property size_t min() const 59 { 60 assert(_min != chooseAtRuntime); 61 return _min; 62 } 63 /** 64 If `FreeList` has been instantiated with $(D minSize == 65 chooseAtRuntime), then the `min` property is writable. Setting it 66 must precede any allocation. 67 68 Params: 69 low = new value for `min` 70 71 Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and 72 `max` has not yet been initialized. Also, no allocation has been 73 yet done with this allocator. 74 75 Postcondition: $(D min == low) 76 */ 77 @property void min(size_t low) 78 { 79 assert(low <= max || max == chooseAtRuntime); 80 minimize; 81 _min = low; 82 } 83 } 84 else 85 { 86 alias min = minSize; 87 } 88 89 static if (maxSize == chooseAtRuntime) 90 { 91 /** 92 Returns the largest allocation size eligible for allocation from the 93 freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias 94 for `maxSize`.) All allocation requests for sizes greater than or 95 equal to `min` and less than or equal to `max` are rounded to $(D 96 max) and forwarded to the parent allocator. When the block fitting the 97 same constraint gets deallocated, it is put in the freelist with the 98 allocated size assumed to be `max`. 99 */ 100 @property size_t max() const { return _max; } 101 102 /** 103 If `FreeList` has been instantiated with $(D maxSize == 104 chooseAtRuntime), then the `max` property is writable. Setting it 105 must precede any allocation. 106 107 Params: 108 high = new value for `max` 109 110 Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and 111 `min` has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator. 112 113 Postcondition: $(D max == high) 114 */ 115 @property void max(size_t high) 116 { 117 assert((high >= min || min == chooseAtRuntime) 118 && high >= (void*).sizeof); 119 minimize; 120 _max = high; 121 } 122 123 @system unittest 124 { 125 import std.experimental.allocator.common : chooseAtRuntime; 126 import std.experimental.allocator.mallocator : Mallocator; 127 128 FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; 129 a.min = 64; 130 a.max = 128; 131 assert(a.min == 64); 132 assert(a.max == 128); 133 } 134 } 135 else 136 { 137 alias max = maxSize; 138 } 139 140 private bool tooSmall(size_t n) const 141 { 142 static if (minSize == 0) return false; 143 else return n < min; 144 } 145 146 private bool tooLarge(size_t n) const 147 { 148 static if (maxSize == unbounded) return false; 149 else return n > max; 150 } 151 152 private bool freeListEligible(size_t n) const 153 { 154 static if (unchecked) 155 { 156 return true; 157 } 158 else 159 { 160 static if (minSize == 0) 161 { 162 if (!n) return false; 163 } 164 static if (minSize == maxSize && minSize != chooseAtRuntime) 165 return n == maxSize; 166 else 167 return !tooSmall(n) && !tooLarge(n); 168 } 169 } 170 171 static if (!unchecked) 172 private void[] blockFor(Node* p) 173 { 174 assert(p); 175 return (cast(void*) p)[0 .. max]; 176 } 177 178 // statistics 179 static if (adaptive == Yes.adaptive) 180 { 181 private enum double windowLength = 1000.0; 182 private enum double tooFewMisses = 0.01; 183 private double probMiss = 1.0; // start with a high miss probability 184 private uint accumSamples, accumMisses; 185 186 void updateStats() 187 { 188 assert(accumSamples >= accumMisses); 189 /* 190 Given that for the past windowLength samples we saw misses with 191 estimated probability probMiss, and assuming the new sample wasMiss or 192 not, what's the new estimated probMiss? 193 */ 194 probMiss = (probMiss * windowLength + accumMisses) 195 / (windowLength + accumSamples); 196 assert(probMiss <= 1.0); 197 accumSamples = 0; 198 accumMisses = 0; 199 // If probability to miss is under x%, yank one off the freelist 200 static if (!unchecked) 201 { 202 if (probMiss < tooFewMisses && _root) 203 { 204 auto b = blockFor(_root); 205 _root = _root.next; 206 parent.deallocate(b); 207 } 208 } 209 } 210 } 211 212 private struct Node { Node* next; } 213 static assert(ParentAllocator.alignment >= Node.alignof); 214 215 // state 216 /** 217 The parent allocator. Depending on whether `ParentAllocator` holds state 218 or not, this is a member variable or an alias for 219 `ParentAllocator.instance`. 220 */ 221 static if (stateSize!ParentAllocator) ParentAllocator parent; 222 else alias parent = ParentAllocator.instance; 223 private Node* root; 224 static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime; 225 static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime; 226 227 /** 228 Alignment offered. 229 */ 230 alias alignment = ParentAllocator.alignment; 231 232 /** 233 If $(D maxSize == unbounded), returns `parent.goodAllocSize(bytes)`. 234 Otherwise, returns `max` for sizes in the interval $(D [min, max]), and 235 `parent.goodAllocSize(bytes)` otherwise. 236 237 Precondition: 238 If set at runtime, `min` and/or `max` must be initialized 239 appropriately. 240 241 Postcondition: 242 $(D result >= bytes) 243 */ 244 size_t goodAllocSize(size_t bytes) 245 { 246 assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime); 247 static if (maxSize != unbounded) 248 { 249 if (freeListEligible(bytes)) 250 { 251 assert(parent.goodAllocSize(max) == max, 252 text("Wrongly configured freelist: maximum should be ", 253 parent.goodAllocSize(max), " instead of ", max)); 254 return max; 255 } 256 } 257 return parent.goodAllocSize(bytes); 258 } 259 260 private void[] allocateEligible(string fillMode)(size_t bytes) 261 if (fillMode == "void" || fillMode == "zero") 262 { 263 enum bool isFillZero = fillMode == "zero"; 264 assert(bytes); 265 if (root) 266 { 267 // faster 268 auto result = (cast(ubyte*) root)[0 .. bytes]; 269 root = root.next; 270 static if (isFillZero) result[0 .. bytes] = 0; 271 return result; 272 } 273 // slower 274 static if (hasTolerance) 275 { 276 immutable toAllocate = max; 277 } 278 else 279 { 280 alias toAllocate = bytes; 281 } 282 assert(toAllocate == max || max == unbounded); 283 static if (isFillZero) 284 auto result = parent.allocateZeroed(toAllocate); 285 else 286 auto result = parent.allocate(toAllocate); 287 static if (hasTolerance) 288 { 289 if (result) result = result.ptr[0 .. bytes]; 290 } 291 static if (adaptive == Yes.adaptive) 292 { 293 ++accumMisses; 294 updateStats; 295 } 296 return result; 297 } 298 299 /** 300 Allocates memory either off of the free list or from the parent allocator. 301 If `n` is within $(D [min, max]) or if the free list is unchecked 302 ($(D minSize == 0 && maxSize == size_t.max)), then the free list is 303 consulted first. If not empty (hit), the block at the front of the free 304 list is removed from the list and returned. Otherwise (miss), a new block 305 of `max` bytes is allocated, truncated to `n` bytes, and returned. 306 307 Params: 308 n = number of bytes to allocate 309 310 Returns: 311 The allocated block, or `null`. 312 313 Precondition: 314 If set at runtime, `min` and/or `max` must be initialized 315 appropriately. 316 317 Postcondition: $(D result.length == bytes || result is null) 318 */ 319 void[] allocate(size_t n) 320 { 321 static if (adaptive == Yes.adaptive) ++accumSamples; 322 assert(n < size_t.max / 2); 323 // fast path 324 if (freeListEligible(n)) 325 { 326 return allocateEligible!"void"(n); 327 } 328 // slower 329 static if (adaptive == Yes.adaptive) 330 { 331 updateStats; 332 } 333 return parent.allocate(n); 334 } 335 336 static if (hasMember!(ParentAllocator, "allocateZeroed")) 337 package(std) void[] allocateZeroed()(size_t n) 338 { 339 static if (adaptive == Yes.adaptive) ++accumSamples; 340 assert(n < size_t.max / 2); 341 // fast path 342 if (freeListEligible(n)) 343 { 344 return allocateEligible!"zero"(n); 345 } 346 // slower 347 static if (adaptive == Yes.adaptive) 348 { 349 updateStats; 350 } 351 return parent.allocateZeroed(n); 352 } 353 354 // Forwarding methods 355 mixin(forwardToMember("parent", 356 "expand", "owns", "reallocate")); 357 358 /** 359 If `block.length` is within $(D [min, max]) or if the free list is 360 unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the 361 block at the front of the free list. For all others, forwards to $(D 362 parent.deallocate) if `Parent.deallocate` is defined. 363 364 Params: 365 block = Block to deallocate. 366 367 Precondition: 368 If set at runtime, `min` and/or `max` must be initialized 369 appropriately. The block must have been allocated with this 370 freelist, and no dynamic changing of `min` or `max` is allowed to 371 occur between allocation and deallocation. 372 */ 373 bool deallocate(void[] block) 374 { 375 if (freeListEligible(block.length)) 376 { 377 if (min == 0) 378 { 379 // In this case a null pointer might have made it this far. 380 if (block is null) return true; 381 } 382 auto t = root; 383 root = cast(Node*) block.ptr; 384 root.next = t; 385 return true; 386 } 387 static if (hasMember!(ParentAllocator, "deallocate")) 388 return parent.deallocate(block); 389 else 390 return false; 391 } 392 393 /** 394 Defined only if `ParentAllocator` defines `deallocateAll`. If so, 395 forwards to it and resets the freelist. 396 */ 397 static if (hasMember!(ParentAllocator, "deallocateAll")) 398 bool deallocateAll() 399 { 400 root = null; 401 return parent.deallocateAll(); 402 } 403 404 /** 405 Nonstandard function that minimizes the memory usage of the freelist by 406 freeing each element in turn. Defined only if `ParentAllocator` defines 407 `deallocate`. $(D FreeList!(0, unbounded)) does not have this function. 408 */ 409 static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) 410 void minimize() 411 { 412 while (root) 413 { 414 auto nuke = blockFor(root); 415 root = root.next; 416 parent.deallocate(nuke); 417 } 418 } 419 420 /** 421 If `ParentAllocator` defines `deallocate`, the list frees all nodes 422 on destruction. $(D FreeList!(0, unbounded)) does not deallocate the memory 423 on destruction. 424 */ 425 static if (!is(ParentAllocator == NullAllocator) && 426 hasMember!(ParentAllocator, "deallocate") && !unchecked) 427 ~this() 428 { 429 minimize(); 430 } 431 } 432 433 @system unittest 434 { 435 import std.experimental.allocator.mallocator : Mallocator; 436 import std.experimental.allocator.building_blocks.stats_collector 437 : StatsCollector, Options; 438 439 struct StatsCollectorWrapper { 440 ~this() 441 { 442 // buf2 should still be around and buf1 deallocated 443 assert(parent.numDeallocate == 1); 444 assert(parent.bytesUsed == 16); 445 } 446 static StatsCollector!(Mallocator, Options.all) parent; 447 alias parent this; 448 } 449 450 FreeList!(StatsCollectorWrapper, 16, 16) fl; 451 auto buf1 = fl.allocate(16); 452 auto buf2 = fl.allocate(16); 453 assert(fl.parent.bytesUsed == 32); 454 455 // After this, the list has 1 node, so no actual deallocation by Mallocator 456 fl.deallocate(buf1); 457 assert(fl.parent.bytesUsed == 32); 458 459 // Destruction should only deallocate the node 460 destroy(fl); 461 } 462 463 @system unittest 464 { 465 import std.experimental.allocator.gc_allocator : GCAllocator; 466 FreeList!(GCAllocator, 0, 8) fl; 467 assert(fl.root is null); 468 auto b1 = fl.allocate(7); 469 fl.allocate(8); 470 assert(fl.root is null); 471 // Ensure deallocate inherits from parent 472 () nothrow @nogc { fl.deallocate(b1); }(); 473 assert(fl.root !is null); 474 fl.allocate(8); 475 assert(fl.root is null); 476 } 477 478 @system unittest 479 { 480 import std.experimental.allocator.gc_allocator : GCAllocator; 481 FreeList!(GCAllocator, 0, 16) fl; 482 // Not @nogc because of std.conv.text 483 assert((() nothrow @safe /*@nogc*/ => fl.goodAllocSize(1))() == 16); 484 } 485 486 // Test that deallocateAll infers from parent 487 @system unittest 488 { 489 import std.experimental.allocator.building_blocks.region : BorrowedRegion; 490 491 auto fl = FreeList!(BorrowedRegion!(), 0, 16)(BorrowedRegion!()(new ubyte[1024 * 64])); 492 auto b = fl.allocate(42); 493 assert(b.length == 42); 494 assert((() pure nothrow @safe @nogc => fl.expand(b, 48))()); 495 assert(b.length == 90); 496 assert((() nothrow @nogc => fl.reallocate(b, 100))()); 497 assert(b.length == 100); 498 assert((() nothrow @nogc => fl.deallocateAll())()); 499 } 500 501 /** 502 Free list built on top of exactly one contiguous block of memory. The block is 503 assumed to have been allocated with `ParentAllocator`, and is released in 504 `ContiguousFreeList`'s destructor (unless `ParentAllocator` is $(D 505 NullAllocator)). 506 507 `ContiguousFreeList` has most advantages of `FreeList` but fewer 508 disadvantages. It has better cache locality because items are closer to one 509 another. It imposes less fragmentation on its parent allocator. 510 511 The disadvantages of `ContiguousFreeList` over `FreeList` are its pay 512 upfront model (as opposed to `FreeList`'s pay-as-you-go approach), and a 513 hard limit on the number of nodes in the list. Thus, a large number of long- 514 lived objects may occupy the entire block, making it unavailable for serving 515 allocations from the free list. However, an absolute cap on the free list size 516 may be beneficial. 517 518 The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not 519 available for `ContiguousFreeList`. 520 */ 521 struct ContiguousFreeList(ParentAllocator, 522 size_t minSize, size_t maxSize = minSize) 523 { 524 import std.experimental.allocator.building_blocks.null_allocator 525 : NullAllocator; 526 import std.experimental.allocator.building_blocks.stats_collector 527 : StatsCollector, Options; 528 import std.traits : hasMember; 529 import std.typecons : Ternary; 530 531 alias Impl = FreeList!(NullAllocator, minSize, maxSize); 532 enum unchecked = minSize == 0 && maxSize == unbounded; 533 alias Node = Impl.Node; 534 535 alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed); 536 537 // state 538 /** 539 The parent allocator. Depending on whether `ParentAllocator` holds state 540 or not, this is a member variable or an alias for 541 `ParentAllocator.instance`. 542 */ 543 SParent parent; 544 FreeList!(NullAllocator, minSize, maxSize) fl; 545 void[] support; 546 size_t allocated; 547 548 /// Alignment offered. 549 enum uint alignment = (void*).alignof; 550 551 private void initialize(ubyte[] buffer, size_t itemSize = fl.max) 552 { 553 assert(itemSize != unbounded && itemSize != chooseAtRuntime); 554 assert(buffer.ptr.alignedAt(alignment)); 555 immutable available = buffer.length / itemSize; 556 if (available == 0) return; 557 support = buffer; 558 fl.root = cast(Node*) buffer.ptr; 559 auto past = cast(Node*) (buffer.ptr + available * itemSize); 560 for (auto n = fl.root; ; ) 561 { 562 auto next = cast(Node*) (cast(ubyte*) n + itemSize); 563 if (next == past) 564 { 565 n.next = null; 566 break; 567 } 568 assert(next < past); 569 assert(n < next); 570 n.next = next; 571 n = next; 572 } 573 } 574 575 /** 576 Constructors setting up the memory structured as a free list. 577 578 Params: 579 buffer = Buffer to structure as a free list. If `ParentAllocator` is not 580 `NullAllocator`, the buffer is assumed to be allocated by `parent` 581 and will be freed in the destructor. 582 parent = Parent allocator. For construction from stateless allocators, use 583 their `instance` static member. 584 bytes = Bytes (not items) to be allocated for the free list. Memory will be 585 allocated during construction and deallocated in the destructor. 586 max = Maximum size eligible for freelisting. Construction with this 587 parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize 588 == unbounded). 589 min = Minimum size eligible for freelisting. Construction with this 590 parameter is defined only if $(D minSize == chooseAtRuntime). If this 591 condition is met and no `min` parameter is present, `min` is 592 initialized with `max`. 593 */ 594 static if (!stateSize!ParentAllocator) 595 this(ubyte[] buffer) 596 { 597 initialize(buffer); 598 } 599 600 /// ditto 601 static if (stateSize!ParentAllocator) 602 this(ParentAllocator parent, ubyte[] buffer) 603 { 604 initialize(buffer); 605 this.parent = SParent(parent); 606 } 607 608 /// ditto 609 static if (!stateSize!ParentAllocator) 610 this(size_t bytes) 611 { 612 initialize(cast(ubyte[])(ParentAllocator.instance.allocate(bytes))); 613 } 614 615 /// ditto 616 static if (stateSize!ParentAllocator) 617 this(ParentAllocator parent, size_t bytes) 618 { 619 initialize(cast(ubyte[])(parent.allocate(bytes))); 620 this.parent = SParent(parent); 621 } 622 623 /// ditto 624 static if (!stateSize!ParentAllocator 625 && (maxSize == chooseAtRuntime || maxSize == unbounded)) 626 this(size_t bytes, size_t max) 627 { 628 static if (maxSize == chooseAtRuntime) fl.max = max; 629 static if (minSize == chooseAtRuntime) fl.min = max; 630 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 631 } 632 633 /// ditto 634 static if (stateSize!ParentAllocator 635 && (maxSize == chooseAtRuntime || maxSize == unbounded)) 636 this(ParentAllocator parent, size_t bytes, size_t max) 637 { 638 static if (maxSize == chooseAtRuntime) fl.max = max; 639 static if (minSize == chooseAtRuntime) fl.min = max; 640 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 641 this.parent = SParent(parent); 642 } 643 644 /// ditto 645 static if (!stateSize!ParentAllocator 646 && (maxSize == chooseAtRuntime || maxSize == unbounded) 647 && minSize == chooseAtRuntime) 648 this(size_t bytes, size_t min, size_t max) 649 { 650 static if (maxSize == chooseAtRuntime) fl.max = max; 651 fl.min = min; 652 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 653 static if (stateSize!ParentAllocator) 654 this.parent = SParent(parent); 655 } 656 657 /// ditto 658 static if (stateSize!ParentAllocator 659 && (maxSize == chooseAtRuntime || maxSize == unbounded) 660 && minSize == chooseAtRuntime) 661 this(ParentAllocator parent, size_t bytes, size_t min, size_t max) 662 { 663 static if (maxSize == chooseAtRuntime) fl.max = max; 664 fl.min = min; 665 initialize(cast(ubyte[])(parent.allocate(bytes)), max); 666 static if (stateSize!ParentAllocator) 667 this.parent = SParent(parent); 668 } 669 670 /** 671 If `n` is eligible for freelisting, returns `max`. Otherwise, returns 672 `parent.goodAllocSize(n)`. 673 674 Precondition: 675 If set at runtime, `min` and/or `max` must be initialized 676 appropriately. 677 678 Postcondition: 679 $(D result >= bytes) 680 */ 681 size_t goodAllocSize(size_t n) 682 { 683 if (fl.freeListEligible(n)) return fl.max; 684 return parent.goodAllocSize(n); 685 } 686 687 /** 688 Allocate `n` bytes of memory. If `n` is eligible for freelist and the 689 freelist is not empty, pops the memory off the free list. In all other 690 cases, uses the parent allocator. 691 */ 692 void[] allocate(size_t n) 693 { 694 auto result = fl.allocate(n); 695 if (result) 696 { 697 // Only case we care about: eligible sizes allocated from us 698 ++allocated; 699 return result; 700 } 701 // All others, allocate from parent 702 return parent.allocate(n); 703 } 704 705 /** 706 Defined if `ParentAllocator` defines it. Checks whether the block 707 belongs to this allocator. 708 */ 709 static if (hasMember!(SParent, "owns") || unchecked) 710 // Ternary owns(const void[] b) const ? 711 Ternary owns(void[] b) 712 { 713 if ((() @trusted => support && b 714 && (&support[0] <= &b[0]) 715 && (&b[0] < &support[0] + support.length) 716 )()) 717 return Ternary.yes; 718 static if (unchecked) 719 return Ternary.no; 720 else 721 return parent.owns(b); 722 } 723 724 /** 725 Deallocates `b`. If it's of eligible size, it's put on the free list. 726 Otherwise, it's returned to `parent`. 727 728 Precondition: `b` has been allocated with this allocator, or is $(D 729 null). 730 */ 731 bool deallocate(void[] b) 732 { 733 if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length) 734 { 735 // we own this guy 736 assert(fl.freeListEligible(b.length)); 737 assert(allocated); 738 --allocated; 739 // Put manually in the freelist 740 auto t = fl.root; 741 fl.root = cast(Node*) b.ptr; 742 fl.root.next = t; 743 return true; 744 } 745 return parent.deallocate(b); 746 } 747 748 /** 749 Deallocates everything from the parent. 750 */ 751 static if (hasMember!(ParentAllocator, "deallocateAll") 752 && stateSize!ParentAllocator) 753 bool deallocateAll() 754 { 755 bool result = fl.deallocateAll && parent.deallocateAll; 756 allocated = 0; 757 return result; 758 } 759 760 /** 761 Returns `Ternary.yes` if no memory is currently allocated with this 762 allocator, `Ternary.no` otherwise. This method never returns 763 `Ternary.unknown`. 764 */ 765 Ternary empty() 766 { 767 return Ternary(allocated == 0 && parent.bytesUsed == 0); 768 } 769 } 770 771 /// 772 @safe unittest 773 { 774 import std.experimental.allocator.building_blocks.allocator_list 775 : AllocatorList; 776 import std.experimental.allocator.gc_allocator : GCAllocator; 777 778 import std.experimental.allocator.common : unbounded; 779 780 alias ScalableFreeList = AllocatorList!((n) => 781 ContiguousFreeList!(GCAllocator, 0, unbounded)(4096) 782 ); 783 } 784 785 @system unittest 786 { 787 import std.experimental.allocator.building_blocks.null_allocator 788 : NullAllocator; 789 import std.typecons : Ternary; 790 alias A = ContiguousFreeList!(NullAllocator, 0, 64); 791 auto a = A(new ubyte[1024]); 792 793 assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); 794 795 assert((() pure nothrow @safe @nogc => a.goodAllocSize(15))() == 64); 796 assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() 797 == (() nothrow @safe @nogc => NullAllocator.instance.goodAllocSize(65))()); 798 799 auto b = a.allocate(100); 800 assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); 801 assert(b.length == 0); 802 // Ensure deallocate inherits from parent 803 () nothrow @nogc { a.deallocate(b); }(); 804 b = a.allocate(64); 805 assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); 806 assert(b.length == 64); 807 assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); 808 assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no); 809 () nothrow @nogc { a.deallocate(b); }(); 810 } 811 812 @system unittest 813 { 814 import std.experimental.allocator.building_blocks.region : Region; 815 import std.experimental.allocator.gc_allocator : GCAllocator; 816 import std.typecons : Ternary; 817 alias A = ContiguousFreeList!(Region!GCAllocator, 0, 64); 818 auto a = A(Region!GCAllocator(1024 * 4), 1024); 819 820 assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); 821 822 assert((() pure nothrow @safe @nogc => a.goodAllocSize(15))() == 64); 823 assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() 824 == (() pure nothrow @safe @nogc => a.parent.goodAllocSize(65))()); 825 826 auto b = a.allocate(100); 827 assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); 828 assert(a.allocated == 0); 829 assert(b.length == 100); 830 // Ensure deallocate inherits from parent 831 assert((() nothrow @nogc => a.deallocate(b))()); 832 assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); 833 b = a.allocate(64); 834 assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); 835 assert(b.length == 64); 836 assert(a.reallocate(b, 100)); 837 assert(b.length == 100); 838 assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); 839 assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no); 840 // Test deallocate infers from parent 841 assert((() nothrow @nogc => a.deallocate(b))()); 842 assert((() nothrow @nogc => a.deallocateAll())()); 843 } 844 845 @system unittest 846 { 847 import std.experimental.allocator.gc_allocator : GCAllocator; 848 alias A = ContiguousFreeList!(GCAllocator, 64, 64); 849 auto a = A(1024); 850 const b = a.allocate(100); 851 assert(b.length == 100); 852 } 853 854 /** 855 FreeList shared across threads. Allocation and deallocation are lock-free. The 856 parameters have the same semantics as for `FreeList`. 857 858 `expand` is defined to forward to `ParentAllocator.expand` 859 (it must be also `shared`). 860 */ 861 struct SharedFreeList(ParentAllocator, 862 size_t minSize, size_t maxSize = minSize, size_t approxMaxNodes = unbounded) 863 { 864 import std.conv : text; 865 import std.exception : enforce; 866 import std.traits : hasMember; 867 868 static if (hasMember!(ParentAllocator, "owns")) 869 { 870 import std.typecons : Ternary; 871 } 872 873 static assert(approxMaxNodes, "approxMaxNodes must not be null."); 874 static assert(minSize != unbounded, "Use minSize = 0 for no low bound."); 875 static assert(maxSize >= (void*).sizeof, 876 "Maximum size must accommodate a pointer."); 877 878 import core.atomic : atomicOp, cas; 879 import core.internal.spinlock : SpinLock; 880 881 private enum unchecked = minSize == 0 && maxSize == unbounded; 882 883 static if (minSize != chooseAtRuntime) 884 { 885 alias min = minSize; 886 } 887 else 888 { 889 private shared size_t _min = chooseAtRuntime; 890 @property size_t min() const shared 891 { 892 assert(_min != chooseAtRuntime); 893 return _min; 894 } 895 @property void min(size_t x) shared 896 { 897 enforce(x <= max); 898 enforce(cas(&_min, chooseAtRuntime, x), 899 "SharedFreeList.min must be initialized exactly once."); 900 } 901 static if (maxSize == chooseAtRuntime) 902 { 903 // Both bounds can be set, provide one function for setting both in 904 // one shot. 905 void setBounds(size_t low, size_t high) shared 906 { 907 enforce(low <= high && high >= (void*).sizeof); 908 enforce(cas(&_min, chooseAtRuntime, low), 909 "SharedFreeList.min must be initialized exactly once."); 910 enforce(cas(&_max, chooseAtRuntime, high), 911 "SharedFreeList.max must be initialized exactly once."); 912 } 913 } 914 } 915 916 private bool tooSmall(size_t n) const shared 917 { 918 static if (minSize == 0) return false; 919 else static if (minSize == chooseAtRuntime) return n < _min; 920 else return n < minSize; 921 } 922 923 static if (maxSize != chooseAtRuntime) 924 { 925 alias max = maxSize; 926 } 927 else 928 { 929 private shared size_t _max = chooseAtRuntime; 930 @property size_t max() const shared { return _max; } 931 @property void max(size_t x) shared 932 { 933 enforce(x >= min && x >= (void*).sizeof); 934 enforce(cas(&_max, chooseAtRuntime, x), 935 "SharedFreeList.max must be initialized exactly once."); 936 } 937 } 938 939 private bool tooLarge(size_t n) const shared 940 { 941 static if (maxSize == unbounded) return false; 942 else static if (maxSize == chooseAtRuntime) return n > _max; 943 else return n > maxSize; 944 } 945 946 private bool freeListEligible(size_t n) const shared 947 { 948 static if (minSize == maxSize && minSize != chooseAtRuntime) 949 return n == maxSize; 950 else return !tooSmall(n) && !tooLarge(n); 951 } 952 953 static if (approxMaxNodes != chooseAtRuntime) 954 { 955 alias approxMaxLength = approxMaxNodes; 956 } 957 else 958 { 959 private shared size_t _approxMaxLength = chooseAtRuntime; 960 @property size_t approxMaxLength() const shared { return _approxMaxLength; } 961 @property void approxMaxLength(size_t x) shared { _approxMaxLength = enforce(x); } 962 } 963 964 static if (approxMaxNodes != unbounded) 965 { 966 private shared size_t nodes; 967 private void incNodes() shared 968 { 969 atomicOp!("+=")(nodes, 1); 970 } 971 private void decNodes() shared 972 { 973 assert(nodes); 974 atomicOp!("-=")(nodes, 1); 975 } 976 private void resetNodes() shared 977 { 978 nodes = 0; 979 } 980 private bool nodesFull() shared 981 { 982 return nodes >= approxMaxLength; 983 } 984 } 985 else 986 { 987 private static void incNodes() { } 988 private static void decNodes() { } 989 private static void resetNodes() { } 990 private enum bool nodesFull = false; 991 } 992 993 version (StdDdoc) 994 { 995 /** 996 Properties for getting (and possibly setting) the bounds. Setting bounds 997 is allowed only once , and before any allocation takes place. Otherwise, 998 the primitives have the same semantics as those of `FreeList`. 999 */ 1000 @property size_t min(); 1001 /// Ditto 1002 @property void min(size_t newMinSize); 1003 /// Ditto 1004 @property size_t max(); 1005 /// Ditto 1006 @property void max(size_t newMaxSize); 1007 /// Ditto 1008 void setBounds(size_t newMin, size_t newMax); 1009 1010 /** 1011 Properties for getting (and possibly setting) the approximate maximum length of a shared freelist. 1012 */ 1013 @property size_t approxMaxLength() const shared; 1014 /// ditto 1015 @property void approxMaxLength(size_t x) shared; 1016 } 1017 1018 /** 1019 The parent allocator. Depending on whether `ParentAllocator` holds state 1020 or not, this is a member variable or an alias for 1021 `ParentAllocator.instance`. 1022 */ 1023 static if (stateSize!ParentAllocator) shared ParentAllocator parent; 1024 else alias parent = ParentAllocator.instance; 1025 1026 mixin(forwardToMember("parent", "expand")); 1027 1028 private SpinLock lock; 1029 1030 private struct Node { Node* next; } 1031 static assert(ParentAllocator.alignment >= Node.alignof); 1032 private Node* _root; 1033 1034 /// Standard primitives. 1035 enum uint alignment = ParentAllocator.alignment; 1036 1037 /// Ditto 1038 size_t goodAllocSize(size_t bytes) shared 1039 { 1040 if (freeListEligible(bytes)) return maxSize == unbounded ? bytes : max; 1041 return parent.goodAllocSize(bytes); 1042 } 1043 1044 /// Ditto 1045 static if (hasMember!(ParentAllocator, "owns")) 1046 Ternary owns(const void[] b) shared const 1047 { 1048 return parent.owns(b); 1049 } 1050 1051 /// Ditto 1052 static if (hasMember!(ParentAllocator, "reallocate")) 1053 bool reallocate(ref void[] b, size_t s) shared 1054 { 1055 return parent.reallocate(b, s); 1056 } 1057 1058 /// Ditto 1059 void[] allocate(size_t bytes) shared 1060 { 1061 assert(bytes < size_t.max / 2); 1062 if (!freeListEligible(bytes)) return parent.allocate(bytes); 1063 if (maxSize != unbounded) bytes = max; 1064 1065 // Try to pop off the freelist 1066 lock.lock(); 1067 if (!_root) 1068 { 1069 lock.unlock(); 1070 return allocateFresh(bytes); 1071 } 1072 else 1073 { 1074 auto oldRoot = _root; 1075 _root = _root.next; 1076 decNodes(); 1077 lock.unlock(); 1078 return (cast(ubyte*) oldRoot)[0 .. bytes]; 1079 } 1080 } 1081 1082 private void[] allocateFresh(const size_t bytes) shared 1083 { 1084 assert(bytes == max || max == unbounded); 1085 return parent.allocate(bytes); 1086 } 1087 1088 /// Ditto 1089 bool deallocate(void[] b) shared 1090 { 1091 if (!nodesFull && freeListEligible(b.length)) 1092 { 1093 auto newRoot = cast(shared Node*) b.ptr; 1094 lock.lock(); 1095 newRoot.next = _root; 1096 _root = newRoot; 1097 incNodes(); 1098 lock.unlock(); 1099 return true; 1100 } 1101 static if (hasMember!(ParentAllocator, "deallocate")) 1102 return parent.deallocate(b); 1103 else 1104 return false; 1105 } 1106 1107 /// Ditto 1108 bool deallocateAll() shared 1109 { 1110 bool result = false; 1111 lock.lock(); 1112 scope(exit) lock.unlock(); 1113 static if (hasMember!(ParentAllocator, "deallocateAll")) 1114 { 1115 result = parent.deallocateAll(); 1116 } 1117 else static if (hasMember!(ParentAllocator, "deallocate")) 1118 { 1119 result = true; 1120 for (auto n = _root; n;) 1121 { 1122 auto tmp = n.next; 1123 if (!parent.deallocate((cast(ubyte*) n)[0 .. max])) 1124 result = false; 1125 n = tmp; 1126 } 1127 } 1128 _root = null; 1129 resetNodes(); 1130 return result; 1131 } 1132 1133 /** 1134 Nonstandard function that minimizes the memory usage of the freelist by 1135 freeing each element in turn. Defined only if `ParentAllocator` defines 1136 `deallocate`. 1137 */ 1138 static if (hasMember!(ParentAllocator, "deallocate") && !unchecked) 1139 void minimize() shared 1140 { 1141 lock.lock(); 1142 scope(exit) lock.unlock(); 1143 1144 for (auto n = _root; n;) 1145 { 1146 auto tmp = n.next; 1147 parent.deallocate((cast(ubyte*) n)[0 .. max]); 1148 n = tmp; 1149 } 1150 1151 _root = null; 1152 resetNodes(); 1153 } 1154 } 1155 1156 /// 1157 @safe unittest 1158 { 1159 import std.experimental.allocator.common : chooseAtRuntime; 1160 import std.experimental.allocator.mallocator : Mallocator; 1161 1162 shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; 1163 a.setBounds(64, 128); 1164 assert(a.max == 128); 1165 assert(a.min == 64); 1166 } 1167 1168 /// 1169 @safe unittest 1170 { 1171 import std.experimental.allocator.common : chooseAtRuntime; 1172 import std.experimental.allocator.mallocator : Mallocator; 1173 1174 shared SharedFreeList!(Mallocator, 50, 50, chooseAtRuntime) a; 1175 // Set the maxSize first so setting the minSize doesn't throw 1176 a.approxMaxLength = 128; 1177 assert(a.approxMaxLength == 128); 1178 a.approxMaxLength = 1024; 1179 assert(a.approxMaxLength == 1024); 1180 a.approxMaxLength = 1; 1181 assert(a.approxMaxLength == 1); 1182 } 1183 1184 @system unittest 1185 { 1186 import core.thread : ThreadGroup; 1187 import std.algorithm.comparison : equal; 1188 import std.experimental.allocator.mallocator : Mallocator; 1189 import std.range : repeat; 1190 1191 static shared SharedFreeList!(Mallocator, 64, 128, 10) a; 1192 1193 assert((() nothrow @safe @nogc => a.goodAllocSize(1))() == platformAlignment); 1194 1195 auto b = a.allocate(96); 1196 // Ensure deallocate inherits from parent 1197 () nothrow @nogc { a.deallocate(b); }(); 1198 1199 void fun() 1200 { 1201 auto b = cast(size_t[]) a.allocate(96); 1202 b[] = cast(size_t) &b; 1203 1204 assert(b.equal(repeat(cast(size_t) &b, b.length))); 1205 () nothrow @nogc { a.deallocate(b); }(); 1206 } 1207 1208 auto tg = new ThreadGroup; 1209 foreach (i; 0 .. 20) 1210 { 1211 tg.create(&fun); 1212 } 1213 1214 tg.joinAll(); 1215 } 1216 1217 @system unittest 1218 { 1219 import std.experimental.allocator.mallocator : Mallocator; 1220 static shared SharedFreeList!(Mallocator, 64, 128, 10) a; 1221 auto b = a.allocate(100); 1222 // Ensure deallocate inherits from parent 1223 () nothrow @nogc { a.deallocate(b); }(); 1224 assert(a.nodes == 1); 1225 b = []; 1226 assert((() nothrow @nogc => a.deallocateAll())()); 1227 assert(a.nodes == 0); 1228 } 1229 1230 @system unittest 1231 { 1232 import std.experimental.allocator.mallocator : Mallocator; 1233 static shared SharedFreeList!(Mallocator, 64, 128, 10) a; 1234 auto b = a.allocate(100); 1235 auto c = a.allocate(100); 1236 // Ensure deallocate inherits from parent 1237 () nothrow @nogc { a.deallocate(c); }(); 1238 assert(a.nodes == 1); 1239 c = []; 1240 a.minimize(); 1241 assert(a.nodes == 0); 1242 () nothrow @nogc { a.deallocate(b); }(); 1243 assert(a.nodes == 1); 1244 b = []; 1245 a.minimize(); 1246 assert(a.nodes == 0); 1247 } 1248 1249 @system unittest 1250 { 1251 import std.experimental.allocator.mallocator : Mallocator; 1252 static shared SharedFreeList!(Mallocator, 64, 128, 10) a; 1253 auto b = a.allocate(100); 1254 auto c = a.allocate(100); 1255 assert(a.nodes == 0); 1256 // Ensure deallocate inherits from parent 1257 () nothrow @nogc { a.deallocate(b); }(); 1258 () nothrow @nogc { a.deallocate(c); }(); 1259 assert(a.nodes == 2); 1260 b = []; 1261 c = []; 1262 a.minimize(); 1263 assert(a.nodes == 0); 1264 } 1265 1266 @system unittest 1267 { 1268 import std.experimental.allocator.mallocator : Mallocator; 1269 shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a; 1270 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1271 auto c = a.allocate(64); 1272 assert((() nothrow @nogc => a.reallocate(c, 96))()); 1273 assert(c.length == 96); 1274 // Ensure deallocate inherits from parent 1275 () nothrow @nogc { a.deallocate(c); }(); 1276 } 1277 1278 @system unittest 1279 { 1280 import std.experimental.allocator.mallocator : Mallocator; 1281 shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime, chooseAtRuntime) a; 1282 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1283 a.allocate(64); 1284 } 1285 1286 @system unittest 1287 { 1288 import std.experimental.allocator.mallocator : Mallocator; 1289 shared SharedFreeList!(Mallocator, 30, 40) a; 1290 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1291 a.allocate(64); 1292 } 1293 1294 @system unittest 1295 { 1296 import std.experimental.allocator.mallocator : Mallocator; 1297 shared SharedFreeList!(Mallocator, 30, 40, chooseAtRuntime) a; 1298 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1299 a.allocate(64); 1300 } 1301 1302 @system unittest 1303 { 1304 // Pull request #5556 1305 import std.experimental.allocator.mallocator : Mallocator; 1306 shared SharedFreeList!(Mallocator, 0, chooseAtRuntime) a; 1307 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1308 a.max = 64; 1309 a.allocate(64); 1310 } 1311 1312 @system unittest 1313 { 1314 // Pull request #5556 1315 import std.experimental.allocator.mallocator : Mallocator; 1316 shared SharedFreeList!(Mallocator, chooseAtRuntime, 64) a; 1317 scope(exit) assert((() nothrow @nogc => a.deallocateAll())()); 1318 a.min = 32; 1319 a.allocate(64); 1320 }