├── .gitignore ├── LICENSE_1_0.txt ├── Makefile ├── README.md ├── comparison ├── Makefile ├── README.md ├── ThroughputMpTest.java ├── dlist.d ├── dlist_test.d ├── elembuf_sync_queue.d ├── elembuf_wait_free_queue.d ├── go.d ├── mp.d ├── plain_array.d ├── plain_d_sync_queue.d └── test_queue.d ├── dub.json ├── lib └── liblfds release 7.1.1 source.tar.bz2 ├── liblfdsd.c ├── liblfdsd.h ├── queue_bmm.h ├── queue_bmm_test.c ├── queue_bss.h ├── queue_bss_test.c ├── queue_umm.h ├── queue_umm_test.c └── source ├── liblfdsd.d └── liblfdsd.dpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Compiled Static libraries 11 | *.a 12 | *.lib 13 | 14 | # Executables 15 | *.exe 16 | 17 | # DUB 18 | .dub 19 | docs.json 20 | __dummy.html 21 | docs/ 22 | 23 | # Code coverage 24 | *.lst 25 | 26 | *.class 27 | 28 | liblfds7.1.1/ 29 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | LIBLFDS = ./liblfds7.1.1 3 | 4 | DPPFLAGS = --preprocess-only --hard-fail --include-path=$(LIBLFDS)/liblfds711/inc --include-path=. --keep-d-files --compiler=ldmd2 #dmd #ldc2 # 5 | LDC2_FLAGS = -debug #-d 6 | LDC2_FLAGS = -O4 --release --boundscheck=off 7 | DMDLIB = -L$(LIBLFDS)/liblfds711/bin -L-L. -L-llfdsd -L-llfdsdc -L-llfds711 8 | 9 | build: 10 | dub build 11 | 12 | gen: 13 | make clean 14 | tar xjf lib/liblfds\ release\ 7.1.1\ source.tar.bz2 15 | # https://liblfds.org/mediawiki/index.php?title=r7.1.1:Building_Guide_(liblfds) 16 | cd liblfds7.1.1/liblfds711/build/gcc_gnumake && make ar_rel 17 | mv -f liblfds7.1.1/liblfds711/bin/liblfds711.a . 18 | sed 's/bmm/bss/g;s/BMM/BSS/g' queue_bmm.h > queue_bss.h 19 | # echo sed 's/bmm/umm/g' queue_bmm.h > queue_umm.h # one time run, then need manual edit 20 | gcc $(CFLAGS) -c liblfdsd.c 21 | ar rcs liblfdsdc.a liblfdsd.o 22 | make source/liblfdsd.d 23 | 24 | source/liblfdsd.d: source/liblfdsd.dpp 25 | dub run dpp -- $(DPPFLAGS) $< 26 | 27 | 28 | test: 29 | ldmd2 -unittest -version=LIBLFDS_TEST source/liblfdsd.d $(LDC2_FLAGS) -L$(DMDLIB) 30 | ./liblfdsd 31 | 32 | liblfdsd.d: liblfdsd.h 33 | 34 | CFLAGS = -I$(LIBLFDS)/liblfds711/inc -Ofast # -g # -std=gnu11 35 | LDFLAGS = -L$(LIBLFDS)/liblfds711/bin -llfds711 36 | 37 | c: 38 | make clean 39 | gcc $(CFLAGS) queue_bmm_test.c $(LDFLAGS) -o queue_bmm_test 40 | gcc $(CFLAGS) queue_bss_test.c $(LDFLAGS) -o queue_bss_test 41 | gcc $(CFLAGS) queue_umm_test.c $(LDFLAGS) -o queue_umm_test 42 | ./queue_bmm_test 43 | ./queue_bss_test 44 | ./queue_umm_test 45 | 46 | 47 | 48 | clean: 49 | $(RM) -fr liblfds7.1.1/ *.o *.a 50 | dub clean 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # liblfdsd 2 | liblfds for D, from the portable, license-free, lock-free data structure C library (https://www.liblfds.org/) 3 | 4 | ## Deps: 5 | 1. https://code.dlang.org/packages/dpp 6 | 2. https://www.liblfds.org/ 7 | Right now, only the following data structure are wrapped: 8 | * https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(bounded,_many_producer,_many_consumer) 9 | * https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(bounded,_single_producer,_single_consumer) 10 | * https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(unbounded,_many_producer,_many_consumer) 11 | ##### download from https://www.liblfds.org/downloads/liblfds%20release%207.1.1%20source.tar.bz2 12 | 13 | ## Test 14 | 15 | After install all the deps: 16 | 17 | ``` 18 | $ make build 19 | $ make test 20 | $ ./liblfdsd 21 | # bmm queue 22 | string: Madge The Skutter; after push & before pop 23 | string: Madge The Skutter; after push & before pop 24 | skutter name = struct: Madge The Skutter; after push & before pop 8 25 | skutter name = class: Madge The Skutter; after push & before pop 8 26 | received 100000000 messages in 4632 msec sum=4999999950000000 speed=21588 msg/msec 27 | received 100000000 messages in 4868 msec sum=4999999950000000 speed=20542 msg/msec 28 | 29 | # bss queue 30 | received 100000000 messages in 2610 msec sum=4999999950000000 speed=38314 msg/msec 31 | 32 | # queue umm 33 | received 100000000 messages in 14994 msec sum=4999999950000000 speed=6669 msg/msec 34 | ``` 35 | 36 | Please check the `comparison` directory for a simple performance comparison with some other D queues. 37 | 38 | ## Design: user MUST read this to use this wrapper library! 39 | 40 | ### To use this libary, you have to know how the orginal C library works 41 | 42 | Please read the C-doc before using this D wrapper lib: 43 | 44 | https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Release_7.1.1_Documentation 45 | 46 | NOTE: 47 | 48 | liblfds7.1.1/liblfds711/inc/liblfds711/lfds711_porting_abstraction_layer_processor.h 49 | ``` 50 | on __x86_64__ machines 51 | typedef int long long unsigned lfds711_pal_uint_t; 52 | ``` 53 | 54 | E.g. 55 | 56 | " 57 | To make those initial values valid (which is to say, visible) upon other logical cores, threads on those cores need to issue the define 58 | LFDS711_MISC_MAKE_VALID_ON_CURRENT_LOGICAL_CORE_INITS_COMPLETED_BEFORE_NOW_ON_ANY_OTHER_LOGICAL_CORE, which does that which it says; any init calls, which have completed (i.e. returned) on any other logical cores will now be valid (visible) on this logical core. 59 | 60 | https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Define_LFDS711_MISC_MAKE_VALID_ON_CURRENT_LOGICAL_CORE_INITS_COMPLETED_BEFORE_NOW_ON_ANY_OTHER_LOGICAL_CORE 61 | " 62 | 63 | It’s ugly macro of that C lib need to be called, but need to live with it. 64 | 65 | And even laughable this: 66 | 67 | " 68 | The LFDS711_QUEUE_BMM_QUERY_GET_POTENTIALLY_INACCURATE_COUNT query is not guaranteed to be accurate. Where enqueue and dequeue operations are not guaranteed to be visible by the time the function calls return, similarly, it may be that a count will during its operation not see an element which has been enqueued, or see that an element has been dequeued. In general however it should be bang on; it's just it's not guaranteed. 69 | 70 | https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_bmm_query#Notes 71 | " 72 | 73 | Yet, this library is the best time tested open source library on the internet. I have no interest to re-invent the wheels, or make the wrapper universal. 74 | 75 | Because it’s much better than the ~4x times slower fewly-used D queues I have found. 76 | 77 | 78 | ### Memory management: remember this is a thin wrapper lib in D 79 | 80 | Let C's be C's, and let D's be D's, i.e. 81 | 82 | * C manage C's memory (the container), and 83 | * D manage D's memory (the objects) 84 | 85 | The only thing interfacing between C and D is the simple uintptr_t (void*) as *value*, so to use this D library: 86 | 87 | * all primitive types (whose .sizeof upto pointer size on the target machine) | class (pointer)'s *value* are stored as value of uintptr_t 88 | * everything else, i.e. all (fat) objects' *address* are stored as value of uintptr_t 89 | 90 | The only extra requirement on the D side is to keep reference to those fat objects to avoid it being GC-ed before being pop-ed. 91 | 92 | (Just as: don't push a stack var into any-type-of queue, and pop it after the stack is gone -- this is the responsibility of the *programmer*, not the container.) 93 | 94 | That's all. 95 | 96 | And please remember to keep a reference on the D side of the fat objects you put in the container: 97 | 98 | E.g. 99 | 100 | Don't: 101 | ``` 102 | queue.push(new Object); // receiver may get garbage reference 103 | queue.push(FatStruct()); // temporary is gone after push() 104 | queue.push(createStuff!options()); // createStuff is somewhere inside 20kLOC in another module 105 | ``` 106 | 107 | Instead, rewrite them as: 108 | 109 | ``` 110 | dSideRefHolder_toPreventGC_BeforePoped_Var = dSideWhateverStuff(); 111 | queue.push(dSideRefHolder_toPreventGC_BeforePoped_Var); 112 | ``` 113 | 114 | Another way is manually (without GC) allocate the object, so GC will not scan them (and their fields) for collection. 115 | 116 | ## Known issue: 117 | 118 | * the queue_umm.h will leak one lfds711_queue_umm_element upon queue_umm_destroy() call. 119 | 120 | * queue_umm is ~6x slower than queue_bss, ~4x slower than queue_bmm; because each push cause one lfds711_queue_umm_element aligned_alloc 121 | -------------------------------------------------------------------------------- /comparison/Makefile: -------------------------------------------------------------------------------- 1 | 2 | DMD_DFLAGS = -release -m64 -boundscheck=off -O 3 | 4 | LDC_DFLAGS = --d-debug 5 | LDC_DFLAGS = -O4 --release --boundscheck=off 6 | 7 | DMD_DUB = DFLAGS="$(DMD_DFLAGS)" ${DMD}/windows/bin/dub.exe 8 | LDC_DUB = DFLAGS="$(LDC_DFLAGS)" ${LDC}/bin/dub 9 | 10 | array: 11 | $(LDC_DUB) plain_array.d 12 | 13 | ldc1p1c: 14 | $(LDC_DUB) build --single test_queue.d 15 | ./app 16 | 17 | dmd1p1c: 18 | $(DMD_DUB) test_queue.d 19 | 20 | 21 | dlist: 22 | ldc2 $(LDC_DFLAGS) dlist_test.d dlist.d 23 | ./dlist_test 24 | 25 | javamp: 26 | javac ThroughputMpTest.java 27 | java ThroughputMpTest 28 | 29 | dmp: 30 | $(LDC_DUB) mp.d 31 | 32 | elembuf_wait_free: 33 | $(LDC_DUB) build --single elembuf_wait_free_queue.d 34 | ./app 35 | 36 | elembuf_sync_q: 37 | $(LDC_DUB) elembuf_sync_queue.d 38 | 39 | go_d: 40 | $(LDC_DUB) go.d 41 | 42 | jin_go_q: 43 | ldc2 $(LDC_DFLAGS) test_queue.d jin_go_queue.d 44 | ./test_queue 45 | 46 | clean: 47 | $(RM) *.o 48 | -------------------------------------------------------------------------------- /comparison/README.md: -------------------------------------------------------------------------------- 1 | 2 | ``` 3 | # test 1-producer-1-consumer lock-free queue 4 | $ make ldc1p1c 5 | received 1000000000 messages in 9845 msec sum=499999999500000000 speed=101574 msg/msec 6 | received 1000000000 messages in 6219 msec sum=499999999500000000 speed=160797 msg/msec 7 | 8 | $ make dmd1p1c 9 | received 1000000000 messages in 53607 msec sum=499999999500000000 speed=18654 msg/msec 10 | 11 | 12 | # test dlist: Lock-free deques and doubly linked lists 13 | $ make dlist 14 | ldc2 -O4 --release --boundscheck=off dlist_test.d dlist.d 15 | ./dlist_test 16 | received 1000000000 messages in 119041 msec sum=499999999500000000 speed=8400 msg/msec 17 | 18 | 19 | # compare message passing between Java & D 20 | $ make javamp 21 | 10000000 messages received in 778.0 ms, sum=49999995000000 speed: 0.0778 microsec/message, 12853.470437017995 messages/msec 22 | 23 | $ make dmp 24 | received 100000000 messages in 27574 msec sum=4999999950000000 speed=3626 msg/msec 25 | 26 | 27 | # go.d using ldc 28 | $ make go_d 29 | received 100000000 messages in 2906 msec sum=4999999950000000 speed=34411 msg/msec 30 | 31 | 32 | # elembuf wait free queue (with producer in fill src delegate) 33 | $ make elembuf_wait_free 34 | received 1000000000 messages in 2732 msec sum=499999999500000000 speed=366032 msg/msec 35 | received 1000000000 messages in 2692 msec sum=499999999500000000 speed=371471 msg/msec 36 | 37 | # elembuf backed plain D sync queue 38 | $ make elembuf_sync_q 39 | received 100000000 messages in 27078 msec sum=5000000050000000 speed=3693 msg/msec 40 | 41 | # plain array loop :-) 42 | $ make array 43 | received 1000000000 messages in 1227 msec sum=499999999500000000 speed=814995 msg/msec 44 | ``` 45 | -------------------------------------------------------------------------------- /comparison/ThroughputMpTest.java: -------------------------------------------------------------------------------- 1 | //package inutil.local; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | 6 | public class ThroughputMpTest { 7 | static long n=10000000; 8 | 9 | static BlockingQueue queue=new ArrayBlockingQueue(1000); 10 | static class Consumer implements Runnable { 11 | @Override 12 | public void run() { 13 | long s=0; 14 | try { 15 | long t0=System.currentTimeMillis(); 16 | for(int i=0;i"; 400 | pNode = pNode._next; 401 | } while (pNode != cast(Node*)&this._tail); 402 | res ~= to!string(pNode) ~ "\n"; 403 | auto rev = ""; 404 | do 405 | { 406 | rev = "<-" ~ to!string(pNode) ~ rev; 407 | pNode = pNode._prev; 408 | } while (pNode != cast(Node*)&this._head); 409 | rev = to!string(pNode) ~ rev; 410 | return res ~ rev; 411 | } 412 | 413 | this() 414 | { 415 | this._head._prev = bottom; 416 | this._head._next = &this._tail; 417 | this._head.sentinel = 0xdeadbeef; 418 | this._tail._prev = &this._head; 419 | this._tail._next = bottom; 420 | this._tail.sentinel = 0xdeadbeef; 421 | } 422 | 423 | bool empty() 424 | { 425 | return this._head._next == &this._tail; 426 | } 427 | 428 | void pushFront(shared T value) 429 | { 430 | auto newNode = new shared(Node)(value); 431 | newNode._next = this._head._next; 432 | newNode._prev = &this._head; 433 | this._head._next = newNode; 434 | newNode._next._prev = newNode; 435 | } 436 | 437 | void pushBack(shared T value) 438 | { 439 | auto newNode = new shared(Node)(value); 440 | newNode._next = &this._tail; 441 | newNode._prev = this._tail._prev; 442 | this._tail._prev = newNode; 443 | newNode._prev._next = newNode; 444 | } 445 | 446 | @property shared(T)* popFront() 447 | { 448 | if (this.empty) 449 | return null; 450 | else 451 | { 452 | shared(Node)* node = this._head._next; 453 | this._head._next = node._next; 454 | node._next._prev = &this._head; 455 | return &node._payload; 456 | } 457 | } 458 | 459 | @property shared(T)* popBack() 460 | { 461 | if (this.empty) 462 | return null; 463 | else 464 | { 465 | shared(Node)* node = this._tail._prev; 466 | this._tail._prev = node._prev; 467 | node._prev._next = &this._tail; 468 | return &node._payload; 469 | } 470 | } 471 | 472 | bool next(ref shared(Node)* cursor) 473 | { 474 | if (cursor == &this._tail) 475 | return false; 476 | else 477 | { 478 | cursor = cursor._next; 479 | return true; 480 | } 481 | } 482 | 483 | bool prev(ref shared(Node)* cursor) 484 | { 485 | if (cursor == &this._head) 486 | return false; 487 | else 488 | { 489 | cursor = cursor._prev; 490 | return true; 491 | } 492 | } 493 | 494 | shared(T)* deleteNode(ref shared(Node)* cursor) 495 | { 496 | if (cursor == &this._head || cursor == &this._tail) 497 | return null; 498 | else 499 | { 500 | shared(Node)* node = cursor; 501 | node._prev._next = node._next; 502 | node._next._prev = node._prev; 503 | return &node._payload; 504 | } 505 | } 506 | 507 | void insertBefore(ref shared(Node)* cursor, shared T value) 508 | { 509 | if (cursor == &this._head) 510 | return this.insertAfter(cursor, value); 511 | else 512 | { 513 | auto node = cast(shared)new Node(value); 514 | node._next = cursor; 515 | node._prev = cursor._prev; 516 | cursor._prev._next = node; 517 | cursor._prev = node; 518 | } 519 | } 520 | 521 | void insertAfter(ref shared(Node)* cursor, in T value) 522 | { 523 | if (cursor == &this._tail) 524 | return this.insertBefore(cursor, value); 525 | else 526 | { 527 | auto node = cast(shared)new Node(value); 528 | node._prev = cursor; 529 | node._next = cursor._next; 530 | cursor._next._prev = node; 531 | cursor._next = node; 532 | } 533 | } 534 | } 535 | 536 | //////////////////////////////////////////////////////////////////////////////// 537 | // lsb helper 538 | //////////////////////////////////////////////////////////////////////////////// 539 | 540 | private: 541 | 542 | static bool haslsb(T)(T* p) 543 | { 544 | return (cast(size_t)p & 1) != 0; 545 | } 546 | 547 | static T* setlsb(T)(T* p) 548 | { 549 | return cast(T*)(cast(size_t)p | 1); 550 | } 551 | 552 | static T* clearlsb(T)(T* p) 553 | { 554 | return cast(T*)(cast(size_t)p & ~1); 555 | } 556 | 557 | //////////////////////////////////////////////////////////////////////////////// 558 | // Unit Tests 559 | //////////////////////////////////////////////////////////////////////////////// 560 | 561 | version (unittest): 562 | 563 | unittest 564 | { 565 | auto testList = new shared(TList)(); 566 | testList.pushFront(cast(shared)TPayload(0)); 567 | auto cursor = &testList._head; 568 | testList.next(cursor); 569 | cursor._next = setlsb(cursor._next); 570 | cursor._prev = setlsb(cursor._prev); 571 | testList.insertBefore(cursor, cast(shared)TPayload(1)); 572 | } 573 | 574 | unittest 575 | { 576 | auto testList = new shared(TList)(); 577 | assert(testList._head._next == &testList._tail); 578 | assert(testList._tail._prev == &testList._head); 579 | 580 | testList.pushFront(cast(shared)TPayload(0)); 581 | assert(testList._head._next != &testList._tail); 582 | assert(testList._tail._prev != &testList._head); 583 | assert(testList._head._next._next == &testList._tail); 584 | assert(testList._tail._prev._prev == &testList._head); 585 | assert(testList._head._next._payload == cast(shared)TPayload(0)); 586 | 587 | auto pValue = testList.popFront(); 588 | assert(testList._head._next == &testList._tail); 589 | assert(testList._tail._prev == &testList._head); 590 | assert(*pValue == cast(shared)TPayload(0)); 591 | } 592 | 593 | struct Heavy 594 | { 595 | this (size_t val) 596 | { 597 | this.val[0] = val; 598 | } 599 | size_t[16] val; 600 | } 601 | 602 | struct Light 603 | { 604 | this (size_t val) 605 | { 606 | this.val = val; 607 | } 608 | size_t val; 609 | } 610 | 611 | alias Light TPayload; 612 | alias shared AtomicDList!(TPayload) TList; 613 | //alias SyncedDList!(TPayload) TList; 614 | shared TList sList; 615 | enum amount = 10_000; 616 | enum Position { Front, Back, } 617 | 618 | void adder(Position Where)() 619 | { 620 | size_t count = amount; 621 | do 622 | { 623 | static if (Where == Position.Front) 624 | sList.pushFront(cast(shared)TPayload(count)); 625 | else 626 | sList.pushBack(cast(shared)TPayload(count)); 627 | } while (--count); 628 | } 629 | 630 | void remover(Position Where)() 631 | { 632 | size_t count = amount; 633 | do 634 | { 635 | static if (Where == Position.Front) 636 | while (sList.popFront() is null) {} 637 | else 638 | while (sList.popBack() is null) {} 639 | } while (--count); 640 | } 641 | 642 | void iterAdder(Position Where)() 643 | { 644 | size_t count = amount; 645 | static if (Where == Position.Front) 646 | { 647 | do 648 | { 649 | auto cursor = &sList._head; 650 | do 651 | { 652 | sList.insertAfter(cursor, cast(shared)TPayload(count)); 653 | } while (--count && sList.next(cursor)); 654 | } while (count); 655 | } 656 | else 657 | { 658 | do 659 | { 660 | auto cursor = &sList._tail; 661 | do 662 | { 663 | sList.insertBefore(cursor, cast(shared)TPayload(count)); 664 | } while (--count && sList.prev(cursor)); 665 | } while (count); 666 | } 667 | } 668 | 669 | void iterRemover(Position Where)() 670 | { 671 | size_t count = amount; 672 | static if (Where == Position.Front) 673 | { 674 | do 675 | { 676 | auto cursor = &sList._head; 677 | do 678 | { 679 | sList.next(cursor); 680 | } while (sList.deleteNode(cursor) !is null && --count); 681 | } while (count); 682 | } 683 | else 684 | { 685 | do 686 | { 687 | auto cursor = &sList._tail; 688 | do 689 | { 690 | sList.prev(cursor); 691 | } while (sList.deleteNode(cursor) !is null && --count); 692 | } while (count); 693 | } 694 | } 695 | 696 | void iterator(Position Where)() 697 | { 698 | size_t max_steps; 699 | size_t times = amount; 700 | static if (Where == Position.Front) 701 | { 702 | do 703 | { 704 | size_t steps; 705 | auto cursor = &sList._head; 706 | while (sList.next(cursor)) { ++steps; } 707 | assert(cursor == &sList._tail); 708 | max_steps = max(max_steps, steps); 709 | } while (--times); 710 | } 711 | else 712 | { 713 | do 714 | { 715 | size_t steps; 716 | auto cursor = &sList._tail; 717 | while (sList.prev(cursor)) { ++steps; } 718 | assert(cursor == &sList._head); 719 | max_steps = max(max_steps, steps); 720 | } while (--times); 721 | } 722 | writefln("size %s", max_steps); 723 | } 724 | 725 | unittest 726 | { 727 | import std.parallelism : totalCPUs; 728 | 729 | sList = new shared(TList)(); 730 | size_t count; 731 | shared(TList.Node)* p; 732 | 733 | foreach(i; 0 .. totalCPUs) 734 | { 735 | if (i & 1) 736 | { 737 | spawn(&remover!(Position.Back)); 738 | spawn(&adder!(Position.Front)); 739 | spawn(&iterator!(Position.Back)); 740 | } 741 | else 742 | { 743 | spawn(&adder!(Position.Back)); 744 | spawn(&remover!(Position.Front)); 745 | spawn(&iterator!(Position.Front)); 746 | } 747 | } 748 | 749 | thread_joinAll(); 750 | count = 0; 751 | p = sList._head.next; 752 | while (p !is &sList._tail) 753 | { 754 | ++count; 755 | p = p.next; 756 | } 757 | writeln("queue empty? -> ", count); 758 | assert(count == 0, to!string(count)); 759 | 760 | foreach(i; 0 .. totalCPUs) 761 | { 762 | if (i & 1) 763 | { 764 | spawn(&iterRemover!(Position.Front)); 765 | spawn(&iterAdder!(Position.Back)); 766 | spawn(&iterator!(Position.Back)); 767 | } 768 | else 769 | { 770 | spawn(&iterAdder!(Position.Front)); 771 | spawn(&iterRemover!(Position.Back)); 772 | spawn(&iterator!(Position.Front)); 773 | } 774 | } 775 | 776 | thread_joinAll(); 777 | count = 0; 778 | p = sList._head.next; 779 | while (p !is &sList._tail) 780 | { 781 | ++count; 782 | p = p.next; 783 | } 784 | writeln("list empty? -> ", count); 785 | assert(count == 0, to!string(count)); 786 | 787 | foreach(i; 0 .. totalCPUs) 788 | { 789 | if (i & 1) 790 | { 791 | spawn(&iterator!(Position.Back)); 792 | spawn(&iterRemover!(Position.Front)); 793 | spawn(&adder!(Position.Front)); 794 | spawn(&iterAdder!(Position.Front)); 795 | spawn(&remover!(Position.Back)); 796 | spawn(&iterator!(Position.Front)); 797 | } 798 | else 799 | { 800 | spawn(&iterator!(Position.Front)); 801 | spawn(&iterAdder!(Position.Back)); 802 | spawn(&remover!(Position.Front)); 803 | spawn(&iterRemover!(Position.Front)); 804 | spawn(&adder!(Position.Back)); 805 | spawn(&iterator!(Position.Front)); 806 | } 807 | } 808 | 809 | thread_joinAll(); 810 | count = 0; 811 | p = sList._head.next; 812 | while (p !is &sList._tail) 813 | { 814 | ++count; 815 | p = p.next; 816 | } 817 | writeln("mixed empty? -> ", count); 818 | assert(count == 0, to!string(count)); 819 | } 820 | -------------------------------------------------------------------------------- /comparison/dlist_test.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.sdl: 3 | name "app" 4 | dependency "lock-free" version="~>0.1.2" 5 | dflags "-release" "-m64" "-boundscheck=off" "-O" platform="dmd" 6 | dflags "-O4" "--release" "--boundscheck=off" platform="ldc2" 7 | +/ 8 | 9 | // dmd.exe -release -m64 -boundscheck=off -O rwqueue.d 10 | // ldc2 -O4 --release --boundscheck=off rwqueue.d 11 | 12 | /* 13 | import lock_free.dlist; 14 | import lock_free.rwqueue; 15 | */ 16 | import dlist; 17 | 18 | import std.stdio; 19 | import std.datetime; 20 | import core.atomic; 21 | import core.thread, std.concurrency; 22 | 23 | 24 | const n=1_000_000_000; //;_000 25 | enum amount = n; 26 | 27 | // alias SafeQueue = RWQueue; 28 | alias SafeQueue = AtomicDList; 29 | 30 | bool full(T)(ref shared AtomicDList!T dlist) { // 31 | return false; // it's never full 32 | } 33 | 34 | void push(T)(ref shared(SafeQueue!T) queue) { 35 | foreach (i; 0 .. amount) 36 | { 37 | while (queue.full) 38 | Thread.yield(); 39 | queue.push(cast(shared T)i); 40 | } 41 | } 42 | 43 | void pop(T)(ref shared(SafeQueue!T) queue) { 44 | StopWatch sw; 45 | sw.start(); 46 | long s = 0; 47 | foreach (i; 0 .. amount) 48 | { 49 | while (queue.empty) 50 | Thread.yield(); 51 | auto p = queue.pop(); 52 | //assert(p.i == cast(shared T)i); 53 | s += p.i; 54 | } 55 | sw.stop(); 56 | writeln("finished receiving"); 57 | writefln("received %d messages in %d msec sum=%d speed=%d msg/msec", n, sw.peek().msecs, s, n/sw.peek().msecs); 58 | } 59 | 60 | 61 | void main() { 62 | static struct Data { size_t i; } 63 | shared(SafeQueue!Data) queue = new shared(SafeQueue!Data)(); 64 | auto t0 = new Thread({push(queue);}), 65 | t1 = new Thread({pop(queue);}); 66 | t0.start(); t1.start(); 67 | t0.join(); t1.join(); 68 | } 69 | -------------------------------------------------------------------------------- /comparison/elembuf_sync_queue.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.sdl: 3 | name "app" 4 | dependency "elembuf" version="~>1.2.2" 5 | dflags "-release" "-m64" "-boundscheck=off" "-O" platform="dmd" 6 | dflags "-O4" "--release" "--boundscheck=off" platform="ldc2" 7 | +/ 8 | 9 | import std.stdio; 10 | import std.concurrency : receiveOnly, send, spawn, Tid, thisTid; 11 | import std.datetime; 12 | import core.atomic : atomicOp, atomicLoad, MemoryOrder; 13 | 14 | import elembuf; 15 | 16 | alias T = int; 17 | 18 | /* 19 | Queue that can be used safely among 20 | different threads. All access to an 21 | instance is automatically locked thanks to 22 | synchronized keyword. 23 | */ 24 | synchronized class SafeQueue(T) // Syncronised is SLOW as it is a mutex on everything! Use atomics with a shared or __gshared variable. 25 | { 26 | // Note: must be private in synchronized 27 | // classes otherwise D complains. 28 | //private T[] elements; 29 | alias BufferT = buffer!(T[], true); 30 | private BufferT elements; 31 | 32 | alias elements this; 33 | 34 | this(bool uselessVariable) // Constructor must not have 0 parameters. 35 | { 36 | // We cannot pass a buffer as it will deallocate when it exits scope. Thus we construct it here locally in the class and pass it on as a non-destructable global. 37 | elements = cast (shared) BufferT(); 38 | } 39 | 40 | auto length() 41 | { 42 | return (cast(BufferT)elements).length; 43 | } 44 | 45 | void push(T value) 46 | { 47 | 48 | // auto arr = [value]; 49 | 50 | /*(cast(BufferT))*/ elements ~= value; 51 | 52 | } 53 | 54 | /// Return T.init if queue empty 55 | void pop() { 56 | //import std.array : empty; 57 | 58 | //if (elements.empty) 59 | cast(BufferT)elements = (cast(BufferT)elements)[1 .. $]; 60 | } 61 | } 62 | 63 | //This must be a module variable so that once it exits producer scope, 64 | //it does not deallocate before the consumer is done 65 | //this is due to RAII, but will be removed in 1.2.X with explicit deinit function calls. 66 | shared SafeQueue!(T) queue; 67 | 68 | /* 69 | Safely print messages independent of 70 | number of concurrent threads. 71 | Note that variadic parameters 72 | are used 73 | for args! That is args might be 0 .. N 74 | parameters. 75 | */ 76 | void safePrint(T...)(T args) 77 | { 78 | // Just executed by one concurrently 79 | synchronized { 80 | import std.stdio : writeln; 81 | writeln(args); 82 | } 83 | } 84 | 85 | const n=100_000_000; 86 | 87 | shared int global_tls = 0; 88 | 89 | void threadProducer(shared(int)* queueCounter) 90 | { 91 | // Push values 0 to n-1 92 | foreach (i; 0..n) { 93 | 94 | while(queue.length == queue.max){} // You cannot fill forever, only fill when there is space available, i.e. buf.avail > 0 95 | queue.push(i); // Push will write element to available space. If pushed without available space, thread will error. 96 | 97 | //if(i % 1000 == 0) 98 | //safePrint("Pushed ", i); 99 | atomicOp!"+="(*queueCounter,1); 100 | atomicOp!"+="(global_tls,1); 101 | } 102 | safePrint("threadProducer:", atomicLoad(global_tls)); 103 | } 104 | 105 | void threadConsumer(Tid owner, 106 | shared(int)* queueCounter) 107 | { 108 | 109 | StopWatch sw; 110 | sw.start(); 111 | long s = 0; 112 | 113 | int popped = 0; 114 | while (popped != n) // IMPORTANT! This was popped = n, but it should be < n, because we start from 0, so the last pop should be n-1 115 | { 116 | if(queue.length == 0) 117 | continue; 118 | 119 | s += queue[0]; 120 | // if (i == int.init) continue; //If you wish sum to be correct, this should not be done as the first item should be 0. 121 | //safePrint("Popped ", queue[0], " (Consumer pushed ", atomicLoad(*queueCounter), ")"); 122 | queue.pop; 123 | ++popped; 124 | // safely fetch current value of 125 | // queueCounter using atomicLoad 126 | atomicOp!"-="(global_tls,1); 127 | } 128 | 129 | sw.stop(); 130 | safePrint("threadConsumer:", atomicLoad(global_tls)); 131 | safePrint("finished receiving"); 132 | safePrint("received ",n," messages in ",sw.peek().msecs," msec sum=",s," speed=",n/sw.peek().msecs," msg/msec"); 133 | 134 | // I'm done! 135 | owner.send(true); // Only send this after you are actually done and not printing. 136 | } 137 | 138 | void main() 139 | { 140 | queue = new shared(SafeQueue!T)(true); 141 | // Buffer would deallocate when it exits the first spawn function scope if it were constructed here. 142 | // Thus we construct it inside the class. See EXPERT -level docs. 143 | shared int counter = 0; 144 | 145 | spawn(&threadProducer, &counter); 146 | auto consumer = spawn(&threadConsumer, thisTid, &counter); 147 | 148 | auto stopped = receiveOnly!bool; 149 | assert(stopped); 150 | 151 | //readln; // Had to add this as it goes away too fast for me. REMOVE THIS LINE! 152 | 153 | } 154 | 155 | -------------------------------------------------------------------------------- /comparison/elembuf_wait_free_queue.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.sdl: 3 | name "app" 4 | dependency "elembuf" version="~>1.2.2" 5 | dflags "-release" "-m64" "-boundscheck=off" "-O" platform="dmd" 6 | dflags "-O4" "--release" "--boundscheck=off" platform="ldc2" 7 | +/ 8 | 9 | // dmd.exe -release -m64 -boundscheck=off -O buffer.d 10 | // ldc2 -O4 --release --boundscheck=off buffer.d 11 | 12 | import std.stdio; 13 | import std.datetime.stopwatch; 14 | import core.atomic; 15 | import core.thread, std.concurrency; 16 | 17 | import elembuf; 18 | 19 | const n=1_000_000_000; //;_000 20 | enum amount = n; 21 | 22 | 23 | void main() //line 22 24 | { 25 | auto buffer = tbuffer(size_t[].init); 26 | 27 | size_t srci = 0; // Source index 28 | size_t consi = 0; // Consumer index 29 | 30 | size_t sum = 0; 31 | 32 | size_t delegate(size_t[]) src = (size_t[] x) // Tell background thread about source 33 | { 34 | const needToFill = amount - srci; 35 | 36 | if( x.length >= needToFill ) // Final fill! 37 | { 38 | foreach(i;0..needToFill){ 39 | x[i] = srci; 40 | srci++; 41 | } 42 | return needToFill; 43 | } 44 | else // Long way to go still... 45 | { 46 | foreach(ref i;x){ 47 | i = srci; 48 | srci++; 49 | } 50 | return x.length; 51 | } 52 | 53 | }; 54 | 55 | buffer ~= src; 56 | 57 | // START! 58 | StopWatch sw; 59 | sw.start(); 60 | 61 | while(consi < amount) 62 | { 63 | buffer ~= buffer.source; 64 | 65 | foreach(elem; buffer) 66 | sum += elem; 67 | 68 | consi += buffer.length; 69 | 70 | buffer = buffer[$..$]; 71 | } 72 | 73 | sw.stop(); 74 | writeln("finished receiving"); 75 | writefln("received %d messages in %d msec sum=%d speed=%d msg/msec", n, sw.peek().total!("msecs"), sum, n/sw.peek().total!("msecs")); 76 | 77 | buffer.deinit; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /comparison/go.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.sdl: 3 | name "app" 4 | dependency "jin-go" version="~>2.0.0" 5 | +/ 6 | 7 | import std.datetime.stopwatch; 8 | import std.stdio; 9 | 10 | import jin.go; 11 | 12 | const int n = 100_000_000; 13 | 14 | void threadProducer(Output!int queue) 15 | { 16 | foreach (int i; 0..n) { 17 | queue.put(i); 18 | } 19 | } 20 | 21 | void main() 22 | { 23 | Input!int queue; 24 | jin.go.go.go!threadProducer(queue.pair); 25 | 26 | StopWatch sw; 27 | sw.start(); 28 | long sum = 0; 29 | 30 | foreach (p; queue) 31 | { 32 | sum += p; 33 | } 34 | 35 | sw.stop(); 36 | 37 | writefln("received %d messages in %d msec sum=%d speed=%d msg/msec", n, 38 | sw.peek.total!"msecs", sum, n / sw.peek.total!"msecs"); 39 | 40 | assert(sum == (n * (n - 1) / 2)); 41 | } 42 | -------------------------------------------------------------------------------- /comparison/mp.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dub 2 | /+ dub.sdl: 3 | +/ 4 | import std.concurrency, std.stdio; 5 | import std.datetime; 6 | 7 | const n=100000000; 8 | void main() { 9 | auto tid=spawn(&receiver); 10 | setMaxMailboxSize(tid, 1000, OnCrowding.block); 11 | tid.send(thisTid); 12 | foreach(i; 0..n) { 13 | tid.send(i); 14 | } 15 | writeln("finished sending"); 16 | auto s=receiveOnly!(string)(); 17 | writeln("received ", s); 18 | } 19 | 20 | void receiver() { 21 | auto mainTid=receiveOnly!(Tid)(); 22 | StopWatch sw; 23 | sw.start(); 24 | long s; 25 | for(auto i=0;i0.4.10" 11 | }, 12 | "preBuildCommands": [ 13 | "make gen" 14 | ], 15 | "postBuildCommands": [ 16 | ], 17 | "lflags-linux": [ 18 | "-L$PACKAGE_DIR", 19 | "-llfdsd", 20 | "-llfdsdc", 21 | "-llfds711" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/liblfds release 7.1.1 source.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mw66/liblfdsd/c42b522fd00f66a25fb8e87ed605ddfb2ca32761/lib/liblfds release 7.1.1 source.tar.bz2 -------------------------------------------------------------------------------- /liblfdsd.c: -------------------------------------------------------------------------------- 1 | #include "liblfdsd.h" 2 | -------------------------------------------------------------------------------- /liblfdsd.h: -------------------------------------------------------------------------------- 1 | #ifndef liblfdsd_h 2 | #define liblfdsd_h 3 | 4 | // bmm & bss queue has the same API interface: 5 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(bounded,_single_producer,_single_consumer) 6 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(bounded,_many_producer,_many_consumer) 7 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(unbounded,_many_producer,_many_consumer) 8 | 9 | 10 | #include 11 | #include 12 | #include 13 | #include "liblfds711.h" 14 | 15 | // let's hide all the uglyness in this C file 16 | // from D side, it only sees the value type as uintptr_t (void*) 17 | // https://stackoverflow.com/a/1464194 18 | // https://stackoverflow.com/a/14068191 19 | // On segmented architectures, on the other hand, it is usual for uintptr_t to be bigger than size_t 20 | typedef uintptr_t container_value_t; // unsigned integer type capable of holding a pointer to void 21 | 22 | 23 | #define INLINE // dpp will remove 'inline' to generate .d; and in .c we need generate the symbol 24 | 25 | INLINE bool is_power_of_two(size_t x) { 26 | return (x >= 2) && ((x & (x - 1)) == 0); 27 | } 28 | 29 | INLINE void ensure_lfds_valid_init_on_current_logical_core() { 30 | LFDS711_MISC_MAKE_VALID_ON_CURRENT_LOGICAL_CORE_INITS_COMPLETED_BEFORE_NOW_ON_ANY_OTHER_LOGICAL_CORE; 31 | } 32 | 33 | 34 | #include "queue_bmm.h" 35 | #include "queue_bss.h" 36 | #include "queue_umm.h" 37 | 38 | #endif//liblfdsd_h 39 | -------------------------------------------------------------------------------- /queue_bmm.h: -------------------------------------------------------------------------------- 1 | #ifndef queue_bmm_h 2 | #define queue_bmm_h 3 | 4 | // this file is intended to be included from liblfdsd.h 5 | 6 | typedef struct { 7 | // https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15 8 | // Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning. 9 | struct lfds711_queue_bmm_state qstate; 10 | struct lfds711_queue_bmm_element* element; 11 | lfds711_pal_uint_t number_elements; 12 | void *user_state; // do we need this? always set to NULL now 13 | } c_queue_bmm; 14 | 15 | // create a new queue 16 | // size: must be a positive integer power of 2 (2, 4, 8, 16, etc) 17 | INLINE c_queue_bmm* queue_bmm_new(size_t size) { 18 | if (!is_power_of_two(size)) { 19 | return NULL; // TODO: resize it! 20 | } 21 | c_queue_bmm* queue = aligned_alloc(LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES, sizeof(c_queue_bmm)); 22 | queue->number_elements = size; 23 | queue->user_state = NULL; 24 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_bmm_init_valid_on_current_logical_core 25 | // There are no alignment requirements for this allocation. 26 | queue->element = malloc(size*sizeof(struct lfds711_queue_bmm_element)); 27 | 28 | lfds711_queue_bmm_init_valid_on_current_logical_core(&(queue->qstate), queue->element, size, queue->user_state); 29 | 30 | return queue; 31 | } 32 | 33 | // only push basic type of size < size_t; or object pointer 34 | INLINE bool queue_bmm_push(c_queue_bmm* queue, container_value_t value) { 35 | int r = lfds711_queue_bmm_enqueue(&(queue->qstate), NULL, (void*)value); 36 | return r; // 0 when full 37 | } 38 | 39 | // return the element, if ok is true; otherwise 0 40 | INLINE container_value_t queue_bmm_pop(c_queue_bmm* queue, int* ok) { 41 | void* value = NULL; 42 | *ok = lfds711_queue_bmm_dequeue(&(queue->qstate), NULL, &value); 43 | if (*ok) { 44 | return (container_value_t)value; 45 | } 46 | return 0; 47 | } 48 | 49 | 50 | // length 51 | INLINE size_t queue_bmm_length(c_queue_bmm* queue) { 52 | lfds711_pal_uint_t len; 53 | lfds711_queue_bmm_query(&(queue->qstate), LFDS711_QUEUE_BMM_QUERY_GET_POTENTIALLY_INACCURATE_COUNT, NULL, &len); 54 | return len; 55 | } 56 | 57 | // destroy the queue 58 | INLINE void queue_bmm_destroy(c_queue_bmm* queue) { 59 | lfds711_queue_bmm_cleanup(&(queue->qstate), NULL ); 60 | free(queue->element); 61 | free(queue); 62 | } 63 | 64 | 65 | #endif//queue_bmm_h 66 | -------------------------------------------------------------------------------- /queue_bmm_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "queue_bmm_bss.h" 5 | 6 | struct test_data 7 | { 8 | char 9 | name[64]; 10 | }; 11 | 12 | int use_liblfds711_h_main() // use raw "liblfds711.h" 13 | { 14 | struct lfds711_queue_bmm_element 15 | qbmme[8]; // TRD : must be a positive integer power of 2 (2, 4, 8, 16, etc) 16 | 17 | struct lfds711_queue_bmm_state 18 | qbmms; 19 | 20 | struct test_data 21 | td, 22 | *temp_td; 23 | 24 | lfds711_queue_bmm_init_valid_on_current_logical_core( &qbmms, qbmme, 8, NULL ); 25 | 26 | strcpy( td.name, "Madge The Skutter" ); 27 | 28 | lfds711_queue_bmm_enqueue( &qbmms, NULL, &td ); 29 | 30 | lfds711_queue_bmm_dequeue( &qbmms, NULL, (void**)(&temp_td) ); 31 | 32 | printf( "skutter name = %s %d\n", temp_td->name, LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES ); 33 | 34 | lfds711_queue_bmm_cleanup( &qbmms, NULL ); 35 | 36 | return( EXIT_SUCCESS ); 37 | } 38 | 39 | int use_queue_bmm_h_main() { // use our wrapper "queue_bmm.h" 40 | struct test_data 41 | td, 42 | *temp_td; 43 | 44 | void* vp; 45 | int ok; 46 | 47 | strcpy( td.name, "Madge The Skutter" ); 48 | 49 | c_queue_bmm* queue = queue_bmm_new(8); 50 | queue_bmm_push(queue, &td); 51 | temp_td = queue_bmm_pop(queue, &ok); 52 | queue_bmm_destroy(queue); 53 | 54 | printf( "skutter name = %s %lu %d\n", temp_td->name, sizeof(vp), LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES ); 55 | return( EXIT_SUCCESS ); 56 | } 57 | 58 | 59 | int main() { // use our wrapper "queue_bmm.h" 60 | use_liblfds711_h_main(); 61 | use_queue_bmm_h_main(); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /queue_bss.h: -------------------------------------------------------------------------------- 1 | #ifndef queue_bss_h 2 | #define queue_bss_h 3 | 4 | // this file is intended to be included from liblfdsd.h 5 | 6 | typedef struct { 7 | // https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15 8 | // Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning. 9 | struct lfds711_queue_bss_state qstate; 10 | struct lfds711_queue_bss_element* element; 11 | lfds711_pal_uint_t number_elements; 12 | void *user_state; // do we need this? always set to NULL now 13 | } c_queue_bss; 14 | 15 | // create a new queue 16 | // size: must be a positive integer power of 2 (2, 4, 8, 16, etc) 17 | INLINE c_queue_bss* queue_bss_new(size_t size) { 18 | if (!is_power_of_two(size)) { 19 | return NULL; // TODO: resize it! 20 | } 21 | c_queue_bss* queue = aligned_alloc(LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES, sizeof(c_queue_bss)); 22 | queue->number_elements = size; 23 | queue->user_state = NULL; 24 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_bss_init_valid_on_current_logical_core 25 | // There are no alignment requirements for this allocation. 26 | queue->element = malloc(size*sizeof(struct lfds711_queue_bss_element)); 27 | 28 | lfds711_queue_bss_init_valid_on_current_logical_core(&(queue->qstate), queue->element, size, queue->user_state); 29 | 30 | return queue; 31 | } 32 | 33 | // only push basic type of size < size_t; or object pointer 34 | INLINE bool queue_bss_push(c_queue_bss* queue, container_value_t value) { 35 | int r = lfds711_queue_bss_enqueue(&(queue->qstate), NULL, (void*)value); 36 | return r; // 0 when full 37 | } 38 | 39 | // return the element, if ok is true; otherwise 0 40 | INLINE container_value_t queue_bss_pop(c_queue_bss* queue, int* ok) { 41 | void* value = NULL; 42 | *ok = lfds711_queue_bss_dequeue(&(queue->qstate), NULL, &value); 43 | if (*ok) { 44 | return (container_value_t)value; 45 | } 46 | return 0; 47 | } 48 | 49 | 50 | // length 51 | INLINE size_t queue_bss_length(c_queue_bss* queue) { 52 | lfds711_pal_uint_t len; 53 | lfds711_queue_bss_query(&(queue->qstate), LFDS711_QUEUE_BSS_QUERY_GET_POTENTIALLY_INACCURATE_COUNT, NULL, &len); 54 | return len; 55 | } 56 | 57 | // destroy the queue 58 | INLINE void queue_bss_destroy(c_queue_bss* queue) { 59 | lfds711_queue_bss_cleanup(&(queue->qstate), NULL ); 60 | free(queue->element); 61 | free(queue); 62 | } 63 | 64 | 65 | #endif//queue_bss_h 66 | -------------------------------------------------------------------------------- /queue_bss_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "liblfds711.h" 5 | 6 | struct test_data 7 | { 8 | char 9 | name[64]; 10 | }; 11 | 12 | int main() 13 | { 14 | struct lfds711_queue_bss_element 15 | qbsse[8]; 16 | 17 | struct lfds711_queue_bss_state 18 | qbsss; 19 | 20 | struct test_data 21 | td, 22 | *temp_td; 23 | 24 | lfds711_queue_bss_init_valid_on_current_logical_core( &qbsss, qbsse, 8, NULL ); 25 | 26 | strcpy( td.name, "Bob The Skutter" ); 27 | 28 | lfds711_queue_bss_enqueue( &qbsss, NULL, &td ); 29 | 30 | lfds711_queue_bss_dequeue( &qbsss, NULL, (void**)(&temp_td) ); 31 | 32 | printf( "skutter name = %s\n", temp_td->name ); 33 | 34 | lfds711_queue_bss_cleanup( &qbsss, NULL ); 35 | 36 | return( EXIT_SUCCESS ); 37 | } 38 | -------------------------------------------------------------------------------- /queue_umm.h: -------------------------------------------------------------------------------- 1 | #ifndef queue_umm_h 2 | #define queue_umm_h 3 | 4 | // this file is intended to be included from liblfdsd.h 5 | 6 | typedef struct { 7 | // https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15 8 | // Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning. 9 | struct lfds711_queue_umm_state qstate; 10 | struct lfds711_queue_umm_element* element; 11 | lfds711_pal_uint_t number_elements; 12 | void *user_state; // do we need this? always set to NULL now 13 | } c_queue_umm; 14 | 15 | // create a new queue 16 | // size: will set to 1, this arg is not needed for unbounded queue, only to make liblfds.dpp easier so all the queue have the same interface 17 | INLINE c_queue_umm* queue_umm_new(size_t size) { 18 | size = 1; // for a single element 19 | c_queue_umm* queue = aligned_alloc(LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES, sizeof(c_queue_umm)); 20 | queue->number_elements = size; 21 | queue->user_state = NULL; 22 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_umm_init_valid_on_current_logical_core 23 | // A pointer to a user-allocated LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES aligned struct lfds711_queue_umm_element. 24 | // The queue data structure contains a single dummy element; this is that dummy element. 25 | // The queue requires a single dummy element to function. This element is in fact used normally - it will emerge from the queue 26 | // - and so it must be possible for the user to treat it, when it does emerge, as he would do any other element. 27 | // NOTE: base on queue_umm_test.c, it is the 1st element that will be pop-ed! ref:queue_umm_destroy() 28 | // TODO: pre-allocate some lfds711_queue_umm_element? 29 | queue->element = aligned_alloc(LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES, sizeof(struct lfds711_queue_umm_element)); 30 | // printf("allc: %p\n", (void*)(queue->element)); 31 | 32 | lfds711_queue_umm_init_valid_on_current_logical_core(&(queue->qstate), queue->element, queue->user_state); 33 | 34 | return queue; 35 | } 36 | 37 | // only push basic type of size < size_t; or object pointer 38 | INLINE bool queue_umm_push(c_queue_umm* queue, container_value_t value) { 39 | // each time, we push, we alloc one lfds711_queue_umm_element 40 | // TODO: add a default to NULL lfds711_queue_umm_element pointer, to use the pre-allocate lfds711_queue_umm_element when available? 41 | struct lfds711_queue_umm_element* qe = aligned_alloc(LFDS711_PAL_ATOMIC_ISOLATION_IN_BYTES, sizeof(struct lfds711_queue_umm_element)); 42 | // printf("allc: %p\n", (void*)qe); 43 | if (qe) { 44 | LFDS711_QUEUE_UMM_SET_VALUE_IN_ELEMENT((*qe), (void*)value); // the macro takes A pointer, which will be cast by the macro to a void *, which the value in queue_element is set to. 45 | lfds711_queue_umm_enqueue(&(queue->qstate), qe); 46 | return true; // unbounded queue, always successful 47 | } 48 | return false; 49 | } 50 | 51 | // return the element, if ok is true; otherwise 0 52 | INLINE container_value_t queue_umm_pop(c_queue_umm* queue, int* ok) { 53 | struct lfds711_queue_umm_element *qe = NULL; 54 | void* value = NULL; 55 | *ok = lfds711_queue_umm_dequeue(&(queue->qstate), &qe); 56 | if (*ok) { // Dequeuing only fails if the queue is empty. 57 | value = LFDS711_QUEUE_UMM_GET_VALUE_FROM_ELEMENT(*qe); // the macro Returns a void pointer, the value from the element. 58 | // printf("free: %p\n", (void*)qe); 59 | free(qe); // after get the value; whenever we pop, free the alloc-ed lfds711_queue_umm_element, ref:queue_umm_push 60 | return (container_value_t)value; 61 | } 62 | return 0; 63 | } 64 | 65 | 66 | // length 67 | INLINE size_t queue_umm_length(c_queue_umm* queue) { 68 | lfds711_pal_uint_t len; 69 | lfds711_queue_umm_query(&(queue->qstate), LFDS711_QUEUE_UMM_QUERY_SINGLETHREADED_GET_COUNT, NULL, &len); 70 | return len; 71 | } 72 | 73 | // destroy the queue 74 | INLINE void queue_umm_destroy(c_queue_umm* queue) { 75 | lfds711_queue_umm_cleanup(&(queue->qstate), NULL ); 76 | // printf("free: %p\n", (void*)(queue->element)); 77 | // free(queue->element); // free(): double free detected in tcache 2 78 | // NOTE: we will have a memory leak here: the last queue_umm_push lfds711_queue_umm_element will not be free-ed 79 | free(queue); 80 | } 81 | 82 | 83 | #endif//queue_umm_h 84 | -------------------------------------------------------------------------------- /queue_umm_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "liblfds711.h" 4 | #include "queue_bmm_bss.h" 5 | 6 | struct test_data 7 | { 8 | struct lfds711_queue_umm_element 9 | qe; 10 | 11 | int 12 | number; 13 | }; 14 | 15 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Queue_(unbounded,_many_producer,_many_consumer) 16 | int use_liblfds711_h_main() { // use raw "liblfds711.h" 17 | int long long unsigned 18 | loop; 19 | 20 | struct lfds711_queue_umm_element 21 | *qe, 22 | qe_dummy; 23 | 24 | struct lfds711_queue_umm_state 25 | qs; 26 | 27 | struct test_data 28 | *td, 29 | *td_temp; 30 | 31 | lfds711_queue_umm_init_valid_on_current_logical_core( &qs, &qe_dummy, NULL ); 32 | 33 | td = malloc( sizeof(struct test_data) * 10 ); 34 | 35 | // TRD : queue ten elements 36 | for( loop = 0 ; loop < 10 ; loop++ ) 37 | { 38 | // TRD : we enqueue the numbers 0 to 9 39 | td[loop].number = (int) loop; 40 | LFDS711_QUEUE_UMM_SET_VALUE_IN_ELEMENT( td[loop].qe, &td[loop] ); 41 | lfds711_queue_umm_enqueue( &qs, &td[loop].qe ); 42 | } 43 | 44 | // TRD : dequeue until the queue is empty 45 | while( lfds711_queue_umm_dequeue(&qs, &qe) ) 46 | { 47 | struct test_data* temp_td = LFDS711_QUEUE_UMM_GET_VALUE_FROM_ELEMENT( *qe ); 48 | 49 | // TRD : we dequeue the numbers 0 to 9 50 | printf( "number = %d\n", temp_td->number ); 51 | } 52 | 53 | lfds711_queue_umm_cleanup( &qs, NULL ); 54 | 55 | free( td ); 56 | 57 | return( EXIT_SUCCESS ); 58 | } 59 | 60 | int use_queue_umm_h_main() { // use our wrapper "queue_umm.h" 61 | int ok; 62 | void* value; 63 | 64 | c_queue_umm* queue = queue_umm_new(8); 65 | for (int i = 5; i--> -5; ) { // test both positive, and negative int 66 | value = (void*)((size_t)i); // as unsigned is ok 67 | queue_umm_push(queue, value); // make sure it's the var `value` holds the value we want to push in! 68 | } 69 | 70 | for (int i = 10; i--> 0; ) { 71 | void *j = queue_umm_pop(queue, &ok); 72 | printf( "pop = %d\n", (int)((size_t)j)); 73 | } 74 | queue_umm_destroy(queue); 75 | 76 | return( EXIT_SUCCESS ); 77 | } 78 | 79 | int main() { 80 | use_liblfds711_h_main(); 81 | use_queue_umm_h_main(); 82 | return( EXIT_SUCCESS ); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /source/liblfdsd.dpp: -------------------------------------------------------------------------------- 1 | #include "liblfdsd.h" 2 | 3 | // import core.memory; do NOT import this, we intentionally do NOT want any GC function to compile! ref:do_NOT_GC.removeRoot 4 | import core.thread; 5 | 6 | import std.array; 7 | import std.traits; 8 | import std.stdio; 9 | import std.typecons; 10 | import std.datetime.stopwatch; 11 | import std.concurrency : receiveOnly, send, spawn, Tid, thisTid; 12 | 13 | alias voidpp = void**; 14 | extern(C) alias element_cleanup_callback = void function(lfds711_queue_bmm_state*, void*, void*); 15 | immutable voidpp NULL_PTR = null; 16 | immutable element_cleanup_callback NULL_CB = null; 17 | 18 | unittest { 19 | import std.traits; 20 | 21 | class C; 22 | union U; 23 | struct S; 24 | interface I; 25 | 26 | static assert( isAggregateType!C); 27 | static assert( isAggregateType!U); 28 | static assert( isAggregateType!S); 29 | static assert( isAggregateType!I); 30 | static assert(!isAggregateType!void); 31 | static assert(isArray!string); 32 | static assert(isArray!(int[])); 33 | static assert(!isAggregateType!(C[string])); 34 | static assert(!isAggregateType!(void delegate(int))); 35 | 36 | string s = "hello"; 37 | assert(false == __traits(isRef, s)); 38 | 39 | int[] arr = [1,2,3]; 40 | assert(false == __traits(isRef, arr)); 41 | } 42 | 43 | 44 | bool treatAsStructInC(T)() { 45 | // https://dlang.org/library/std/traits/is_aggregate_type.html 46 | // string, array is not isAggregateType 47 | static if (is(T == struct) || is(T == union) || isArray!T) { 48 | return true; 49 | } else { 50 | // basic type int|double will be treated as it is 51 | // class, interface are treated as pointers in C 52 | return false; 53 | } 54 | } 55 | 56 | enum queue_bmm_decl = q{ 57 | 58 | // this class is shared: otherwise, please use a normal queue 59 | // T: could be stuct*|class, string*|array*, int|double 60 | shared class queue_bmm(T) { 61 | public: 62 | 63 | alias PopT = T; 64 | static if (is(T == class) || is(T == interface) || isPointer!(T)) { // these are pointers in C 65 | immutable PopT invalidPop = null; 66 | } else { 67 | immutable PopT invalidPop = T.max; // 0 is typically used by user! TODO: remove this invalidPop, or at least doc it 68 | } 69 | 70 | this(size_t n=1024) { 71 | _queue = cast(shared)(queue_bmm_new(n)); // pointer from C is always global can be accessed by any threads 72 | capacity = n; 73 | } 74 | 75 | // return false, when the queue is full 76 | // the value must be simple scalar type, can be copied 77 | bool push(T value) { 78 | container_value_t v = cast(container_value_t)(cast(void*)value); 79 | return queue_bmm_push(cast(c_queue_bmm*)_queue, v); 80 | } 81 | 82 | // return invalidPop if queue empty 83 | // https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_bmm_dequeue#Return_Value 84 | // Returns 1 on a successful dequeue. Returns 0 if dequeing failed. Dequeuing only fails if the queue is empty. 85 | PopT pop() { 86 | int ok; 87 | container_value_t value = queue_bmm_pop(cast(c_queue_bmm*)_queue, &ok); 88 | if (ok) { 89 | PopT result = cast(PopT)(cast(void*)value); 90 | return result; 91 | } 92 | return cast(PopT)invalidPop; 93 | } 94 | 95 | bool isInvalid(ref PopT p) { 96 | return p is cast(PopT)invalidPop; 97 | } 98 | 99 | size_t length() { 100 | return queue_bmm_length(cast(c_queue_bmm*)_queue); 101 | } 102 | 103 | bool full() { 104 | return this.length() == capacity; 105 | } 106 | 107 | /* this bool is not guaranteed to be correct! 108 | https://www.liblfds.org/mediawiki/index.php?title=r7.1.1:Function_lfds711_queue_bmm_query#Notes 109 | */ 110 | bool empty() { 111 | return 0 == this.length(); 112 | } 113 | 114 | ~this() { 115 | queue_bmm_destroy(cast(c_queue_bmm*)_queue); 116 | } 117 | 118 | private: 119 | c_queue_bmm* _queue; 120 | size_t capacity; 121 | // PopT invalidPop; // signal invalid element, e.g. pop from empty queue 122 | } 123 | 124 | }; 125 | 126 | enum queue_bss_decl = queue_bmm_decl.replace("bmm", "bss"); 127 | enum queue_umm_decl = queue_bmm_decl.replace("bmm", "umm"); 128 | 129 | mixin(queue_bmm_decl); 130 | mixin(queue_bss_decl); 131 | mixin(queue_umm_decl); 132 | 133 | 134 | 135 | const size_t n = 100_000_000; 136 | /* 137 | alias SafeQueue = queue_bss; // speed=41701 msg/msec 138 | alias SafeQueue = queue_bmm; // speed=19988 msg/msec 139 | */ 140 | alias SafeQueue = queue_umm; // speed= 6215 msg/msec; is ~6x slower than queue_bss, because each push cause one lfds711_queue_umm_element aligned_alloc 141 | 142 | void threadProducer(shared(SafeQueue!int) queue) { 143 | ensure_lfds_valid_init_on_current_logical_core(); 144 | foreach (int i; 0..n) { 145 | for (;;) { 146 | if (queue.push(i)) {break;} 147 | Thread.yield(); 148 | } 149 | } 150 | } 151 | 152 | void threadConsumer(shared(SafeQueue!int) queue) { 153 | ensure_lfds_valid_init_on_current_logical_core(); 154 | StopWatch sw; 155 | sw.start(); 156 | long sum = 0; 157 | int p; 158 | 159 | foreach (i; 0..n) { 160 | for (;;) { 161 | p = queue.pop(); 162 | // if (queue.isInvalid(p)) {} // sync call on shared object is very expensive 163 | if (p != queue.invalidPop) { // empty may not be accurate 164 | break; 165 | } 166 | Thread.yield(); 167 | } 168 | sum += p; 169 | } 170 | 171 | sw.stop(); 172 | writefln("received %d messages in %d msec sum=%d speed=%d msg/msec", n, sw.peek().total!"msecs", sum, n/sw.peek().total!"msecs"); 173 | assert(sum == (n*(n-1)/2)); 174 | } 175 | 176 | unittest { 177 | // will push pop 100_000_000 int 178 | void testIntQueue() { 179 | auto queue = new shared(SafeQueue!int); 180 | 181 | writeln([int.init, int.max, queue.invalidPop]); 182 | spawn(&threadProducer, queue); 183 | spawn(&threadConsumer, queue); 184 | 185 | thread_joinAll(); 186 | } 187 | 188 | void testStringQueue() { 189 | string name = "string: Madge The Skutter"; 190 | string* temp_td = null; 191 | 192 | auto queue = new shared(SafeQueue!(string*))(2); 193 | ensure_lfds_valid_init_on_current_logical_core(); 194 | 195 | assert(0 == queue.length()); 196 | assert(queue.empty()); 197 | assert(!queue.full()); 198 | 199 | assert(name == "string: Madge The Skutter"); 200 | queue.push(&name); 201 | assert(1 == queue.length()); 202 | 203 | string full = "full"; 204 | queue.push(&full); // won't store string literal 205 | assert(2 == queue.length()); 206 | assert(queue.full()); 207 | 208 | name ~= "; change the pushed after push & before pop"; 209 | temp_td = queue.pop(); 210 | assert(1 == queue.length()); 211 | 212 | writeln(name); 213 | writeln(*temp_td); 214 | assert(name == "string: Madge The Skutter; change the pushed after push & before pop"); 215 | assert(*temp_td == "string: Madge The Skutter; change the pushed after push & before pop"); 216 | assert(temp_td == &name); // it is the name's address that we pushed! 217 | 218 | temp_td = queue.pop(); 219 | writeln(*temp_td); 220 | assert(0 == queue.length()); 221 | assert(*temp_td == "full"); 222 | assert(temp_td == &full); // it is the full's address that we pushed! 223 | } 224 | 225 | void testStructQueue() { 226 | struct test_data { 227 | string name; 228 | } 229 | 230 | test_data td; 231 | test_data* temp_td = null; 232 | td.name = "struct: Madge The Skutter"; 233 | 234 | auto queue = new shared(SafeQueue!(test_data*)); // TODO: shall we pass in test_data or test_data*? 235 | assert(td.name == "struct: Madge The Skutter"); 236 | queue.push(&td); 237 | td.name ~= "; after push & before pop"; 238 | temp_td = queue.pop(); 239 | assert(temp_td.name == "struct: Madge The Skutter; after push & before pop"); 240 | assert(temp_td == &td); // it is the address we pushed in! 241 | 242 | printf( "skutter name = %s %lu\n", &(temp_td.name[0]), voidpp.sizeof ); 243 | } 244 | 245 | void testClassQueue() { 246 | class test_data { 247 | string name; 248 | } 249 | 250 | test_data td = new test_data; 251 | test_data temp_td; 252 | td.name = "class: Madge The Skutter"; 253 | 254 | auto queue = new shared(SafeQueue!test_data); 255 | assert(td.name == "class: Madge The Skutter"); 256 | queue.push(td); 257 | td.name ~= "; after push & before pop"; 258 | temp_td = queue.pop(); 259 | assert(temp_td.name == "class: Madge The Skutter; after push & before pop"); 260 | 261 | assert(td is temp_td); 262 | assert(td == temp_td); 263 | 264 | printf( "skutter name = %s %lu\n", &(temp_td.name[0]), voidpp.sizeof ); 265 | } 266 | 267 | void main() { 268 | testStringQueue(); // TODO: revive for queue_bss; 269 | testStructQueue(); 270 | testClassQueue(); 271 | testIntQueue(); 272 | } 273 | 274 | main(); 275 | 276 | } 277 | 278 | version(LIBLFDS_TEST) { 279 | void main() {} 280 | } 281 | --------------------------------------------------------------------------------