Corium 1.1.0
High-Performance Zero-Heap C++20 MPSC Application Runtime
Loading...
Searching...
No Matches
MpscRingBuffer.hpp
Go to the documentation of this file.
1
7#pragma once
8
9#include <array>
10#include <atomic>
11#include <cstddef>
12#include <cstdint>
13#include <new>
14#include <utility>
15
16#if !defined(CORIUM_CACHE_LINE_SIZE)
17#if defined(CORIUM_COMPACT_MEMORY) || defined(CORIUM_DISABLE_CACHE_ALIGNMENT)
18#define CORIUM_CACHE_LINE_SIZE alignof(std::max_align_t)
19#else
20#define CORIUM_CACHE_LINE_SIZE 64
21#endif
22#endif
23
24namespace corium {
25
32template <typename T, std::size_t Capacity>
34 static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of 2.");
35
36 struct alignas(CORIUM_CACHE_LINE_SIZE) Cell {
37 std::atomic<std::size_t> sequence;
38 alignas(alignof(T)) std::byte storage[sizeof(T)];
39
40 template <typename... Args>
41 void construct(Args&&... args) {
42 new (static_cast<void*>(storage)) T(std::forward<Args>(args)...);
43 }
44
45 [[nodiscard]] T& value() noexcept {
46 return *std::launder(reinterpret_cast<T*>(storage));
47 }
48
49 [[nodiscard]] const T& value() const noexcept {
50 return *std::launder(reinterpret_cast<const T*>(storage));
51 }
52
53 void destroy() noexcept {
54 value().~T();
55 }
56 };
57
58public:
59 struct PushResult {
60 bool pushed;
62 };
63
65 for (std::size_t i = 0; i < Capacity; ++i) {
66 _buffer[i].sequence.store(i, std::memory_order_relaxed);
67 }
68 _enqueuePos.store(0, std::memory_order_relaxed);
69 _dequeuePos.store(0, std::memory_order_relaxed);
70 }
71
73 T dummy;
74 while (tryPop(dummy)) {}
75 }
76
79
82
85 template <typename... Args>
86 PushResult tryPush(Args&&... args) {
87 Cell* cell = nullptr;
88 std::size_t pos = _enqueuePos.load(std::memory_order_relaxed);
89
90 for (;;) {
91 cell = &_buffer[pos & Mask];
92 std::size_t seq = cell->sequence.load(std::memory_order_acquire);
93 intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos);
94
95 if (diff == 0) {
96 if (_enqueuePos.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) {
97 break; // Reserved cell
98 }
99 } else if (diff < 0) {
100 return {false, false}; // Full
101 } else {
102 pos = _enqueuePos.load(std::memory_order_relaxed);
103 }
104 }
105
106 cell->construct(std::forward<Args>(args)...);
107 cell->sequence.store(pos + 1, std::memory_order_release);
108
109 const bool wasEmpty = (pos == _dequeuePos.load(std::memory_order_relaxed));
110 return {true, wasEmpty};
111 }
112
115 bool tryPop(T& result) {
116 Cell* cell = nullptr;
117 std::size_t pos = _dequeuePos.load(std::memory_order_relaxed);
118
119 cell = &_buffer[pos & Mask];
120 std::size_t seq = cell->sequence.load(std::memory_order_acquire);
121 intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos + 1);
122
123 if (diff == 0) {
124 _dequeuePos.store(pos + 1, std::memory_order_relaxed);
125 result = std::move(cell->value());
126 cell->destroy();
127 cell->sequence.store(pos + Mask + 1, std::memory_order_release);
128 return true;
129 }
130
131 return false;
132 }
133
135 [[nodiscard]] bool empty() const noexcept {
136 const std::size_t pos = _dequeuePos.load(std::memory_order_relaxed);
137 const Cell* cell = &_buffer[pos & Mask];
138 const std::size_t seq = cell->sequence.load(std::memory_order_acquire);
139 const intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos + 1);
140 return diff != 0;
141 }
142
144 [[nodiscard]] static constexpr std::size_t capacity() noexcept {
145 return Capacity;
146 }
147
148private:
149 static constexpr std::size_t Mask = Capacity - 1;
150
151 alignas(CORIUM_CACHE_LINE_SIZE) std::array<Cell, Capacity> _buffer;
152 alignas(CORIUM_CACHE_LINE_SIZE) std::atomic<std::size_t> _enqueuePos;
153 alignas(CORIUM_CACHE_LINE_SIZE) std::atomic<std::size_t> _dequeuePos;
154};
155
156namespace internal {
157 template <typename T, std::size_t Capacity>
159} // namespace internal
160
161} // namespace corium
#define CORIUM_CACHE_LINE_SIZE
Definition MpscRingBuffer.hpp:20
Lock-free Multiple-Producer, Single-Consumer (MPSC) RingBuffer. Implements Dmitry Vyukov's algorithm ...
Definition MpscRingBuffer.hpp:33
bool tryPop(T &result)
Pop an item from the queue (Single-Consumer only).
Definition MpscRingBuffer.hpp:115
~MpscRingBuffer()
Definition MpscRingBuffer.hpp:72
MpscRingBuffer(const MpscRingBuffer &)=delete
MpscRingBuffer & operator=(MpscRingBuffer &&)=delete
PushResult tryPush(Args &&... args)
Push an item into the queue (Multi-Producer thread safe).
Definition MpscRingBuffer.hpp:86
MpscRingBuffer()
Definition MpscRingBuffer.hpp:64
MpscRingBuffer(MpscRingBuffer &&)=delete
bool empty() const noexcept
Check if the queue is empty (approximate if concurrent producers are active).
Definition MpscRingBuffer.hpp:135
MpscRingBuffer & operator=(const MpscRingBuffer &)=delete
static constexpr std::size_t capacity() noexcept
Return fixed capacity of this ring buffer.
Definition MpscRingBuffer.hpp:144
Definition Application.hpp:16
Definition MpscRingBuffer.hpp:59
bool wasEmpty
Definition MpscRingBuffer.hpp:61
bool pushed
Definition MpscRingBuffer.hpp:60