1 // Written in the D programming language. 2 /** 3 Allocator that collects useful statistics about allocations, both global and per 4 calling point. The statistics collected can be configured statically by choosing 5 combinations of `Options` appropriately. 6 7 Example: 8 ---- 9 import std.experimental.allocator.gc_allocator : GCAllocator; 10 import std.experimental.allocator.building_blocks.free_list : FreeList; 11 alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed); 12 ---- 13 */ 14 module std.experimental.allocator.building_blocks.stats_collector; 15 16 import std.experimental.allocator.common; 17 18 /** 19 _Options for $(D StatsCollector) defined below. Each enables during 20 compilation one specific counter, statistic, or other piece of information. 21 */ 22 enum Options : ulong 23 { 24 /** 25 Counts the number of calls to $(D owns). 26 */ 27 numOwns = 1u << 0, 28 /** 29 Counts the number of calls to $(D allocate). All calls are counted, 30 including requests for zero bytes or failed requests. 31 */ 32 numAllocate = 1u << 1, 33 /** 34 Counts the number of calls to $(D allocate) that succeeded, i.e. they 35 returned a block as large as requested. (N.B. requests for zero bytes count 36 as successful.) 37 */ 38 numAllocateOK = 1u << 2, 39 /** 40 Counts the number of calls to $(D expand), regardless of arguments or 41 result. 42 */ 43 numExpand = 1u << 3, 44 /** 45 Counts the number of calls to $(D expand) that resulted in a successful 46 expansion. 47 */ 48 numExpandOK = 1u << 4, 49 /** 50 Counts the number of calls to $(D reallocate), regardless of arguments or 51 result. 52 */ 53 numReallocate = 1u << 5, 54 /** 55 Counts the number of calls to $(D reallocate) that succeeded. 56 (Reallocations to zero bytes count as successful.) 57 */ 58 numReallocateOK = 1u << 6, 59 /** 60 Counts the number of calls to $(D reallocate) that resulted in an in-place 61 reallocation (no memory moved). If this number is close to the total number 62 of reallocations, that indicates the allocator finds room at the current 63 block's end in a large fraction of the cases, but also that internal 64 fragmentation may be high (the size of the unit of allocation is large 65 compared to the typical allocation size of the application). 66 */ 67 numReallocateInPlace = 1u << 7, 68 /** 69 Counts the number of calls to $(D deallocate). 70 */ 71 numDeallocate = 1u << 8, 72 /** 73 Counts the number of calls to $(D deallocateAll). 74 */ 75 numDeallocateAll = 1u << 9, 76 /** 77 Chooses all $(D numXxx) flags. 78 */ 79 numAll = (1u << 10) - 1, 80 /** 81 Tracks bytes currently allocated by this allocator. This number goes up 82 and down as memory is allocated and deallocated, and is zero if the 83 allocator currently has no active allocation. 84 */ 85 bytesUsed = 1u << 10, 86 /** 87 Tracks total cumulative bytes allocated by means of $(D allocate), 88 $(D expand), and $(D reallocate) (when resulting in an expansion). This 89 number always grows and indicates allocation traffic. To compute bytes 90 deallocated cumulatively, subtract $(D bytesUsed) from $(D bytesAllocated). 91 */ 92 bytesAllocated = 1u << 11, 93 /** 94 Tracks the sum of all $(D delta) values in calls of the form 95 $(D expand(b, delta)) that succeed (return $(D true)). 96 */ 97 bytesExpanded = 1u << 12, 98 /** 99 Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of 100 the form $(D realloc(b, s)) that succeed (return $(D true)). In per-call 101 statistics, also unambiguously counts the bytes deallocated with 102 $(D deallocate). 103 */ 104 bytesContracted = 1u << 13, 105 /** 106 Tracks the sum of all bytes moved as a result of calls to $(D realloc) that 107 were unable to reallocate in place. A large number (relative to $(D 108 bytesAllocated)) indicates that the application should use larger 109 preallocations. 110 */ 111 bytesMoved = 1u << 14, 112 /** 113 Tracks the sum of all bytes NOT moved as result of calls to $(D realloc) 114 that managed to reallocate in place. A large number (relative to $(D 115 bytesAllocated)) indicates that the application is expansion-intensive and 116 is saving a good amount of moves. However, if this number is relatively 117 small and $(D bytesSlack) is high, it means the application is 118 overallocating for little benefit. 119 */ 120 bytesNotMoved = 1u << 15, 121 /** 122 Measures the sum of extra bytes allocated beyond the bytes requested, i.e. 123 the $(HTTP goo.gl/YoKffF, internal fragmentation). This is the current 124 effective number of slack bytes, and it goes up and down with time. 125 */ 126 bytesSlack = 1u << 16, 127 /** 128 Measures the maximum bytes allocated over the time. This is useful for 129 dimensioning allocators. 130 */ 131 bytesHighTide = 1u << 17, 132 /** 133 Chooses all $(D byteXxx) flags. 134 */ 135 bytesAll = ((1u << 18) - 1) & ~numAll, 136 /** 137 Combines all flags above. 138 */ 139 all = (1u << 18) - 1 140 } 141 142 /** 143 144 Allocator that collects extra data about allocations. Since each piece of 145 information adds size and time overhead, statistics can be individually enabled 146 or disabled through compile-time $(D flags). 147 148 All stats of the form $(D numXxx) record counts of events occurring, such as 149 calls to functions and specific results. The stats of the form $(D bytesXxx) 150 collect cumulative sizes. 151 152 In addition, the data $(D callerSize), $(D callerModule), $(D callerFile), $(D 153 callerLine), and $(D callerTime) is associated with each specific allocation. 154 This data prefixes each allocation. 155 156 */ 157 struct StatsCollector(Allocator, ulong flags = Options.all, 158 ulong perCallFlags = 0) 159 { 160 private: 161 import std.traits : hasMember, Signed; 162 import std.typecons : Ternary; 163 define(string type,string[]names...)164 static string define(string type, string[] names...) 165 { 166 string result; 167 foreach (v; names) 168 result ~= "static if (flags & Options."~v~") {" 169 ~ "private "~type~" _"~v~";" 170 ~ "public const("~type~") "~v~"() const { return _"~v~"; }" 171 ~ "}"; 172 return result; 173 } 174 add(string counter)175 void add(string counter)(Signed!size_t n) 176 { 177 mixin("static if (flags & Options." ~ counter 178 ~ ") _" ~ counter ~ " += n;"); 179 static if (counter == "bytesUsed" && (flags & Options.bytesHighTide)) 180 { 181 if (bytesHighTide < bytesUsed ) _bytesHighTide = bytesUsed; 182 } 183 } 184 up(string counter)185 void up(string counter)() { add!counter(1); } down(string counter)186 void down(string counter)() { add!counter(-1); } 187 version(StdDdoc)188 version (StdDdoc) 189 { 190 /** 191 Read-only properties enabled by the homonym $(D flags) chosen by the 192 user. 193 194 Example: 195 ---- 196 StatsCollector!(Mallocator, 197 Options.bytesUsed | Options.bytesAllocated) a; 198 auto d1 = a.allocate(10); 199 auto d2 = a.allocate(11); 200 a.deallocate(d1); 201 assert(a.bytesAllocated == 21); 202 assert(a.bytesUsed == 11); 203 a.deallocate(d2); 204 assert(a.bytesAllocated == 21); 205 assert(a.bytesUsed == 0); 206 ---- 207 */ 208 @property ulong numOwns() const; 209 /// Ditto 210 @property ulong numAllocate() const; 211 /// Ditto 212 @property ulong numAllocateOK() const; 213 /// Ditto 214 @property ulong numExpand() const; 215 /// Ditto 216 @property ulong numExpandOK() const; 217 /// Ditto 218 @property ulong numReallocate() const; 219 /// Ditto 220 @property ulong numReallocateOK() const; 221 /// Ditto 222 @property ulong numReallocateInPlace() const; 223 /// Ditto 224 @property ulong numDeallocate() const; 225 /// Ditto 226 @property ulong numDeallocateAll() const; 227 /// Ditto 228 @property ulong bytesUsed() const; 229 /// Ditto 230 @property ulong bytesAllocated() const; 231 /// Ditto 232 @property ulong bytesExpanded() const; 233 /// Ditto 234 @property ulong bytesContracted() const; 235 /// Ditto 236 @property ulong bytesMoved() const; 237 /// Ditto 238 @property ulong bytesNotMoved() const; 239 /// Ditto 240 @property ulong bytesSlack() const; 241 /// Ditto 242 @property ulong bytesHighTide() const; 243 } 244 245 public: 246 /** 247 The parent allocator is publicly accessible either as a direct member if it 248 holds state, or as an alias to `Allocator.instance` otherwise. One may use 249 it for making calls that won't count toward statistics collection. 250 */ 251 static if (stateSize!Allocator) Allocator parent; 252 else alias parent = Allocator.instance; 253 254 private: 255 // Per-allocator state 256 mixin(define("ulong", 257 "numOwns", 258 "numAllocate", 259 "numAllocateOK", 260 "numExpand", 261 "numExpandOK", 262 "numReallocate", 263 "numReallocateOK", 264 "numReallocateInPlace", 265 "numDeallocate", 266 "numDeallocateAll", 267 "bytesUsed", 268 "bytesAllocated", 269 "bytesExpanded", 270 "bytesContracted", 271 "bytesMoved", 272 "bytesNotMoved", 273 "bytesSlack", 274 "bytesHighTide", 275 )); 276 277 public: 278 279 /// Alignment offered is equal to $(D Allocator.alignment). 280 alias alignment = Allocator.alignment; 281 282 /** 283 Increments $(D numOwns) (per instance and and per call) and forwards to $(D 284 parent.owns(b)). 285 */ 286 static if (hasMember!(Allocator, "owns")) 287 { 288 static if ((perCallFlags & Options.numOwns) == 0) owns(void[]b)289 Ternary owns(void[] b) 290 { return ownsImpl(b); } 291 else 292 Ternary owns(string f = __FILE, uint n = line)(void[] b) 293 { return ownsImpl!(f, n)(b); } 294 } 295 296 private Ternary ownsImpl(string f = null, uint n = 0)(void[] b) 297 { 298 up!"numOwns"; 299 addPerCall!(f, n, "numOwns")(1); 300 return parent.owns(b); 301 } 302 303 /** 304 Forwards to $(D parent.allocate). Affects per instance: $(D numAllocate), 305 $(D bytesUsed), $(D bytesAllocated), $(D bytesSlack), $(D numAllocateOK), 306 and $(D bytesHighTide). Affects per call: $(D numAllocate), $(D 307 numAllocateOK), and $(D bytesAllocated). 308 */ 309 static if (!(perCallFlags 310 & (Options.numAllocate | Options.numAllocateOK 311 | Options.bytesAllocated))) 312 { allocate(size_t n)313 void[] allocate(size_t n) 314 { return allocateImpl(n); } 315 } 316 else 317 { 318 void[] allocate(string f = __FILE__, ulong n = __LINE__) 319 (size_t bytes) 320 { return allocateImpl!(f, n)(bytes); } 321 } 322 323 private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes) 324 { 325 auto result = parent.allocate(bytes); 326 add!"bytesUsed"(result.length); 327 add!"bytesAllocated"(result.length); 328 immutable slack = this.goodAllocSize(result.length) - result.length; 329 add!"bytesSlack"(slack); 330 up!"numAllocate"; 331 add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK 332 addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated") 333 (1, result.length == bytes, result.length); 334 return result; 335 } 336 337 /** 338 Defined whether or not $(D Allocator.expand) is defined. Affects 339 per instance: $(D numExpand), $(D numExpandOK), $(D bytesExpanded), 340 $(D bytesSlack), $(D bytesAllocated), and $(D bytesUsed). Affects per call: 341 $(D numExpand), $(D numExpandOK), $(D bytesExpanded), and 342 $(D bytesAllocated). 343 */ 344 static if (!(perCallFlags 345 & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded))) 346 { expand(ref void[]b,size_t delta)347 bool expand(ref void[] b, size_t delta) 348 { return expandImpl(b, delta); } 349 } 350 else 351 { 352 bool expand(string f = __FILE__, uint n = __LINE__) 353 (ref void[] b, size_t delta) 354 { return expandImpl!(f, n)(b, delta); } 355 } 356 357 private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s) 358 { 359 up!"numExpand"; 360 Signed!size_t slack = 0; 361 static if (!hasMember!(Allocator, "expand")) 362 { 363 auto result = s == 0; 364 } 365 else 366 { 367 immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length; 368 auto result = parent.expand(b, s); 369 if (result) 370 { 371 up!"numExpandOK"; 372 add!"bytesUsed"(s); 373 add!"bytesAllocated"(s); 374 add!"bytesExpanded"(s); 375 slack = Signed!size_t(this.goodAllocSize(b.length) - b.length 376 - bytesSlackB4); 377 add!"bytesSlack"(slack); 378 } 379 } 380 immutable xtra = result ? s : 0; 381 addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded", 382 "bytesAllocated") 383 (1, result, xtra, xtra); 384 return result; 385 } 386 387 /** 388 Defined whether or not $(D Allocator.reallocate) is defined. Affects 389 per instance: $(D numReallocate), $(D numReallocateOK), $(D 390 numReallocateInPlace), $(D bytesNotMoved), $(D bytesAllocated), $(D 391 bytesSlack), $(D bytesExpanded), and $(D bytesContracted). Affects per call: 392 $(D numReallocate), $(D numReallocateOK), $(D numReallocateInPlace), 393 $(D bytesNotMoved), $(D bytesExpanded), $(D bytesContracted), and 394 $(D bytesMoved). 395 */ 396 static if (!(perCallFlags 397 & (Options.numReallocate | Options.numReallocateOK 398 | Options.numReallocateInPlace | Options.bytesNotMoved 399 | Options.bytesExpanded | Options.bytesContracted 400 | Options.bytesMoved))) 401 { reallocate(ref void[]b,size_t s)402 bool reallocate(ref void[] b, size_t s) 403 { return reallocateImpl(b, s); } 404 } 405 else 406 { 407 bool reallocate(string f = __FILE__, ulong n = __LINE__) 408 (ref void[] b, size_t s) 409 { return reallocateImpl!(f, n)(b, s); } 410 } 411 412 private bool reallocateImpl(string f = null, uint n = 0) 413 (ref void[] b, size_t s) 414 { 415 up!"numReallocate"; 416 const bytesSlackB4 = this.goodAllocSize(b.length) - b.length; 417 const oldB = b.ptr; 418 const oldLength = b.length; 419 420 const result = parent.reallocate(b, s); 421 422 Signed!size_t slack = 0; 423 bool wasInPlace = false; 424 Signed!size_t delta = 0; 425 426 if (result) 427 { 428 up!"numReallocateOK"; 429 slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4; 430 add!"bytesSlack"(slack); 431 add!"bytesUsed"(Signed!size_t(b.length - oldLength)); 432 if (oldB == b.ptr) 433 { 434 // This was an in-place reallocation, yay 435 wasInPlace = true; 436 up!"numReallocateInPlace"; 437 add!"bytesNotMoved"(oldLength); 438 delta = b.length - oldLength; 439 if (delta >= 0) 440 { 441 // Expansion 442 add!"bytesAllocated"(delta); 443 add!"bytesExpanded"(delta); 444 } 445 else 446 { 447 // Contraction 448 add!"bytesContracted"(-delta); 449 } 450 } 451 else 452 { 453 // This was a allocate-move-deallocate cycle 454 add!"bytesAllocated"(b.length); 455 add!"bytesMoved"(oldLength); 456 } 457 } 458 addPerCall!(f, n, "numReallocate", "numReallocateOK", 459 "numReallocateInPlace", "bytesNotMoved", 460 "bytesExpanded", "bytesContracted", "bytesMoved") 461 (1, result, wasInPlace, wasInPlace ? oldLength : 0, 462 delta >= 0 ? delta : 0, delta < 0 ? -delta : 0, 463 wasInPlace ? 0 : oldLength); 464 return result; 465 } 466 467 /** 468 Defined whether or not $(D Allocator.deallocate) is defined. Affects 469 per instance: $(D numDeallocate), $(D bytesUsed), and $(D bytesSlack). 470 Affects per call: $(D numDeallocate) and $(D bytesContracted). 471 */ 472 static if (!(perCallFlags & 473 (Options.numDeallocate | Options.bytesContracted))) deallocate(void[]b)474 bool deallocate(void[] b) 475 { return deallocateImpl(b); } 476 else 477 bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b) 478 { return deallocateImpl!(f, n)(b); } 479 480 private bool deallocateImpl(string f = null, uint n = 0)(void[] b) 481 { 482 up!"numDeallocate"; 483 add!"bytesUsed"(-Signed!size_t(b.length)); 484 add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length)); 485 addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length); 486 static if (hasMember!(Allocator, "deallocate")) 487 return parent.deallocate(b); 488 else 489 return false; 490 } 491 492 static if (hasMember!(Allocator, "deallocateAll")) 493 { 494 /** 495 Defined only if $(D Allocator.deallocateAll) is defined. Affects 496 per instance and per call $(D numDeallocateAll). 497 */ 498 static if (!(perCallFlags & Options.numDeallocateAll)) deallocateAll()499 bool deallocateAll() 500 { return deallocateAllImpl(); } 501 else 502 bool deallocateAll(string f = __FILE__, uint n = __LINE__)() 503 { return deallocateAllImpl!(f, n)(); } 504 505 private bool deallocateAllImpl(string f = null, uint n = 0)() 506 { 507 up!"numDeallocateAll"; 508 addPerCall!(f, n, "numDeallocateAll")(1); 509 static if ((flags & Options.bytesUsed)) 510 _bytesUsed = 0; 511 return parent.deallocateAll(); 512 } 513 } 514 515 /** 516 Defined only if $(D Options.bytesUsed) is defined. Returns $(D bytesUsed == 517 0). 518 */ 519 static if (flags & Options.bytesUsed) empty()520 Ternary empty() 521 { 522 return Ternary(_bytesUsed == 0); 523 } 524 525 /** 526 Reports per instance statistics to $(D output) (e.g. $(D stdout)). The 527 format is simple: one kind and value per line, separated by a colon, e.g. 528 $(D bytesAllocated:7395404) 529 */ reportStatistics(R)530 void reportStatistics(R)(auto ref R output) 531 { 532 import std.conv : to; 533 import std.traits : EnumMembers; 534 foreach (e; EnumMembers!Options) 535 { 536 static if ((flags & e) && e != Options.numAll 537 && e != Options.bytesAll && e != Options.all) 538 output.write(e.to!string, ":", mixin(e.to!string), '\n'); 539 } 540 } 541 542 static if (perCallFlags) 543 { 544 /** 545 Defined if $(D perCallFlags) is nonzero. 546 */ 547 struct PerCallStatistics 548 { 549 /// The file and line of the call. 550 string file; 551 /// Ditto 552 uint line; 553 /// The options corresponding to the statistics collected. 554 Options[] opts; 555 /// The values of the statistics. Has the same length as $(D opts). 556 ulong[] values; 557 // Next in the chain. 558 private PerCallStatistics* next; 559 560 /** 561 Format to a string such as: 562 $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]). 563 */ toStringPerCallStatistics564 string toString() const 565 { 566 import std.conv : text, to; 567 auto result = text(file, "(", line, "): ["); 568 foreach (i, opt; opts) 569 { 570 if (i) result ~= ", "; 571 result ~= opt.to!string; 572 result ~= ':'; 573 result ~= values[i].to!string; 574 } 575 return result ~= "]"; 576 } 577 } 578 private static PerCallStatistics* root; 579 580 /** 581 Defined if $(D perCallFlags) is nonzero. Iterates all monitored 582 file/line instances. The order of iteration is not meaningful (items 583 are inserted at the front of a list upon the first call), so 584 preprocessing the statistics after collection might be appropriate. 585 */ byFileLine()586 static auto byFileLine() 587 { 588 static struct Voldemort 589 { 590 PerCallStatistics* current; 591 bool empty() { return !current; } 592 ref PerCallStatistics front() { return *current; } 593 void popFront() { current = current.next; } 594 auto save() { return this; } 595 } 596 return Voldemort(root); 597 } 598 599 /** 600 Defined if $(D perCallFlags) is nonzero. Outputs (e.g. to a $(D File)) 601 a simple report of the collected per-call statistics. 602 */ reportPerCallStatistics(R)603 static void reportPerCallStatistics(R)(auto ref R output) 604 { 605 output.write("Stats for: ", StatsCollector.stringof, '\n'); 606 foreach (ref stat; byFileLine) 607 { 608 output.write(stat, '\n'); 609 } 610 } 611 statsAt(string f,uint n,opts...)612 private PerCallStatistics* statsAt(string f, uint n, opts...)() 613 { 614 import std.array : array; 615 import std.range : repeat; 616 617 static PerCallStatistics s = { f, n, [ opts ], 618 repeat(0UL, opts.length).array }; 619 static bool inserted; 620 621 if (!inserted) 622 { 623 // Insert as root 624 s.next = root; 625 root = &s; 626 inserted = true; 627 } 628 return &s; 629 } 630 addPerCall(string f,uint n,names...)631 private void addPerCall(string f, uint n, names...)(ulong[] values...) 632 { 633 import std.array : join; 634 enum uint mask = mixin("Options."~[names].join("|Options.")); 635 static if (perCallFlags & mask) 636 { 637 // Per allocation info 638 auto ps = mixin("statsAt!(f, n," 639 ~ "Options."~[names].join(", Options.") 640 ~")"); 641 foreach (i; 0 .. names.length) 642 { 643 ps.values[i] += values[i]; 644 } 645 } 646 } 647 } 648 else 649 { addPerCall(string f,uint n,names...)650 private void addPerCall(string f, uint n, names...)(ulong[]...) 651 { 652 } 653 } 654 } 655 656 /// 657 @system unittest 658 { 659 import std.experimental.allocator.building_blocks.free_list : FreeList; 660 import std.experimental.allocator.gc_allocator : GCAllocator; 661 alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all); 662 663 Allocator alloc; 664 auto b = alloc.allocate(10); 665 alloc.reallocate(b, 20); 666 alloc.deallocate(b); 667 668 import std.file : deleteme, remove; 669 import std.range : walkLength; 670 import std.stdio : File; 671 672 auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt"; 673 scope(exit) remove(f); 674 Allocator.reportPerCallStatistics(File(f, "w")); 675 alloc.reportStatistics(File(f, "a")); 676 assert(File(f).byLine.walkLength == 22); 677 } 678 679 @system unittest 680 { test(Allocator)681 void test(Allocator)() 682 { 683 import std.range : walkLength; 684 import std.stdio : writeln; 685 Allocator a; 686 auto b1 = a.allocate(100); 687 assert(a.numAllocate == 1); 688 assert(a.expand(b1, 0)); 689 assert(a.reallocate(b1, b1.length + 1)); 690 auto b2 = a.allocate(101); 691 assert(a.numAllocate == 2); 692 assert(a.bytesAllocated == 202); 693 assert(a.bytesUsed == 202); 694 auto b3 = a.allocate(202); 695 assert(a.numAllocate == 3); 696 assert(a.bytesAllocated == 404); 697 698 a.deallocate(b2); 699 assert(a.numDeallocate == 1); 700 a.deallocate(b1); 701 assert(a.numDeallocate == 2); 702 a.deallocate(b3); 703 assert(a.numDeallocate == 3); 704 assert(a.numAllocate == a.numDeallocate); 705 assert(a.bytesUsed == 0); 706 } 707 708 import std.experimental.allocator.building_blocks.free_list : FreeList; 709 import std.experimental.allocator.gc_allocator : GCAllocator; 710 test!(StatsCollector!(GCAllocator, Options.all, Options.all)); 711 test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all, 712 Options.all)); 713 } 714 715 @system unittest 716 { test(Allocator)717 void test(Allocator)() 718 { 719 import std.range : walkLength; 720 import std.stdio : writeln; 721 Allocator a; 722 auto b1 = a.allocate(100); 723 assert(a.expand(b1, 0)); 724 assert(a.reallocate(b1, b1.length + 1)); 725 auto b2 = a.allocate(101); 726 auto b3 = a.allocate(202); 727 728 a.deallocate(b2); 729 a.deallocate(b1); 730 a.deallocate(b3); 731 } 732 import std.experimental.allocator.building_blocks.free_list : FreeList; 733 import std.experimental.allocator.gc_allocator : GCAllocator; 734 test!(StatsCollector!(GCAllocator, 0, 0)); 735 } 736