DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
SpscQueue.h
Go to the documentation of this file.
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
37#include <array>
38#include <atomic>
39#include <cstddef>
40#include <type_traits>
41
42namespace dspark {
43
44// Cache-line size used for false-sharing padding. Fixed per architecture on
45// purpose: std::hardware_destructive_interference_size may vary with tuning
46// flags (GCC documents this and warns about it), and a header-only library
47// must never let the layout of the same template differ between translation
48// units. 128 on AArch64 covers Apple Silicon's 128-byte lines (over-padding
49// on 64-byte ARM cores is harmless); 64 everywhere else.
50#if defined(__aarch64__) || defined(_M_ARM64)
51 inline constexpr std::size_t kCacheLineSize = 128;
52#else
53 inline constexpr std::size_t kCacheLineSize = 64;
54#endif
55
56template <typename T, std::size_t Capacity = 32>
58{
59 static_assert(Capacity > 0 && (Capacity & (Capacity - 1)) == 0,
60 "SpscQueue: Capacity must be a power of two.");
61 static_assert(std::is_trivially_copyable_v<T>,
62 "SpscQueue: Element type must be trivially copyable for lock-free safety.");
63
64public:
66 SpscQueue() = default;
67
68 SpscQueue(const SpscQueue&) = delete;
69 SpscQueue& operator=(const SpscQueue&) = delete;
70
80 [[nodiscard]] bool push(const T& item) noexcept
81 {
82 const auto write = writePos_.load(std::memory_order_relaxed);
83 const auto next = (write + 1) & kMask;
84
85 // Check against cached read position first to avoid cross-core atomic load
86 if (next == cachedReadPos_)
87 {
88 cachedReadPos_ = readPos_.load(std::memory_order_acquire);
89 if (next == cachedReadPos_)
90 return false; // Truly full
91 }
92
93 buffer_[write] = item;
94 writePos_.store(next, std::memory_order_release);
95 return true;
96 }
97
107 [[nodiscard]] bool pop(T& item) noexcept
108 {
109 const auto read = readPos_.load(std::memory_order_relaxed);
110
111 // Check against cached write position first to avoid cross-core atomic load
112 if (read == cachedWritePos_)
113 {
114 cachedWritePos_ = writePos_.load(std::memory_order_acquire);
115 if (read == cachedWritePos_)
116 return false; // Truly empty
117 }
118
119 item = buffer_[read];
120 readPos_.store((read + 1) & kMask, std::memory_order_release);
121 return true;
122 }
123
131 [[nodiscard]] std::size_t sizeApprox() const noexcept
132 {
133 const auto w = writePos_.load(std::memory_order_relaxed);
134 const auto r = readPos_.load(std::memory_order_relaxed);
135 return (w - r) & kMask;
136 }
137
139 [[nodiscard]] bool empty() const noexcept { return sizeApprox() == 0; }
140
142 [[nodiscard]] static constexpr std::size_t capacity() noexcept { return Capacity - 1; }
143
144private:
145 static constexpr std::size_t kMask = Capacity - 1;
146
147 std::array<T, Capacity> buffer_ {};
148
149#ifdef _MSC_VER
150#pragma warning(push)
151#pragma warning(disable: 4324) // structure was padded due to alignment specifier
152#endif
153
154 // ========================================================================
155 // Producer Cache Line
156 // ========================================================================
157 alignas(kCacheLineSize) std::atomic<std::size_t> writePos_ {0};
158 std::size_t cachedReadPos_ {0}; // Only read/written by the producer
159
160 // ========================================================================
161 // Consumer Cache Line
162 // ========================================================================
163 alignas(kCacheLineSize) std::atomic<std::size_t> readPos_ {0};
164 std::size_t cachedWritePos_ {0}; // Only read/written by the consumer
165
166#ifdef _MSC_VER
167#pragma warning(pop)
168#endif
169};
170
171} // namespace dspark
std::size_t sizeApprox() const noexcept
Returns the approximate number of elements in the queue.
Definition SpscQueue.h:131
SpscQueue(const SpscQueue &)=delete
bool empty() const noexcept
Returns true if the queue appears empty (approximate).
Definition SpscQueue.h:139
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:142
SpscQueue()=default
Constructs an empty SPSC queue.
bool push(const T &item) noexcept
Pushes an element into the queue (producer side).
Definition SpscQueue.h:80
bool pop(T &item) noexcept
Pops an element from the queue (consumer side).
Definition SpscQueue.h:107
Main namespace for the DSPark framework.
constexpr std::size_t kCacheLineSize
Definition SpscQueue.h:53