2021-11-14 17:27:25 +00:00
|
|
|
/*
|
|
|
|
Copyright (c) 2020 Erik Rigtorp <erik@rigtorp.se>
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
|
|
in the Software without restriction, including without limitation the rights
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
|
|
copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <atomic>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cstddef>
|
|
|
|
#include <new> // std::hardware_destructive_interference_size
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <type_traits> // std::enable_if, std::is_*_constructible
|
|
|
|
|
2021-11-14 17:42:07 +00:00
|
|
|
#include "../common/TracyAlloc.hpp"
|
2021-11-14 17:27:25 +00:00
|
|
|
|
2021-11-14 17:42:07 +00:00
|
|
|
namespace tracy {
|
2021-11-14 17:27:25 +00:00
|
|
|
|
2021-11-14 17:42:07 +00:00
|
|
|
template <typename T> class SPSCQueue {
|
2021-11-14 17:27:25 +00:00
|
|
|
public:
|
2021-11-14 17:42:07 +00:00
|
|
|
explicit SPSCQueue(const size_t capacity)
|
|
|
|
: capacity_(capacity) {
|
2021-11-14 17:27:25 +00:00
|
|
|
// The queue needs at least one element
|
|
|
|
if (capacity_ < 1) {
|
|
|
|
capacity_ = 1;
|
|
|
|
}
|
|
|
|
capacity_++; // Needs one slack element
|
|
|
|
// Prevent overflowing size_t
|
|
|
|
if (capacity_ > SIZE_MAX - 2 * kPadding) {
|
|
|
|
capacity_ = SIZE_MAX - 2 * kPadding;
|
|
|
|
}
|
|
|
|
|
2021-11-14 17:42:07 +00:00
|
|
|
slots_ = (T*)tracy_malloc(sizeof(T) * (capacity_ + 2 * kPadding));
|
2021-11-14 17:27:25 +00:00
|
|
|
|
|
|
|
static_assert(alignof(SPSCQueue<T>) == kCacheLineSize, "");
|
|
|
|
static_assert(sizeof(SPSCQueue<T>) >= 3 * kCacheLineSize, "");
|
|
|
|
assert(reinterpret_cast<char *>(&readIdx_) -
|
|
|
|
reinterpret_cast<char *>(&writeIdx_) >=
|
|
|
|
static_cast<std::ptrdiff_t>(kCacheLineSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
~SPSCQueue() {
|
|
|
|
while (front()) {
|
|
|
|
pop();
|
|
|
|
}
|
2021-11-14 17:42:07 +00:00
|
|
|
tracy_free(slots_);
|
2021-11-14 17:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// non-copyable and non-movable
|
|
|
|
SPSCQueue(const SPSCQueue &) = delete;
|
|
|
|
SPSCQueue &operator=(const SPSCQueue &) = delete;
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
void emplace(Args &&...args) noexcept(
|
|
|
|
std::is_nothrow_constructible<T, Args &&...>::value) {
|
|
|
|
static_assert(std::is_constructible<T, Args &&...>::value,
|
|
|
|
"T must be constructible with Args&&...");
|
|
|
|
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
|
|
|
auto nextWriteIdx = writeIdx + 1;
|
|
|
|
if (nextWriteIdx == capacity_) {
|
|
|
|
nextWriteIdx = 0;
|
|
|
|
}
|
|
|
|
while (nextWriteIdx == readIdxCache_) {
|
|
|
|
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
|
|
|
}
|
|
|
|
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
|
|
|
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
bool try_emplace(Args &&...args) noexcept(
|
|
|
|
std::is_nothrow_constructible<T, Args &&...>::value) {
|
|
|
|
static_assert(std::is_constructible<T, Args &&...>::value,
|
|
|
|
"T must be constructible with Args&&...");
|
|
|
|
auto const writeIdx = writeIdx_.load(std::memory_order_relaxed);
|
|
|
|
auto nextWriteIdx = writeIdx + 1;
|
|
|
|
if (nextWriteIdx == capacity_) {
|
|
|
|
nextWriteIdx = 0;
|
|
|
|
}
|
|
|
|
if (nextWriteIdx == readIdxCache_) {
|
|
|
|
readIdxCache_ = readIdx_.load(std::memory_order_acquire);
|
|
|
|
if (nextWriteIdx == readIdxCache_) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
new (&slots_[writeIdx + kPadding]) T(std::forward<Args>(args)...);
|
|
|
|
writeIdx_.store(nextWriteIdx, std::memory_order_release);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
|
|
|
static_assert(std::is_copy_constructible<T>::value,
|
|
|
|
"T must be copy constructible");
|
|
|
|
emplace(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename P, typename = typename std::enable_if<
|
|
|
|
std::is_constructible<T, P &&>::value>::type>
|
|
|
|
void push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
|
|
|
emplace(std::forward<P>(v));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
try_push(const T &v) noexcept(std::is_nothrow_copy_constructible<T>::value) {
|
|
|
|
static_assert(std::is_copy_constructible<T>::value,
|
|
|
|
"T must be copy constructible");
|
|
|
|
return try_emplace(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename P, typename = typename std::enable_if<
|
|
|
|
std::is_constructible<T, P &&>::value>::type>
|
|
|
|
bool try_push(P &&v) noexcept(std::is_nothrow_constructible<T, P &&>::value) {
|
|
|
|
return try_emplace(std::forward<P>(v));
|
|
|
|
}
|
|
|
|
|
|
|
|
T *front() noexcept {
|
|
|
|
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
|
|
|
if (readIdx == writeIdxCache_) {
|
|
|
|
writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
|
|
|
|
if (writeIdxCache_ == readIdx) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &slots_[readIdx + kPadding];
|
|
|
|
}
|
|
|
|
|
|
|
|
void pop() noexcept {
|
|
|
|
static_assert(std::is_nothrow_destructible<T>::value,
|
|
|
|
"T must be nothrow destructible");
|
|
|
|
auto const readIdx = readIdx_.load(std::memory_order_relaxed);
|
|
|
|
assert(writeIdx_.load(std::memory_order_acquire) != readIdx);
|
|
|
|
slots_[readIdx + kPadding].~T();
|
|
|
|
auto nextReadIdx = readIdx + 1;
|
|
|
|
if (nextReadIdx == capacity_) {
|
|
|
|
nextReadIdx = 0;
|
|
|
|
}
|
|
|
|
readIdx_.store(nextReadIdx, std::memory_order_release);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t size() const noexcept {
|
|
|
|
std::ptrdiff_t diff = writeIdx_.load(std::memory_order_acquire) -
|
|
|
|
readIdx_.load(std::memory_order_acquire);
|
|
|
|
if (diff < 0) {
|
|
|
|
diff += capacity_;
|
|
|
|
}
|
|
|
|
return static_cast<size_t>(diff);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool empty() const noexcept { return size() == 0; }
|
|
|
|
|
|
|
|
size_t capacity() const noexcept { return capacity_ - 1; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
#ifdef __cpp_lib_hardware_interference_size
|
|
|
|
static constexpr size_t kCacheLineSize =
|
|
|
|
std::hardware_destructive_interference_size;
|
|
|
|
#else
|
|
|
|
static constexpr size_t kCacheLineSize = 64;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Padding to avoid false sharing between slots_ and adjacent allocations
|
|
|
|
static constexpr size_t kPadding = (kCacheLineSize - 1) / sizeof(T) + 1;
|
|
|
|
|
|
|
|
private:
|
|
|
|
size_t capacity_;
|
|
|
|
T *slots_;
|
|
|
|
|
|
|
|
// Align to cache line size in order to avoid false sharing
|
|
|
|
// readIdxCache_ and writeIdxCache_ is used to reduce the amount of cache
|
|
|
|
// coherency traffic
|
|
|
|
alignas(kCacheLineSize) std::atomic<size_t> writeIdx_ = {0};
|
|
|
|
alignas(kCacheLineSize) size_t readIdxCache_ = 0;
|
|
|
|
alignas(kCacheLineSize) std::atomic<size_t> readIdx_ = {0};
|
|
|
|
alignas(kCacheLineSize) size_t writeIdxCache_ = 0;
|
|
|
|
|
|
|
|
// Padding to avoid adjacent allocations to share cache line with
|
|
|
|
// writeIdxCache_
|
|
|
|
char padding_[kCacheLineSize - sizeof(writeIdxCache_)];
|
|
|
|
};
|
|
|
|
} // namespace rigtorp
|