25#ifndef JOIN_CORE_QUEUE_HPP
26#define JOIN_CORE_QUEUE_HPP
50 static constexpr uint64_t
MAGIC = 0x9F7E3B2A8D5C4E1B;
53 alignas (64) std::atomic_uint64_t
_magic;
56 alignas (64) std::atomic_uint64_t
_head;
62 alignas (64) std::atomic_uint64_t
_tail;
71 template <
typename Type>
81 template <
typename Type>
85 alignas (64) std::atomic_uint64_t
_seq;
91 char _padding[(64 - ((
sizeof (std::atomic_uint64_t) +
sizeof (Type)) % 64)) % 64];
97 template <
typename Type,
typename Slot>
108 template <
typename Type,
typename Backend>
114 template <
typename SyncPolicy>
120 template <
typename Type,
typename Backend>
126 template <
typename Type,
typename Backend,
typename SyncPolicy>
129 static_assert (std::is_trivially_copyable_v<Type>,
"type must be trivially copyable");
130 static_assert (std::is_trivially_destructible_v<Type>,
"type must be trivially destructible");
142 template <
typename... Args>
145 , _mask (_capacity - 1)
146 , _elementSize (sizeof (
Slot))
147 , _totalSize (sizeof (
QueueSync) + (_capacity * _elementSize))
148 , _backend (_totalSize,
std::forward<Args> (args)...)
149 , _segment (static_cast<
Segment*> (_backend.get ()))
151 uint64_t expected = 0;
153 if (_segment->
_sync.
_magic.compare_exchange_strong (expected, 0xFFFFFFFFFFFFFFFF,
154 std::memory_order_acq_rel))
156 _segment->_sync._head.store (0, std::memory_order_relaxed);
157 _segment->_sync._cachedTail = 0;
158 _segment->_sync._tail.store (0, std::memory_order_relaxed);
159 _segment->_sync._cachedHead = 0;
163 _segment->_sync._magic.store (QueueSync::MAGIC, std::memory_order_release);
168 while (_segment->_sync._magic.load (std::memory_order_acquire) != QueueSync::MAGIC)
211 int tryPush (const Type& element) noexcept
213 return SyncPolicy::tryPush (_segment, element, _capacity, _mask);
222 ssize_t
tryPush (
const Type* elements,
size_t size)
noexcept
224 return SyncPolicy::tryPush (_segment, elements, size, _capacity, _mask);
232 int push (
const Type& element)
noexcept
236 while (tryPush (element) == -1)
255 int push (
const Type* elements,
size_t size)
noexcept
260 while (pushed < size)
262 ssize_t n = tryPush (elements + pushed, size - pushed);
274 pushed +=
static_cast<uint64_t
> (n);
288 return SyncPolicy::tryPop (_segment, element, _capacity, _mask);
297 ssize_t
tryPop (Type* elements,
size_t size)
noexcept
299 return SyncPolicy::tryPop (_segment, elements, size, _capacity, _mask);
307 int pop (Type& element)
noexcept
311 while (tryPop (element) == -1)
330 int pop (Type* elements,
size_t size)
noexcept
335 while (popped < size)
337 ssize_t n = tryPop (elements + popped, size - popped);
349 popped +=
static_cast<uint64_t
> (n);
366 auto head = _segment->_sync._head.load (std::memory_order_acquire);
367 auto tail = _segment->_sync._tail.load (std::memory_order_relaxed);
381 return _capacity - pending ();
394 return pending () == _capacity;
407 return pending () == 0;
416 int mbind (
int numa)
const noexcept
418 return _backend.mbind (numa);
428 return _backend.mlock ();
435 void initSlots () noexcept
439 for (uint64_t i = 0; i < _capacity; ++i)
441 _segment->_elements[i]._seq.store (i, std::memory_order_relaxed);
447 alignas (64)
const uint64_t _capacity = 0;
450 const uint64_t _mask = 0;
453 const uint64_t _elementSize = 0;
456 const uint64_t _totalSize = 0;
462 Segment* _segment =
nullptr;
468 template <
typename Type,
typename Backend>
482 static int tryPush (
Segment* segment,
const Type& element, uint64_t capacity, uint64_t mask)
noexcept
492 auto& sync = segment->_sync;
493 uint64_t head = sync._head.load (std::memory_order_relaxed);
497 sync._cachedTail = sync._tail.load (std::memory_order_acquire);
498 if ((head - sync._cachedTail) == capacity)
505 segment->_elements[head & mask].data = element;
506 sync._head.store (head + 1, std::memory_order_release);
520 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
521 uint64_t mask)
noexcept
523 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
529 auto& sync = segment->_sync;
530 uint64_t head = sync._head.load (std::memory_order_relaxed);
531 uint64_t avail = capacity - (head - sync._cachedTail);
535 sync._cachedTail = sync._tail.load (std::memory_order_acquire);
536 avail = capacity - (head - sync._cachedTail);
544 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), avail);
545 uint64_t offset = head & mask;
546 uint64_t first = std::min (toWrite, capacity - offset);
548 std::memcpy (&segment->_elements[offset], elements, first * sizeof (Type));
552 std::memcpy (&segment->_elements[0], elements + first, (toWrite - first) * sizeof (Type));
555 sync._head.store (head + toWrite, std::memory_order_release);
557 return static_cast<ssize_t
> (toWrite);
568 static int tryPop (
Segment* segment, Type& element, uint64_t , uint64_t mask)
noexcept
578 auto& sync = segment->_sync;
579 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
581 if (sync._cachedHead == tail)
583 sync._cachedHead = sync._head.load (std::memory_order_acquire);
584 if (sync._cachedHead == tail)
591 element = segment->_elements[tail & mask].data;
592 sync._tail.store (tail + 1, std::memory_order_release);
606 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
608 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
614 auto& sync = segment->_sync;
615 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
616 uint64_t pending = sync._cachedHead - tail;
620 sync._cachedHead = sync._head.load (std::memory_order_acquire);
621 pending = sync._cachedHead - tail;
629 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), pending);
630 uint64_t offset = tail & mask;
631 uint64_t first = std::min (toRead, capacity - offset);
633 std::memcpy (elements, &segment->_elements[offset], first * sizeof (Type));
637 std::memcpy (elements + first, &segment->_elements[0], (toRead - first) * sizeof (Type));
640 sync._tail.store (tail + toRead, std::memory_order_release);
642 return static_cast<ssize_t
> (toRead);
649 template <
typename Type,
typename Backend>
663 static int tryPush (
Segment* segment,
const Type& element, uint64_t , uint64_t mask)
noexcept
673 auto& sync = segment->_sync;
674 uint64_t head = sync._head.load (std::memory_order_relaxed);
679 auto* slot = &segment->_elements[head & mask];
680 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
684 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + 1, std::memory_order_acquire,
685 std::memory_order_relaxed)))
687 slot->data = element;
688 slot->_seq.store (head + 1, std::memory_order_release);
700 head = sync._head.load (std::memory_order_relaxed);
714 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
715 uint64_t mask)
noexcept
717 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
724 auto& sync = segment->_sync;
725 uint64_t head = sync._head.load (std::memory_order_relaxed);
729 uint64_t tail = sync._tail.load (std::memory_order_acquire);
730 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), capacity - (head - tail));
738 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + toWrite, std::memory_order_acquire,
739 std::memory_order_relaxed)))
741 for (uint64_t i = 0; i < toWrite; ++i)
743 auto* slot = &segment->_elements[(head + i) & mask];
744 slot->data = elements[i];
745 slot->_seq.store (head + i + 1, std::memory_order_release);
748 return static_cast<ssize_t
> (toWrite);
763 static int tryPop (
Segment* segment, Type& element, uint64_t capacity, uint64_t mask)
noexcept
773 auto& sync = segment->_sync;
774 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
775 auto* slot = &segment->_elements[tail & mask];
776 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
784 element = slot->data;
785 slot->_seq.store (tail + capacity, std::memory_order_release);
786 sync._tail.store (tail + 1, std::memory_order_release);
800 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
802 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
808 auto& sync = segment->_sync;
809 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
810 uint64_t head = sync._head.load (std::memory_order_acquire);
811 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), head - tail);
814 for (uint64_t i = 0; i < toRead; ++i)
816 auto* slot = &segment->_elements[(tail + i) & mask];
818 if (
JOIN_UNLIKELY (slot->_seq.load (std::memory_order_acquire) != tail + i + 1))
823 elements[i] = slot->data;
824 slot->_seq.store (tail + i + capacity, std::memory_order_release);
830 sync._tail.store (tail + popped, std::memory_order_release);
831 return static_cast<ssize_t
> (popped);
842 template <
typename Type,
typename Backend>
856 static int tryPush (
Segment* segment,
const Type& element, uint64_t capacity, uint64_t mask)
noexcept
870 static ssize_t
tryPush (
Segment* segment,
const Type* elements,
size_t size, uint64_t capacity,
871 uint64_t mask)
noexcept
873 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
880 auto& sync = segment->_sync;
881 uint64_t head = sync._head.load (std::memory_order_relaxed);
885 uint64_t tail = sync._tail.load (std::memory_order_acquire);
886 uint64_t toWrite = std::min (
static_cast<uint64_t
> (size), capacity - (head - tail));
894 if (
JOIN_LIKELY (sync._head.compare_exchange_weak (head, head + toWrite, std::memory_order_acquire,
895 std::memory_order_relaxed)))
897 for (uint64_t i = 0; i < toWrite; ++i)
899 auto* slot = &segment->_elements[(head + i) & mask];
901 while (slot->_seq.load (std::memory_order_acquire) != head + i)
905 slot->data = elements[i];
906 slot->_seq.store (head + i + 1, std::memory_order_release);
909 return static_cast<ssize_t
> (toWrite);
924 static int tryPop (
Segment* segment, Type& element, uint64_t capacity, uint64_t mask)
noexcept
934 auto& sync = segment->_sync;
935 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
940 auto* slot = &segment->_elements[tail & mask];
941 uint64_t seq = slot->_seq.load (std::memory_order_acquire);
943 if (seq == (tail + 1))
945 if (
JOIN_LIKELY (sync._tail.compare_exchange_weak (tail, tail + 1, std::memory_order_acquire,
946 std::memory_order_relaxed)))
948 element = slot->data;
949 slot->_seq.store (tail + capacity, std::memory_order_release);
961 tail = sync._tail.load (std::memory_order_relaxed);
975 static ssize_t
tryPop (
Segment* segment, Type* elements,
size_t size, uint64_t capacity, uint64_t mask)
noexcept
977 if (
JOIN_UNLIKELY (segment ==
nullptr || elements ==
nullptr || size == 0))
984 auto& sync = segment->_sync;
985 uint64_t tail = sync._tail.load (std::memory_order_relaxed);
989 uint64_t head = sync._head.load (std::memory_order_acquire);
990 uint64_t toRead = std::min (
static_cast<uint64_t
> (size), head - tail);
999 for (; ready < toRead; ++ready)
1001 auto* slot = &segment->_elements[(tail + ready) & mask];
1002 if (
JOIN_UNLIKELY (slot->_seq.load (std::memory_order_acquire) != tail + ready + 1))
1006 elements[ready] = slot->data;
1015 if (
JOIN_LIKELY (sync._tail.compare_exchange_weak (tail, tail + ready, std::memory_order_acquire,
1016 std::memory_order_relaxed)))
1018 for (uint64_t i = 0; i < ready; ++i)
1020 segment->_elements[(tail + i) & mask]._seq.store (tail + i + capacity,
1021 std::memory_order_release);
1024 return static_cast<ssize_t
> (ready);
1035 template <
typename Backend,
template <
typename,
typename>
class SyncPolicy>
1039 template <
typename Type>
adaptive backoff for busy-wait loops, requires Rdtsc::calibrate ().
Definition backoff.hpp:47
queue base class.
Definition queue.hpp:128
int push(const Type *elements, size_t size) noexcept
push multiple elements into the ring buffer.
Definition queue.hpp:255
BasicQueue(uint64_t capacity, Args &&... args)
create instance.
Definition queue.hpp:143
std::conditional_t< needs_seq_v< SyncPolicy >, QueueSlotFull< Type >, QueueSlotLight< Type > > Slot
Definition queue.hpp:134
int push(const Type &element) noexcept
push element into the ring buffer.
Definition queue.hpp:232
int mlock() const noexcept
lock memory in RAM.
Definition queue.hpp:426
ssize_t tryPop(Type *elements, size_t size) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:297
int tryPop(Type &element) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:286
~BasicQueue() noexcept=default
destroy queue instance.
int pop(Type *elements, size_t size) noexcept
pop multiple elements from the ring buffer.
Definition queue.hpp:330
BasicQueue(BasicQueue &&other)=delete
move constructor.
int pop(Type &element) noexcept
pop element from the ring buffer.
Definition queue.hpp:307
bool full() const noexcept
check if the ring buffer is full.
Definition queue.hpp:388
ssize_t tryPush(const Type *elements, size_t size) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:222
bool empty() const noexcept
check if the ring buffer is empty.
Definition queue.hpp:401
uint64_t pending() const noexcept
get the number of pending elements for reading.
Definition queue.hpp:360
uint64_t available() const noexcept
get the number of available slots for writing.
Definition queue.hpp:375
Type ValueType
Definition queue.hpp:133
BasicQueue(const BasicQueue &other)=delete
copy constructor.
Definition acceptor.hpp:32
constexpr bool needs_seq_v
primary trait: all sync policies need sequence numbers by default.
Definition queue.hpp:115
constexpr uint64_t nextPow2(uint64_t value) noexcept
round up to the next power of two.
Definition utils.hpp:475
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
multiple producer multiple consumer ring buffer.
Definition queue.hpp:844
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:870
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:975
static int tryPush(Segment *segment, const Type &element, uint64_t capacity, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:856
static int tryPop(Segment *segment, Type &element, uint64_t capacity, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:924
multiple producer single consumer ring buffer.
Definition queue.hpp:651
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:800
static int tryPush(Segment *segment, const Type &element, uint64_t, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:663
static int tryPop(Segment *segment, Type &element, uint64_t capacity, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:763
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:714
queue memory segment.
Definition queue.hpp:99
Slot _elements[]
flexible array of queue slots.
Definition queue.hpp:104
QueueSync _sync
synchronization primitives.
Definition queue.hpp:101
full queue slot used by MPSC/MPMC.
Definition queue.hpp:83
Type data
stored element data.
Definition queue.hpp:88
std::atomic_uint64_t _seq
sequence number for synchronization.
Definition queue.hpp:85
char _padding[(64 -((sizeof(std::atomic_uint64_t)+sizeof(Type)) % 64)) % 64]
padding to prevent false sharing.
Definition queue.hpp:91
lightweight queue slot used by SPSC.
Definition queue.hpp:73
Type data
stored element data.
Definition queue.hpp:75
queue synchronization primitives.
Definition queue.hpp:48
std::atomic_uint64_t _head
write position.
Definition queue.hpp:56
uint64_t _cachedTail
cached tail index for the producer side.
Definition queue.hpp:59
uint64_t _cachedHead
cached head index for the consumer side.
Definition queue.hpp:65
static constexpr uint64_t MAGIC
magic number for initialization detection.
Definition queue.hpp:50
std::atomic_uint64_t _tail
read position.
Definition queue.hpp:62
std::atomic_uint64_t _magic
initialization state atomic.
Definition queue.hpp:53
single producer single consumer ring buffer.
Definition queue.hpp:470
static ssize_t tryPop(Segment *segment, Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to pop multiple elements from the ring buffer.
Definition queue.hpp:606
static ssize_t tryPush(Segment *segment, const Type *elements, size_t size, uint64_t capacity, uint64_t mask) noexcept
try to push multiple elements into the ring buffer.
Definition queue.hpp:520
static int tryPop(Segment *segment, Type &element, uint64_t, uint64_t mask) noexcept
try to pop element from the ring buffer.
Definition queue.hpp:568
static int tryPush(Segment *segment, const Type &element, uint64_t capacity, uint64_t mask) noexcept
try to push element into the ring buffer.
Definition queue.hpp:482
queue forward declarations.
Definition queue.hpp:1037
#define JOIN_LIKELY(x)
Definition utils.hpp:50
#define JOIN_UNLIKELY(x)
Definition utils.hpp:51