#pragma once
#include "../common/TracyAlloc.hpp"
#include "../common/TracyForceInline.hpp"
#include "../common/TracySystem.hpp"
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif
#if defined(__APPLE__)
#include "TargetConditionals.h"
#endif
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <type_traits>
#include <algorithm>
#include <utility>
#include <limits>
#include <climits>
#include <array>
#include <thread>
namespace tracy
{
namespace moodycamel { namespace details {
#if defined(__GNUC__)
inline bool cqLikely(bool x) { return __builtin_expect((x), true); }
inline bool cqUnlikely(bool x) { return __builtin_expect((x), false); }
#else
inline bool cqLikely(bool x) { return x; }
inline bool cqUnlikely(bool x) { return x; }
#endif
} }
namespace
{
template <bool>
struct compile_time_condition
{
static const bool value = false;
};
template <>
struct compile_time_condition<true>
{
static const bool value = true;
};
}
namespace moodycamel {
namespace details {
template<typename T>
struct const_numeric_max {
static_assert(std::is_integral<T>::value, "const_numeric_max can only be used with integers");
static const T value = std::numeric_limits<T>::is_signed
? (static_cast<T>(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast<T>(1)
: static_cast<T>(-1);
};
#if defined(__GLIBCXX__)
typedef ::max_align_t std_max_align_t; #else
typedef std::max_align_t std_max_align_t; #endif
typedef union {
std_max_align_t x;
long long y;
void* z;
} max_align_t;
}
struct ConcurrentQueueDefaultTraits
{
typedef std::size_t size_t;
typedef std::size_t index_t;
static const size_t BLOCK_SIZE = 64*1024;
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32;
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32;
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256;
static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max<size_t>::value;
#if defined(malloc) || defined(free)
static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); }
static inline void WORKAROUND_free(void* ptr) { return free(ptr); }
static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); }
static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); }
#else
static inline void* malloc(size_t size) { return tracy::tracy_malloc(size); }
static inline void free(void* ptr) { return tracy::tracy_free(ptr); }
#endif
};
struct ProducerToken;
struct ConsumerToken;
template<typename T, typename Traits> class ConcurrentQueue;
namespace details
{
struct ConcurrentQueueProducerTypelessBase
{
ConcurrentQueueProducerTypelessBase* next;
std::atomic<bool> inactive;
ProducerToken* token;
uint32_t threadId;
ConcurrentQueueProducerTypelessBase()
: next(nullptr), inactive(false), token(nullptr), threadId(0)
{
}
};
template<typename T>
static inline bool circular_less_than(T a, T b)
{
static_assert(std::is_integral<T>::value && !std::numeric_limits<T>::is_signed, "circular_less_than is intended to be used only with unsigned integer types");
return static_cast<T>(a - b) > static_cast<T>(static_cast<T>(1) << (static_cast<T>(sizeof(T) * CHAR_BIT - 1)));
}
template<typename U>
static inline char* align_for(char* ptr)
{
const std::size_t alignment = std::alignment_of<U>::value;
return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
}
template<typename T>
static inline T ceil_to_pow_2(T x)
{
static_assert(std::is_integral<T>::value && !std::numeric_limits<T>::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types");
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
for (std::size_t i = 1; i < sizeof(T); i <<= 1) {
x |= x >> (i << 3);
}
++x;
return x;
}
template<typename T>
static inline void swap_relaxed(std::atomic<T>& left, std::atomic<T>& right)
{
T temp = std::move(left.load(std::memory_order_relaxed));
left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed);
right.store(std::move(temp), std::memory_order_relaxed);
}
template<typename T>
static inline T const& nomove(T const& x)
{
return x;
}
template<bool Enable>
struct nomove_if
{
template<typename T>
static inline T const& eval(T const& x)
{
return x;
}
};
template<>
struct nomove_if<false>
{
template<typename U>
static inline auto eval(U&& x)
-> decltype(std::forward<U>(x))
{
return std::forward<U>(x);
}
};
template<typename It>
static inline auto deref_noexcept(It& it) noexcept -> decltype(*it)
{
return *it;
}
#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
template<typename T> struct is_trivially_destructible : std::is_trivially_destructible<T> { };
#else
template<typename T> struct is_trivially_destructible : std::has_trivial_destructor<T> { };
#endif
template<typename T> struct static_is_lock_free_num { enum { value = 0 }; };
template<> struct static_is_lock_free_num<signed char> { enum { value = ATOMIC_CHAR_LOCK_FREE }; };
template<> struct static_is_lock_free_num<short> { enum { value = ATOMIC_SHORT_LOCK_FREE }; };
template<> struct static_is_lock_free_num<int> { enum { value = ATOMIC_INT_LOCK_FREE }; };
template<> struct static_is_lock_free_num<long> { enum { value = ATOMIC_LONG_LOCK_FREE }; };
template<> struct static_is_lock_free_num<long long> { enum { value = ATOMIC_LLONG_LOCK_FREE }; };
template<typename T> struct static_is_lock_free : static_is_lock_free_num<typename std::make_signed<T>::type> { };
template<> struct static_is_lock_free<bool> { enum { value = ATOMIC_BOOL_LOCK_FREE }; };
template<typename U> struct static_is_lock_free<U*> { enum { value = ATOMIC_POINTER_LOCK_FREE }; };
}
struct ProducerToken
{
template<typename T, typename Traits>
explicit ProducerToken(ConcurrentQueue<T, Traits>& queue);
ProducerToken(ProducerToken&& other) noexcept
: producer(other.producer)
{
other.producer = nullptr;
if (producer != nullptr) {
producer->token = this;
}
}
inline ProducerToken& operator=(ProducerToken&& other) noexcept
{
swap(other);
return *this;
}
void swap(ProducerToken& other) noexcept
{
std::swap(producer, other.producer);
if (producer != nullptr) {
producer->token = this;
}
if (other.producer != nullptr) {
other.producer->token = &other;
}
}
inline bool valid() const { return producer != nullptr; }
~ProducerToken()
{
if (producer != nullptr) {
producer->token = nullptr;
producer->inactive.store(true, std::memory_order_release);
}
}
ProducerToken(ProducerToken const&) = delete;
ProducerToken& operator=(ProducerToken const&) = delete;
private:
template<typename T, typename Traits> friend class ConcurrentQueue;
protected:
details::ConcurrentQueueProducerTypelessBase* producer;
};
struct ConsumerToken
{
template<typename T, typename Traits>
explicit ConsumerToken(ConcurrentQueue<T, Traits>& q);
ConsumerToken(ConsumerToken&& other) noexcept
: initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer)
{
}
inline ConsumerToken& operator=(ConsumerToken&& other) noexcept
{
swap(other);
return *this;
}
void swap(ConsumerToken& other) noexcept
{
std::swap(initialOffset, other.initialOffset);
std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset);
std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent);
std::swap(currentProducer, other.currentProducer);
std::swap(desiredProducer, other.desiredProducer);
}
ConsumerToken(ConsumerToken const&) = delete;
ConsumerToken& operator=(ConsumerToken const&) = delete;
private:
template<typename T, typename Traits> friend class ConcurrentQueue;
private: std::uint32_t initialOffset;
std::uint32_t lastKnownGlobalOffset;
std::uint32_t itemsConsumedFromCurrent;
details::ConcurrentQueueProducerTypelessBase* currentProducer;
details::ConcurrentQueueProducerTypelessBase* desiredProducer;
};
template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
class ConcurrentQueue
{
public:
struct ExplicitProducer;
typedef moodycamel::ProducerToken producer_token_t;
typedef moodycamel::ConsumerToken consumer_token_t;
typedef typename Traits::index_t index_t;
typedef typename Traits::size_t size_t;
static const size_t BLOCK_SIZE = static_cast<size_t>(Traits::BLOCK_SIZE);
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast<size_t>(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD);
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast<size_t>(Traits::EXPLICIT_INITIAL_INDEX_SIZE);
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast<std::uint32_t>(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE);
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4307)
#pragma warning(disable: 4309)
#endif
static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max<size_t>::value - static_cast<size_t>(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max<size_t>::value : ((static_cast<size_t>(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
static_assert(!std::numeric_limits<size_t>::is_signed && std::is_integral<size_t>::value, "Traits::size_t must be an unsigned integral type");
static_assert(!std::numeric_limits<index_t>::is_signed && std::is_integral<index_t>::value, "Traits::index_t must be an unsigned integral type");
static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t");
static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)");
static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)");
static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)");
public:
explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
: producerListTail(nullptr),
producerCount(0),
initialBlockPoolIndex(0),
nextExplicitConsumerId(0),
globalExplicitConsumerOffset(0)
{
populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1));
}
ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers)
: producerListTail(nullptr),
producerCount(0),
initialBlockPoolIndex(0),
nextExplicitConsumerId(0),
globalExplicitConsumerOffset(0)
{
size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers);
populate_initial_block_list(blocks);
}
~ConcurrentQueue()
{
auto ptr = producerListTail.load(std::memory_order_relaxed);
while (ptr != nullptr) {
auto next = ptr->next_prod();
if (ptr->token != nullptr) {
ptr->token->producer = nullptr;
}
destroy(ptr);
ptr = next;
}
auto block = freeList.head_unsafe();
while (block != nullptr) {
auto next = block->freeListNext.load(std::memory_order_relaxed);
if (block->dynamicallyAllocated) {
destroy(block);
}
block = next;
}
destroy_array(initialBlockPool, initialBlockPoolSize);
}
ConcurrentQueue(ConcurrentQueue const&) = delete;
ConcurrentQueue(ConcurrentQueue&& other) = delete;
ConcurrentQueue& operator=(ConcurrentQueue const&) = delete;
ConcurrentQueue& operator=(ConcurrentQueue&& other) = delete;
public:
tracy_force_inline T* enqueue_begin(producer_token_t const& token, index_t& currentTailIndex)
{
return static_cast<ExplicitProducer*>(token.producer)->ConcurrentQueue::ExplicitProducer::enqueue_begin(currentTailIndex);
}
template<class NotifyThread, class ProcessData>
size_t try_dequeue_bulk_single(consumer_token_t& token, NotifyThread notifyThread, ProcessData processData )
{
if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) {
if (!update_current_producer_after_rotation(token)) {
return 0;
}
}
size_t count = static_cast<ProducerBase*>(token.currentProducer)->dequeue_bulk(notifyThread, processData);
token.itemsConsumedFromCurrent += static_cast<std::uint32_t>(count);
auto tail = producerListTail.load(std::memory_order_acquire);
auto ptr = static_cast<ProducerBase*>(token.currentProducer)->next_prod();
if (ptr == nullptr) {
ptr = tail;
}
if( count == 0 )
{
while (ptr != static_cast<ProducerBase*>(token.currentProducer)) {
auto dequeued = ptr->dequeue_bulk(notifyThread, processData);
if (dequeued != 0) {
token.currentProducer = ptr;
token.itemsConsumedFromCurrent = static_cast<std::uint32_t>(dequeued);
return dequeued;
}
ptr = ptr->next_prod();
if (ptr == nullptr) {
ptr = tail;
}
}
return 0;
}
else
{
token.currentProducer = ptr;
token.itemsConsumedFromCurrent = 0;
return count;
}
}
size_t size_approx() const
{
size_t size = 0;
for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
size += ptr->size_approx();
}
return size;
}
static bool is_lock_free()
{
return
details::static_is_lock_free<bool>::value == 2 &&
details::static_is_lock_free<size_t>::value == 2 &&
details::static_is_lock_free<std::uint32_t>::value == 2 &&
details::static_is_lock_free<index_t>::value == 2 &&
details::static_is_lock_free<void*>::value == 2;
}
private:
friend struct ProducerToken;
friend struct ConsumerToken;
friend struct ExplicitProducer;
inline bool update_current_producer_after_rotation(consumer_token_t& token)
{
auto tail = producerListTail.load(std::memory_order_acquire);
if (token.desiredProducer == nullptr && tail == nullptr) {
return false;
}
auto prodCount = producerCount.load(std::memory_order_relaxed);
auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed);
if (details::cqUnlikely(token.desiredProducer == nullptr)) {
std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount);
token.desiredProducer = tail;
for (std::uint32_t i = 0; i != offset; ++i) {
token.desiredProducer = static_cast<ProducerBase*>(token.desiredProducer)->next_prod();
if (token.desiredProducer == nullptr) {
token.desiredProducer = tail;
}
}
}
std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset;
if (delta >= prodCount) {
delta = delta % prodCount;
}
for (std::uint32_t i = 0; i != delta; ++i) {
token.desiredProducer = static_cast<ProducerBase*>(token.desiredProducer)->next_prod();
if (token.desiredProducer == nullptr) {
token.desiredProducer = tail;
}
}
token.lastKnownGlobalOffset = globalOffset;
token.currentProducer = token.desiredProducer;
token.itemsConsumedFromCurrent = 0;
return true;
}
template <typename N>
struct FreeListNode
{
FreeListNode() : freeListRefs(0), freeListNext(nullptr) { }
std::atomic<std::uint32_t> freeListRefs;
std::atomic<N*> freeListNext;
};
template<typename N> struct FreeList
{
FreeList() : freeListHead(nullptr) { }
FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); }
void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); }
FreeList(FreeList const&) = delete;
FreeList& operator=(FreeList const&) = delete;
inline void add(N* node)
{
if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) {
add_knowing_refcount_is_zero(node);
}
}
inline N* try_get()
{
auto head = freeListHead.load(std::memory_order_acquire);
while (head != nullptr) {
auto prevHead = head;
auto refs = head->freeListRefs.load(std::memory_order_relaxed);
if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) {
head = freeListHead.load(std::memory_order_acquire);
continue;
}
auto next = head->freeListNext.load(std::memory_order_relaxed);
if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) {
assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0);
head->freeListRefs.fetch_sub(2, std::memory_order_release);
return head;
}
refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel);
if (refs == SHOULD_BE_ON_FREELIST + 1) {
add_knowing_refcount_is_zero(prevHead);
}
}
return nullptr;
}
N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); }
private:
inline void add_knowing_refcount_is_zero(N* node)
{
auto head = freeListHead.load(std::memory_order_relaxed);
while (true) {
node->freeListNext.store(head, std::memory_order_relaxed);
node->freeListRefs.store(1, std::memory_order_release);
if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) {
if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) {
continue;
}
}
return;
}
}
private:
std::atomic<N*> freeListHead;
static const std::uint32_t REFS_MASK = 0x7FFFFFFF;
static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000;
};
struct Block
{
Block()
: next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true)
{
}
inline bool is_empty() const
{
if (compile_time_condition<BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD>::value) {
for (size_t i = 0; i < BLOCK_SIZE; ++i) {
if (!emptyFlags[i].load(std::memory_order_relaxed)) {
return false;
}
}
std::atomic_thread_fence(std::memory_order_acquire);
return true;
}
else {
if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) {
std::atomic_thread_fence(std::memory_order_acquire);
return true;
}
assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE);
return false;
}
}
inline bool set_empty(index_t i)
{
if (BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].load(std::memory_order_relaxed));
emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].store(true, std::memory_order_release);
return false;
}
else {
auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release);
assert(prevVal < BLOCK_SIZE);
return prevVal == BLOCK_SIZE - 1;
}
}
inline bool set_many_empty(index_t i, size_t count)
{
if (compile_time_condition<BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD>::value) {
std::atomic_thread_fence(std::memory_order_release);
i = BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1)) - count + 1;
for (size_t j = 0; j != count; ++j) {
assert(!emptyFlags[i + j].load(std::memory_order_relaxed));
emptyFlags[i + j].store(true, std::memory_order_relaxed);
}
return false;
}
else {
auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release);
assert(prevVal + count <= BLOCK_SIZE);
return prevVal + count == BLOCK_SIZE;
}
}
inline void set_all_empty()
{
if (BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
for (size_t i = 0; i != BLOCK_SIZE; ++i) {
emptyFlags[i].store(true, std::memory_order_relaxed);
}
}
else {
elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed);
}
}
inline void reset_empty()
{
if (compile_time_condition<BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD>::value) {
for (size_t i = 0; i != BLOCK_SIZE; ++i) {
emptyFlags[i].store(false, std::memory_order_relaxed);
}
}
else {
elementsCompletelyDequeued.store(0, std::memory_order_relaxed);
}
}
inline T* operator[](index_t idx) noexcept { return static_cast<T*>(static_cast<void*>(elements)) + static_cast<size_t>(idx & static_cast<index_t>(BLOCK_SIZE - 1)); }
inline T const* operator[](index_t idx) const noexcept { return static_cast<T const*>(static_cast<void const*>(elements)) + static_cast<size_t>(idx & static_cast<index_t>(BLOCK_SIZE - 1)); }
private:
static_assert(std::alignment_of<T>::value <= std::alignment_of<details::max_align_t>::value, "The queue does not support super-aligned types at this time");
union {
char elements[sizeof(T) * BLOCK_SIZE];
details::max_align_t dummy;
};
public:
Block* next;
std::atomic<size_t> elementsCompletelyDequeued;
std::atomic<bool> emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1];
public:
std::atomic<std::uint32_t> freeListRefs;
std::atomic<Block*> freeListNext;
std::atomic<bool> shouldBeOnFreeList;
bool dynamicallyAllocated; };
static_assert(std::alignment_of<Block>::value >= std::alignment_of<details::max_align_t>::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping");
struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase
{
ProducerBase(ConcurrentQueue* parent_) :
tailIndex(0),
headIndex(0),
dequeueOptimisticCount(0),
dequeueOvercommit(0),
tailBlock(nullptr),
parent(parent_)
{
}
virtual ~ProducerBase() { };
template<class NotifyThread, class ProcessData>
inline size_t dequeue_bulk(NotifyThread notifyThread, ProcessData processData)
{
return static_cast<ExplicitProducer*>(this)->dequeue_bulk(notifyThread, processData);
}
inline ProducerBase* next_prod() const { return static_cast<ProducerBase*>(next); }
inline size_t size_approx() const
{
auto tail = tailIndex.load(std::memory_order_relaxed);
auto head = headIndex.load(std::memory_order_relaxed);
return details::circular_less_than(head, tail) ? static_cast<size_t>(tail - head) : 0;
}
inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); }
protected:
std::atomic<index_t> tailIndex; std::atomic<index_t> headIndex;
std::atomic<index_t> dequeueOptimisticCount;
std::atomic<index_t> dequeueOvercommit;
Block* tailBlock;
public:
ConcurrentQueue* parent;
};
public:
struct ExplicitProducer : public ProducerBase
{
explicit ExplicitProducer(ConcurrentQueue* _parent) :
ProducerBase(_parent),
blockIndex(nullptr),
pr_blockIndexSlotsUsed(0),
pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1),
pr_blockIndexFront(0),
pr_blockIndexEntries(nullptr),
pr_blockIndexRaw(nullptr)
{
size_t poolBasedIndexSize = details::ceil_to_pow_2(_parent->initialBlockPoolSize) >> 1;
if (poolBasedIndexSize > pr_blockIndexSize) {
pr_blockIndexSize = poolBasedIndexSize;
}
new_block_index(0); }
~ExplicitProducer()
{
if (this->tailBlock != nullptr) { Block* halfDequeuedBlock = nullptr;
if ((this->headIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1)) != 0) {
size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1);
while (details::circular_less_than<index_t>(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) {
i = (i + 1) & (pr_blockIndexSize - 1);
}
assert(details::circular_less_than<index_t>(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed)));
halfDequeuedBlock = pr_blockIndexEntries[i].block;
}
auto block = this->tailBlock;
do {
block = block->next;
if (block->ConcurrentQueue::Block::is_empty()) {
continue;
}
size_t i = 0; if (block == halfDequeuedBlock) {
i = static_cast<size_t>(this->headIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1));
}
auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast<size_t>(this->tailIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1));
while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) {
(*block)[i++]->~T();
}
} while (block != this->tailBlock);
}
if (this->tailBlock != nullptr) {
auto block = this->tailBlock;
do {
auto nextBlock = block->next;
if (block->dynamicallyAllocated) {
destroy(block);
}
else {
this->parent->add_block_to_free_list(block);
}
block = nextBlock;
} while (block != this->tailBlock);
}
auto header = static_cast<BlockIndexHeader*>(pr_blockIndexRaw);
while (header != nullptr) {
auto prev = static_cast<BlockIndexHeader*>(header->prev);
header->~BlockIndexHeader();
(Traits::free)(header);
header = prev;
}
}
inline void enqueue_begin_alloc(index_t currentTailIndex)
{
if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::is_empty()) {
this->tailBlock = this->tailBlock->next;
this->tailBlock->ConcurrentQueue::Block::reset_empty();
}
else {
if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) {
new_block_index(pr_blockIndexSlotsUsed);
}
auto newBlock = this->parent->ConcurrentQueue::requisition_block();
newBlock->ConcurrentQueue::Block::reset_empty();
if (this->tailBlock == nullptr) {
newBlock->next = newBlock;
}
else {
newBlock->next = this->tailBlock->next;
this->tailBlock->next = newBlock;
}
this->tailBlock = newBlock;
++pr_blockIndexSlotsUsed;
}
auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront];
entry.base = currentTailIndex;
entry.block = this->tailBlock;
blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release);
pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1);
}
tracy_force_inline T* enqueue_begin(index_t& currentTailIndex)
{
currentTailIndex = this->tailIndex.load(std::memory_order_relaxed);
if (details::cqUnlikely((currentTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0)) {
this->enqueue_begin_alloc(currentTailIndex);
}
return (*this->tailBlock)[currentTailIndex];
}
tracy_force_inline std::atomic<index_t>& get_tail_index()
{
return this->tailIndex;
}
template<class NotifyThread, class ProcessData>
size_t dequeue_bulk(NotifyThread notifyThread, ProcessData processData)
{
auto tail = this->tailIndex.load(std::memory_order_relaxed);
auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed);
auto desiredCount = static_cast<size_t>(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit));
if (details::circular_less_than<size_t>(0, desiredCount)) {
desiredCount = desiredCount < 8192 ? desiredCount : 8192;
std::atomic_thread_fence(std::memory_order_acquire);
auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed);
assert(overcommit <= myDequeueCount);
tail = this->tailIndex.load(std::memory_order_acquire);
auto actualCount = static_cast<size_t>(tail - (myDequeueCount - overcommit));
if (details::circular_less_than<size_t>(0, actualCount)) {
actualCount = desiredCount < actualCount ? desiredCount : actualCount;
if (actualCount < desiredCount) {
this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release);
}
auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel);
auto localBlockIndex = blockIndex.load(std::memory_order_acquire);
auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire);
auto headBase = localBlockIndex->entries[localBlockIndexHead].base;
auto firstBlockBaseIndex = firstIndex & ~static_cast<index_t>(BLOCK_SIZE - 1);
auto offset = static_cast<size_t>(static_cast<typename std::make_signed<index_t>::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE);
auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1);
notifyThread( this->threadId );
auto index = firstIndex;
do {
auto firstIndexInBlock = index;
auto endIndex = (index & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
endIndex = details::circular_less_than<index_t>(firstIndex + static_cast<index_t>(actualCount), endIndex) ? firstIndex + static_cast<index_t>(actualCount) : endIndex;
auto block = localBlockIndex->entries[indexIndex].block;
const auto sz = endIndex - index;
processData( (*block)[index], sz );
index += sz;
block->ConcurrentQueue::Block::set_many_empty(firstIndexInBlock, static_cast<size_t>(endIndex - firstIndexInBlock));
indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1);
} while (index != firstIndex + actualCount);
return actualCount;
}
else {
this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release);
}
}
return 0;
}
private:
struct BlockIndexEntry
{
index_t base;
Block* block;
};
struct BlockIndexHeader
{
size_t size;
std::atomic<size_t> front; BlockIndexEntry* entries;
void* prev;
};
bool new_block_index(size_t numberOfFilledSlotsToExpose)
{
auto prevBlockSizeMask = pr_blockIndexSize - 1;
pr_blockIndexSize <<= 1;
auto newRawPtr = static_cast<char*>((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of<BlockIndexEntry>::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize));
if (newRawPtr == nullptr) {
pr_blockIndexSize >>= 1; return false;
}
auto newBlockIndexEntries = reinterpret_cast<BlockIndexEntry*>(details::align_for<BlockIndexEntry>(newRawPtr + sizeof(BlockIndexHeader)));
size_t j = 0;
if (pr_blockIndexSlotsUsed != 0) {
auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask;
do {
newBlockIndexEntries[j++] = pr_blockIndexEntries[i];
i = (i + 1) & prevBlockSizeMask;
} while (i != pr_blockIndexFront);
}
auto header = new (newRawPtr) BlockIndexHeader;
header->size = pr_blockIndexSize;
header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed);
header->entries = newBlockIndexEntries;
header->prev = pr_blockIndexRaw;
pr_blockIndexFront = j;
pr_blockIndexEntries = newBlockIndexEntries;
pr_blockIndexRaw = newRawPtr;
blockIndex.store(header, std::memory_order_release);
return true;
}
private:
std::atomic<BlockIndexHeader*> blockIndex;
size_t pr_blockIndexSlotsUsed;
size_t pr_blockIndexSize;
size_t pr_blockIndexFront; BlockIndexEntry* pr_blockIndexEntries;
void* pr_blockIndexRaw;
};
ExplicitProducer* get_explicit_producer(producer_token_t const& token)
{
return static_cast<ExplicitProducer*>(token.producer);
}
private:
void populate_initial_block_list(size_t blockCount)
{
initialBlockPoolSize = blockCount;
if (initialBlockPoolSize == 0) {
initialBlockPool = nullptr;
return;
}
initialBlockPool = create_array<Block>(blockCount);
if (initialBlockPool == nullptr) {
initialBlockPoolSize = 0;
}
for (size_t i = 0; i < initialBlockPoolSize; ++i) {
initialBlockPool[i].dynamicallyAllocated = false;
}
}
inline Block* try_get_block_from_initial_pool()
{
if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) {
return nullptr;
}
auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed);
return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr;
}
inline void add_block_to_free_list(Block* block)
{
freeList.add(block);
}
inline void add_blocks_to_free_list(Block* block)
{
while (block != nullptr) {
auto next = block->next;
add_block_to_free_list(block);
block = next;
}
}
inline Block* try_get_block_from_free_list()
{
return freeList.try_get();
}
Block* requisition_block()
{
auto block = try_get_block_from_initial_pool();
if (block != nullptr) {
return block;
}
block = try_get_block_from_free_list();
if (block != nullptr) {
return block;
}
return create<Block>();
}
ProducerBase* recycle_or_create_producer()
{
bool recycled;
return recycle_or_create_producer(recycled);
}
ProducerBase* recycle_or_create_producer(bool& recycled)
{
for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
if (ptr->inactive.load(std::memory_order_relaxed)) {
if( ptr->size_approx() == 0 )
{
bool expected = true;
if (ptr->inactive.compare_exchange_strong(expected, false, std::memory_order_acquire, std::memory_order_relaxed)) {
recycled = true;
return ptr;
}
}
}
}
recycled = false;
return add_producer(static_cast<ProducerBase*>(create<ExplicitProducer>(this)));
}
ProducerBase* add_producer(ProducerBase* producer)
{
if (producer == nullptr) {
return nullptr;
}
producerCount.fetch_add(1, std::memory_order_relaxed);
auto prevTail = producerListTail.load(std::memory_order_relaxed);
do {
producer->next = prevTail;
} while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed));
return producer;
}
void reown_producers()
{
for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) {
ptr->parent = this;
}
}
template<typename U>
static inline U* create_array(size_t count)
{
assert(count > 0);
return static_cast<U*>((Traits::malloc)(sizeof(U) * count));
}
template<typename U>
static inline void destroy_array(U* p, size_t count)
{
((void)count);
if (p != nullptr) {
assert(count > 0);
(Traits::free)(p);
}
}
template<typename U>
static inline U* create()
{
auto p = (Traits::malloc)(sizeof(U));
return new (p) U;
}
template<typename U, typename A1>
static inline U* create(A1&& a1)
{
auto p = (Traits::malloc)(sizeof(U));
return new (p) U(std::forward<A1>(a1));
}
template<typename U>
static inline void destroy(U* p)
{
if (p != nullptr) {
p->~U();
}
(Traits::free)(p);
}
private:
std::atomic<ProducerBase*> producerListTail;
std::atomic<std::uint32_t> producerCount;
std::atomic<size_t> initialBlockPoolIndex;
Block* initialBlockPool;
size_t initialBlockPoolSize;
FreeList<Block> freeList;
std::atomic<std::uint32_t> nextExplicitConsumerId;
std::atomic<std::uint32_t> globalExplicitConsumerOffset;
};
template<typename T, typename Traits>
ProducerToken::ProducerToken(ConcurrentQueue<T, Traits>& queue)
: producer(queue.recycle_or_create_producer())
{
if (producer != nullptr) {
producer->token = this;
producer->threadId = detail::GetThreadHandleImpl();
}
}
template<typename T, typename Traits>
ConsumerToken::ConsumerToken(ConcurrentQueue<T, Traits>& queue)
: itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr)
{
initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release);
lastKnownGlobalOffset = static_cast<std::uint32_t>(-1);
}
template<typename T, typename Traits>
inline void swap(ConcurrentQueue<T, Traits>& a, ConcurrentQueue<T, Traits>& b) noexcept
{
a.swap(b);
}
inline void swap(ProducerToken& a, ProducerToken& b) noexcept
{
a.swap(b);
}
inline void swap(ConsumerToken& a, ConsumerToken& b) noexcept
{
a.swap(b);
}
}
}
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif