DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
SpscQueue.h
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
46#include <array>
47#include <atomic>
48#include <cstddef>
49#include <type_traits>
50
51namespace dspark {
52
53// Cache-line size used for false-sharing padding. Fixed per architecture on
54// purpose: std::hardware_destructive_interference_size may vary with tuning
55// flags (GCC documents this and warns about it), and a header-only library
56// must never let the layout of the same template differ between translation
57// units. 128 on AArch64 covers Apple Silicon's 128-byte lines (over-padding
58// on 64-byte ARM cores is harmless); 64 everywhere else.
59#if defined(__aarch64__) || defined(_M_ARM64)
60 inline constexpr std::size_t kCacheLineSize = 128;
61#else
62 inline constexpr std::size_t kCacheLineSize = 64;
63#endif
64
65template <typename T, std::size_t Capacity = 32>
67{
68 static_assert(Capacity > 0 && (Capacity & (Capacity - 1)) == 0,
69 "SpscQueue: Capacity must be a power of two.");
70 static_assert(std::is_trivially_copyable_v<T>,
71 "SpscQueue: Element type must be trivially copyable for lock-free safety.");
72
73public:
75 SpscQueue() = default;
76
77 SpscQueue(const SpscQueue&) = delete;
78 SpscQueue& operator=(const SpscQueue&) = delete;
79
89 [[nodiscard]] bool push(const T& item) noexcept
90 {
91 const auto write = writePos_.load(std::memory_order_relaxed);
92 const auto next = (write + 1) & kMask;
93
94 // Check against cached read position first to avoid cross-core atomic load
95 if (next == cachedReadPos_)
96 {
97 cachedReadPos_ = readPos_.load(std::memory_order_acquire);
98 if (next == cachedReadPos_)
99 return false; // Truly full
100 }
101
102 buffer_[write] = item;
103 writePos_.store(next, std::memory_order_release);
104 return true;
105 }
106
116 [[nodiscard]] bool pop(T& item) noexcept
117 {
118 const auto read = readPos_.load(std::memory_order_relaxed);
119
120 // Check against cached write position first to avoid cross-core atomic load
121 if (read == cachedWritePos_)
122 {
123 cachedWritePos_ = writePos_.load(std::memory_order_acquire);
124 if (read == cachedWritePos_)
125 return false; // Truly empty
126 }
127
128 item = buffer_[read];
129 readPos_.store((read + 1) & kMask, std::memory_order_release);
130 return true;
131 }
132
140 [[nodiscard]] std::size_t sizeApprox() const noexcept
141 {
142 const auto w = writePos_.load(std::memory_order_relaxed);
143 const auto r = readPos_.load(std::memory_order_relaxed);
144 return (w - r) & kMask;
145 }
146
148 [[nodiscard]] bool empty() const noexcept { return sizeApprox() == 0; }
149
151 [[nodiscard]] static constexpr std::size_t capacity() noexcept { return Capacity - 1; }
152
153private:
154 static constexpr std::size_t kMask = Capacity - 1;
155
156 std::array<T, Capacity> buffer_ {};
157
158#ifdef _MSC_VER
159#pragma warning(push)
160#pragma warning(disable: 4324) // structure was padded due to alignment specifier
161#endif
162
163 // ========================================================================
164 // Producer Cache Line
165 // ========================================================================
166 alignas(kCacheLineSize) std::atomic<std::size_t> writePos_ {0};
167 std::size_t cachedReadPos_ {0}; // Only read/written by the producer
168
169 // ========================================================================
170 // Consumer Cache Line
171 // ========================================================================
172 alignas(kCacheLineSize) std::atomic<std::size_t> readPos_ {0};
173 std::size_t cachedWritePos_ {0}; // Only read/written by the consumer
174
175#ifdef _MSC_VER
176#pragma warning(pop)
177#endif
178};
179
180} // namespace dspark
std::size_t sizeApprox() const noexcept
Returns the approximate number of elements in the queue.
Definition SpscQueue.h:140
SpscQueue(const SpscQueue &)=delete
bool empty() const noexcept
Returns true if the queue appears empty (approximate).
Definition SpscQueue.h:148
SpscQueue & operator=(const SpscQueue &)=delete
static constexpr std::size_t capacity() noexcept
Usable capacity: one slot is reserved to tell full from empty.
Definition SpscQueue.h:151
SpscQueue()=default
Constructs an empty SPSC queue.
bool push(const T &item) noexcept
Pushes an element into the queue (producer side).
Definition SpscQueue.h:89
bool pop(T &item) noexcept
Pops an element from the queue (consumer side).
Definition SpscQueue.h:116
Main namespace for the DSPark framework.
constexpr std::size_t kCacheLineSize
Definition SpscQueue.h:62