1 /** 2 * Unrolled Linked List. 3 * Copyright: © 2014 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.unrolledlist; 9 10 /** 11 * Unrolled Linked List. 12 * 13 * Nodes are (by default) sized to fit within a 64-byte cache line. The number 14 * of items stored per node can be read from the $(B nodeCapacity) field. 15 * See_also: $(LINK http://en.wikipedia.org/wiki/Unrolled_linked_list) 16 * Params: 17 * T = the element type 18 * supportGC = true to ensure that the GC scans the nodes of the unrolled 19 * list, false if you are sure that no references to GC-managed memory 20 * will be stored in this container. 21 * cacheLineSize = Nodes will be sized to fit within this number of bytes. 22 */ 23 struct UnrolledList(T, bool supportGC = true, size_t cacheLineSize = 64) 24 { 25 this(this) @disable; 26 27 ~this() 28 { 29 Node* prev = null; 30 Node* cur = _front; 31 while (cur !is null) 32 { 33 prev = cur; 34 cur = cur.next; 35 static if (!is(T == class)) 36 foreach (ref item; cur.items) 37 typeid(T).destroy(&item); 38 deallocateNode(prev); 39 } 40 } 41 42 /** 43 * Inserts the given item into the end of the list. 44 */ 45 void insertBack(T item) 46 { 47 if (_back is null) 48 { 49 _back = allocateNode(item); 50 _front = _back; 51 } 52 else 53 { 54 size_t index = _back.nextAvailableIndex(); 55 if (index >= nodeCapacity) 56 { 57 Node* n = allocateNode(item); 58 n.prev = _back; 59 _back.next = n; 60 _back = n; 61 index = 0; 62 } 63 else 64 { 65 _back.items[index] = item; 66 _back.markUsed(index); 67 } 68 } 69 _length++; 70 assert (_back.registry <= fullBits!nodeCapacity); 71 } 72 73 /** 74 * Inserts the given range into the end of the list 75 */ 76 void insertBack(R)(R range) 77 { 78 foreach (ref r; range) 79 insertBack(r); 80 } 81 82 /// ditto 83 alias put = insertBack; 84 /// ditto 85 alias insert = insertBack; 86 87 /** 88 * Inserts the given item in the frontmost available cell, which may put the 89 * item anywhere in the list as removal may leave gaps in list nodes. Use 90 * this only if the order of elements is not important. 91 */ 92 void insertAnywhere(T item) 93 { 94 Node* n = _front; 95 while (_front !is null) 96 { 97 size_t i = n.nextAvailableIndex(); 98 if (i >= nodeCapacity) 99 { 100 if (n.next is null) 101 break; 102 n = n.next; 103 continue; 104 } 105 n.items[i] = item; 106 n.markUsed(i); 107 _length++; 108 assert (n.registry <= fullBits!nodeCapacity); 109 return; 110 } 111 assert (n is _back); 112 n = allocateNode(item); 113 _back.next = n; 114 _back = n; 115 _length++; 116 assert (_back.registry <= fullBits!nodeCapacity); 117 } 118 119 /// Returns: the length of the list 120 size_t length() const nothrow pure @property @safe @nogc 121 { 122 return _length; 123 } 124 125 /// Returns: true if the list is empty 126 bool empty() const nothrow pure @property @safe @nogc 127 { 128 return _length == 0; 129 } 130 131 /** 132 * Removes the given item from the list. 133 * Returns: true if something was removed. 134 */ 135 bool remove(T item) 136 { 137 import core.bitop : popcnt; 138 if (_front is null) 139 return false; 140 Node* n = _front; 141 bool r = false; 142 loop: while (n !is null) 143 { 144 foreach (i; 0 .. nodeCapacity) 145 { 146 if (n.items[i] == item) 147 { 148 n.markUnused(i); 149 --_length; 150 r = true; 151 if (n.next !is null 152 && (popcnt(n.next.registry) + popcnt(n.registry) <= nodeCapacity)) 153 { 154 mergeNodes(n, n.next); 155 } 156 break loop; 157 } 158 } 159 n = n.next; 160 } 161 if (_front.registry == 0) 162 { 163 deallocateNode(_front); 164 _front = null; 165 _back = null; 166 } 167 return r; 168 } 169 170 /// Pops the front item off of the list 171 void popFront() 172 { 173 moveFront(); 174 assert (_front is null || _front.registry != 0); 175 } 176 177 /// Pops the front item off of the list and returns it 178 T moveFront() 179 in 180 { 181 assert (!empty()); 182 assert (_front.registry != 0); 183 } 184 body 185 { 186 import core.bitop : bsf, popcnt; 187 assert (_front.registry != 0); 188 size_t index = bsf(_front.registry); 189 T r = _front.items[index]; 190 _front.markUnused(index); 191 _length--; 192 if (_front.registry == 0) 193 { 194 auto f = _front; 195 if (_front.next !is null) 196 _front.next.prev = null; 197 assert (_front.next !is _front); 198 _front = _front.next; 199 if (_front is null) 200 _back = null; 201 else 202 assert (_front.registry <= fullBits!nodeCapacity); 203 deallocateNode(f); 204 return r; 205 } 206 if (_front.next !is null 207 && (popcnt(_front.next.registry) + popcnt(_front.registry) <= nodeCapacity)) 208 { 209 mergeNodes(_front, _front.next); 210 } 211 return r; 212 } 213 214 debug(EMSI_CONTAINERS) invariant() 215 { 216 import std.string: format; 217 assert (_front is null || _front.registry != 0, format("%x, %b", _front, _front.registry)); 218 assert (_front !is null || _back is null); 219 } 220 221 /** 222 * Time complexity is O(1) 223 * Returns: the item at the front of the list 224 */ 225 inout(T) front() inout @property 226 in 227 { 228 assert (!empty); 229 assert(_front.registry != 0); 230 } 231 body 232 { 233 import core.bitop: bsf; 234 import std.string: format; 235 assert (_front.registry != 0); 236 size_t index = bsf(_front.registry); 237 assert (index < nodeCapacity, format("%d", index)); 238 return _front.items[index]; 239 } 240 241 /** 242 * Time complexity is O(n) 243 * Returns: the item at the back of the list 244 */ 245 inout(T) back() inout @property 246 in 247 { 248 assert (!empty); 249 assert (!_back.empty); 250 } 251 body 252 { 253 size_t i = nodeCapacity - 1; 254 while (_back.isFree(i)) 255 i--; 256 return _back.items[i]; 257 } 258 259 /// Pops the back item off of the list. 260 void popBack() 261 { 262 moveBack(); 263 } 264 265 /// Removes an item from the back of the list and returns it. 266 T moveBack() 267 in 268 { 269 assert (!empty); 270 assert (!_back.empty); 271 } 272 body 273 { 274 import core.bitop : popcnt; 275 size_t i = nodeCapacity - 1; 276 while (_back.isFree(i)) 277 { 278 if (i == 0) 279 break; 280 else 281 i--; 282 } 283 assert (!_back.isFree(i)); 284 T item = _back.items[i]; 285 _back.markUnused(i); 286 _length--; 287 if (_back.registry == 0) 288 { 289 auto b = _back; 290 if (_back.prev !is null) 291 _back.prev.next = null; 292 else 293 _front = null; 294 _back = _back.prev; 295 deallocateNode(b); 296 return item; 297 } 298 if (_back.prev !is null 299 && (popcnt(_back.prev.registry) + popcnt(_back.registry) <= nodeCapacity)) 300 { 301 mergeNodes(_back.prev, _back); 302 } 303 return item; 304 } 305 306 /** 307 * Number of items stored per node. 308 */ 309 enum size_t nodeCapacity = fatNodeCapacity!(T.sizeof, 2, ushort, cacheLineSize); 310 311 /// Returns: a range over the list 312 Range range() const nothrow pure 313 { 314 return Range(_front); 315 } 316 317 /// ditto 318 alias opSlice = range; 319 320 private static struct Range 321 { 322 @disable this(); 323 324 this(inout(Node)* current) 325 { 326 import core.bitop: bsf; 327 this.current = current; 328 if (current !is null) 329 { 330 assert(current.registry != 0); 331 index = bsf(current.registry); 332 assert (index < nodeCapacity); 333 } 334 } 335 336 T front() const @property @trusted @nogc 337 { 338 return cast(T) current.items[index]; 339 } 340 341 void popFront() nothrow pure 342 { 343 index++; 344 while (true) 345 { 346 if (current is null) 347 return; 348 if (index >= nodeCapacity) 349 { 350 current = current.next; 351 index = 0; 352 } 353 else 354 { 355 if (current.isFree(index)) 356 index++; 357 else 358 return; 359 } 360 } 361 } 362 363 bool empty() const nothrow pure @property @safe @nogc 364 { 365 return current is null; 366 } 367 368 Range save() const nothrow pure @property @safe @nogc 369 { 370 return this; 371 } 372 373 private: 374 375 const(Node)* current; 376 size_t index; 377 } 378 379 private: 380 381 import std.allocator: allocate, deallocate, Mallocator; 382 import containers.internal.node : fatNodeCapacity, shouldAddGCRange, 383 fullBits, shouldNullSlot; 384 import containers.internal.storage_type : ContainerStorageType; 385 386 Node* _back; 387 Node* _front; 388 size_t _length; 389 390 Node* allocateNode(T item) 391 { 392 Node* n = allocate!Node(Mallocator.it); 393 static if (supportGC && shouldAddGCRange!T) 394 { 395 import core.memory: GC; 396 GC.addRange(n, Node.sizeof); 397 } 398 n.items[0] = item; 399 n.markUsed(0); 400 return n; 401 } 402 403 void deallocateNode(Node* n) 404 { 405 deallocate(Mallocator.it, n); 406 static if (supportGC && shouldAddGCRange!T) 407 { 408 import core.memory: GC; 409 GC.removeRange(n); 410 } 411 } 412 413 void mergeNodes(Node* first, Node* second) 414 in 415 { 416 assert (first !is null); 417 assert (second is first.next); 418 } 419 body 420 { 421 import core.bitop: bsf; 422 size_t i; 423 ContainerStorageType!T[nodeCapacity] temp; 424 foreach (j; 0 .. nodeCapacity) 425 if (!first.isFree(j)) 426 temp[i++] = first.items[j]; 427 foreach (j; 0 .. nodeCapacity) 428 if (!second.isFree(j)) 429 temp[i++] = second.items[j]; 430 first.next = second.next; 431 first.items[0 .. i] = temp[0 .. i]; 432 first.registry = 0; 433 foreach (k; 0 .. i) 434 first.markUsed(k); 435 assert (first.registry <= fullBits!nodeCapacity); 436 static if (supportGC && shouldAddGCRange!T) 437 { 438 import core.memory : GC; 439 GC.removeRange(second); 440 } 441 if (_back is second) 442 _back = first; 443 deallocate(Mallocator.it, second); 444 } 445 446 static struct Node 447 { 448 size_t nextAvailableIndex() const nothrow pure @safe @nogc 449 { 450 import core.bitop: bsf; 451 assert((~registry) != 0); 452 return bsf(~registry); 453 } 454 455 void markUsed(size_t index) nothrow pure @safe @nogc 456 { 457 registry |= (1 << index); 458 } 459 460 void markUnused(size_t index) nothrow pure @safe @nogc 461 { 462 registry &= ~(1 << index); 463 static if (shouldNullSlot!T) 464 items[index] = null; 465 } 466 467 bool empty() const nothrow pure @safe @nogc 468 { 469 return registry == 0; 470 } 471 472 bool isFree(size_t index) const nothrow pure @safe @nogc 473 { 474 return (registry & (1 << index)) == 0; 475 } 476 477 debug(EMSI_CONTAINERS) invariant() 478 { 479 import std.string : format; 480 assert (registry <= fullBits!nodeCapacity, format("%016b %016b", registry, fullBits!nodeCapacity)); 481 assert (prev !is &this); 482 assert (next !is &this); 483 } 484 485 ushort registry; 486 ContainerStorageType!T[nodeCapacity] items; 487 Node* prev; 488 Node* next; 489 } 490 } 491 492 unittest 493 { 494 import std.algorithm : equal; 495 import std.range : iota; 496 import std.string : format; 497 UnrolledList!int l; 498 static assert (l.Node.sizeof <= 64); 499 assert (l.empty); 500 l.insert(0); 501 assert (l.length == 1); 502 assert (!l.empty); 503 foreach (i; 1 .. 100) 504 l.insert(i); 505 assert (l.length == 100); 506 assert (equal(l[], iota(100))); 507 foreach (i; 0 .. 100) 508 assert (l.remove(i), format("%d", i)); 509 assert (l.length == 0, format("%d", l.length)); 510 assert (l.empty); 511 UnrolledList!int l2; 512 l2.insert(1); 513 l2.insert(2); 514 l2.insert(3); 515 assert (l2.front == 1); 516 l2.popFront(); 517 assert (l2.front == 2); 518 assert (equal(l2[], [2, 3])); 519 l2.popFront(); 520 assert (equal(l2[], [3])); 521 l2.popFront(); 522 assert (l2.empty, format("%d", l2.front)); 523 assert (equal(l2[], cast(int[]) [])); 524 UnrolledList!int l3; 525 foreach (i; 0 .. 200) 526 l3.insert(i); 527 foreach (i; 0 .. 200) 528 { 529 auto x = l3.moveFront(); 530 assert (x == i, format("%d %d", i, x)); 531 } 532 assert (l3.empty); 533 foreach (i; 0 .. 200) 534 l3.insert(i); 535 assert (l3.length == 200); 536 foreach (i; 0 .. 200) 537 { 538 assert (l3.length == 200 - i); 539 auto x = l3.moveBack(); 540 assert (x == 200 - i - 1, format("%d %d", 200 - 1 - 1, x)); 541 } 542 assert (l3.empty); 543 } 544 545 unittest 546 { 547 struct A { int a; int b; } 548 UnrolledList!(const(A)) objs; 549 objs.insert(A(10, 11)); 550 static assert (is (typeof(objs.front) == const)); 551 static assert (is (typeof(objs[].front) == const)); 552 }