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