WPILibC++ 2025.1.1
Loading...
Searching...
No Matches
FastQueue.h
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5// This is a modified version of readerwriterqueue to remove atomics and barriers
6// for single-thread operation.
7//
8// Copyright (c) 2013-2021, Cameron Desrochers
9// All rights reserved.
10//
11// Redistribution and use in source and binary forms, with or without modification,
12// are permitted provided that the following conditions are met:
13//
14// - Redistributions of source code must retain the above copyright notice, this list of
15// conditions and the following disclaimer.
16// - Redistributions in binary form must reproduce the above copyright notice, this list of
17// conditions and the following disclaimer in the documentation and/or other materials
18// provided with the distribution.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
21// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
25// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#pragma once
31
32#include <cassert>
33#include <cerrno>
34#include <cstdint>
35#include <cstdlib> // For malloc/free/abort & size_t
36#include <new>
37#include <type_traits>
38#include <utility>
39
40// WPI_FQ_FORCEINLINE
41#if defined(_MSC_VER)
42#define WPI_FQ_FORCEINLINE __forceinline
43#elif defined(__GNUC__)
44//#define WPI_FQ_FORCEINLINE __attribute__((always_inline))
45#define WPI_FQ_FORCEINLINE inline
46#else
47#define WPI_FQ_FORCEINLINE inline
48#endif
49
50// A queue for a single-consumer, single-producer architecture.
51// The queue is also wait-free in the common path (except if more memory
52// needs to be allocated, in which case malloc is called).
53// Allocates memory sparingly, and only once if the original maximum size
54// estimate is never exceeded.
55
56namespace wpi {
57
58template<typename T, size_t MAX_BLOCK_SIZE = 512>
60{
61 // Design: Based on a queue-of-queues. The low-level queues are just
62 // circular buffers with front and tail indices indicating where the
63 // next element to dequeue is and where the next element can be enqueued,
64 // respectively. Each low-level queue is called a "block". Each block
65 // wastes exactly one element's worth of space to keep the design simple
66 // (if front == tail then the queue is empty, and can't be full).
67 // The high-level queue is a circular linked list of blocks; again there
68 // is a front and tail, but this time they are pointers to the blocks.
69 // The front block is where the next element to be dequeued is, provided
70 // the block is not empty. The back block is where elements are to be
71 // enqueued, provided the block is not full.
72 // The producer owns all the tail indices/pointers. The consumer
73 // owns all the front indices/pointers. Both read each
74 // other's variables, but only the owning side updates them. E.g. After
75 // the consumer reads the producer's tail, the tail may change before the
76 // consumer is done dequeuing an object, but the consumer knows the tail
77 // will never go backwards, only forwards.
78 // If there is no room to enqueue an object, an additional block (of
79 // equal size to the last block) is added. Blocks are never removed.
80
81public:
82 typedef T value_type;
83
84 // Constructs a queue that can hold at least `size` elements without further
85 // allocations. If more than MAX_BLOCK_SIZE elements are requested,
86 // then several blocks of MAX_BLOCK_SIZE each are reserved (including
87 // at least one extra buffer block).
88 explicit FastQueue(size_t size = 15)
89 {
90 static_assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && "MAX_BLOCK_SIZE must be a power of 2");
91 static_assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");
92
93 Block* firstBlock = nullptr;
94
95 largestBlockSize = ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block
96 if (largestBlockSize > MAX_BLOCK_SIZE * 2) {
97 // We need a spare block in case the producer is writing to a different block the consumer is reading from, and
98 // wants to enqueue the maximum number of elements. We also need a spare element in each block to avoid the ambiguity
99 // between front == tail meaning "empty" and "full".
100 // So the effective number of slots that are guaranteed to be usable at any time is the block size - 1 times the
101 // number of blocks - 1. Solving for size and applying a ceiling to the division gives us (after simplifying):
102 size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);
103 largestBlockSize = MAX_BLOCK_SIZE;
104 Block* lastBlock = nullptr;
105 for (size_t i = 0; i != initialBlockCount; ++i) {
106 auto block = make_block(largestBlockSize);
107 if (block == nullptr) {
108 throw std::bad_alloc();
109 }
110 if (firstBlock == nullptr) {
111 firstBlock = block;
112 }
113 else {
114 lastBlock->next = block;
115 }
116 lastBlock = block;
117 block->next = firstBlock;
118 }
119 }
120 else {
121 firstBlock = make_block(largestBlockSize);
122 if (firstBlock == nullptr) {
123 throw std::bad_alloc();
124 }
125 firstBlock->next = firstBlock;
126 }
127 frontBlock = firstBlock;
128 tailBlock = firstBlock;
129 }
130
132 : frontBlock(other.frontBlock),
133 tailBlock(other.tailBlock),
134 largestBlockSize(other.largestBlockSize)
135 {
136 other.largestBlockSize = 32;
137 Block* b = other.make_block(other.largestBlockSize);
138 if (b == nullptr) {
139 throw std::bad_alloc();
140 }
141 b->next = b;
142 other.frontBlock = b;
143 other.tailBlock = b;
144 }
145
147 {
148 Block* b = frontBlock;
149 frontBlock = other.frontBlock;
150 other.frontBlock = b;
151 b = tailBlock;
152 tailBlock = other.tailBlock;
153 other.tailBlock = b;
154 std::swap(largestBlockSize, other.largestBlockSize);
155 return *this;
156 }
157
159 {
160 // Destroy any remaining objects in queue and free memory
161 Block* frontBlock_ = frontBlock;
162 Block* block = frontBlock_;
163 do {
164 Block* nextBlock = block->next;
165 size_t blockFront = block->front;
166 size_t blockTail = block->tail;
167
168 for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {
169 auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));
170 element->~T();
171 (void)element;
172 }
173
174 block->~Block();
175 std::free(block);
176 block = nextBlock;
177 } while (block != frontBlock_);
178 }
179
180
181 // Enqueues a copy of element if there is room in the queue.
182 // Returns true if the element was enqueued, false otherwise.
183 // Does not allocate memory.
184 WPI_FQ_FORCEINLINE bool try_enqueue(T const& element)
185 {
186 return inner_enqueue<CannotAlloc>(element);
187 }
188
189 // Enqueues a moved copy of element if there is room in the queue.
190 // Returns true if the element was enqueued, false otherwise.
191 // Does not allocate memory.
193 {
194 return inner_enqueue<CannotAlloc>(std::forward<T>(element));
195 }
196
197 // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
198 template<typename... Args>
199 WPI_FQ_FORCEINLINE bool try_emplace(Args&&... args)
200 {
201 return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);
202 }
203
204 // Enqueues a copy of element on the queue.
205 // Allocates an additional block of memory if needed.
206 // Only fails (returns false) if memory allocation fails.
207 WPI_FQ_FORCEINLINE bool enqueue(T const& element)
208 {
209 return inner_enqueue<CanAlloc>(element);
210 }
211
212 // Enqueues a moved copy of element on the queue.
213 // Allocates an additional block of memory if needed.
214 // Only fails (returns false) if memory allocation fails.
215 WPI_FQ_FORCEINLINE bool enqueue(T&& element)
216 {
217 return inner_enqueue<CanAlloc>(std::forward<T>(element));
218 }
219
220 // Like enqueue() but with emplace semantics (i.e. construct-in-place).
221 template<typename... Args>
222 WPI_FQ_FORCEINLINE bool emplace(Args&&... args)
223 {
224 return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);
225 }
226
227 // Attempts to dequeue an element; if the queue is empty,
228 // returns false instead. If the queue has at least one element,
229 // moves front to result using operator=, then returns true.
230 template<typename U>
231 bool try_dequeue(U& result)
232 {
233 // High-level pseudocode:
234 // Remember where the tail block is
235 // If the front block has an element in it, dequeue it
236 // Else
237 // If front block was the tail block when we entered the function, return false
238 // Else advance to next block and dequeue the item there
239
240 Block* frontBlock_ = frontBlock;
241 size_t blockTail = frontBlock_->localTail;
242 size_t blockFront = frontBlock_->front;
243
244 if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail)) {
245 // Front block not empty, dequeue from here
246 auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
247 result = std::move(*element);
248 element->~T();
249
250 blockFront = (blockFront + 1) & frontBlock_->sizeMask;
251
252 frontBlock_->front = blockFront;
253 }
254 else if (frontBlock_ != tailBlock) {
255 frontBlock_ = frontBlock;
256 blockTail = frontBlock_->localTail = frontBlock_->tail;
257 blockFront = frontBlock_->front;
258
259 // Front block is empty but there's another block ahead, advance to it
260 Block* nextBlock = frontBlock_->next;
261 size_t nextBlockFront = nextBlock->front;
262 size_t nextBlockTail = nextBlock->localTail = nextBlock->tail;
263
264 // Since the tailBlock is only ever advanced after being written to,
265 // we know there's for sure an element to dequeue on it
266 assert(nextBlockFront != nextBlockTail);
267 (void) nextBlockTail;
268
269 // We're done with this block, let the producer use it if it needs
270 frontBlock = frontBlock_ = nextBlock;
271
272 auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
273
274 result = std::move(*element);
275 element->~T();
276
277 nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
278
279 frontBlock_->front = nextBlockFront;
280 }
281 else {
282 // No elements in current block and no other block to advance to
283 return false;
284 }
285
286 return true;
287 }
288
289
290 // Returns a pointer to the front element in the queue (the one that
291 // would be removed next by a call to `try_dequeue` or `pop`). If the
292 // queue appears empty at the time the method is called, nullptr is
293 // returned instead.
294 T* peek() const
295 {
296 // See try_dequeue() for reasoning
297
298 Block* frontBlock_ = frontBlock;
299 size_t blockTail = frontBlock_->localTail;
300 size_t blockFront = frontBlock_->front;
301
302 if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail)) {
303 return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
304 }
305 else if (frontBlock_ != tailBlock) {
306 frontBlock_ = frontBlock;
307 blockTail = frontBlock_->localTail = frontBlock_->tail;
308 blockFront = frontBlock_->front;
309
310 Block* nextBlock = frontBlock_->next;
311
312 size_t nextBlockFront = nextBlock->front;
313
314 assert(nextBlockFront != nextBlock->tail);
315 return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));
316 }
317
318 return nullptr;
319 }
320
321 // Removes the front element from the queue, if any, without returning it.
322 // Returns true on success, or false if the queue appeared empty at the time
323 // `pop` was called.
324 bool pop()
325 {
326 // See try_dequeue() for reasoning
327
328 Block* frontBlock_ = frontBlock;
329 size_t blockTail = frontBlock_->localTail;
330 size_t blockFront = frontBlock_->front;
331
332 if (blockFront != blockTail || blockFront != (frontBlock_->localTail = frontBlock_->tail)) {
333 auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
334 element->~T();
335
336 blockFront = (blockFront + 1) & frontBlock_->sizeMask;
337
338 frontBlock_->front = blockFront;
339 }
340 else if (frontBlock_ != tailBlock) {
341 frontBlock_ = frontBlock;
342 blockTail = frontBlock_->localTail = frontBlock_->tail;
343 blockFront = frontBlock_->front;
344
345 // Front block is empty but there's another block ahead, advance to it
346 Block* nextBlock = frontBlock_->next;
347
348 size_t nextBlockFront = nextBlock->front;
349 size_t nextBlockTail = nextBlock->localTail = nextBlock->tail;
350
351 assert(nextBlockFront != nextBlockTail);
352 (void) nextBlockTail;
353
354 frontBlock = frontBlock_ = nextBlock;
355
356 auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
357 element->~T();
358
359 nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
360
361 frontBlock_->front = nextBlockFront;
362 }
363 else {
364 // No elements in current block and no other block to advance to
365 return false;
366 }
367
368 return true;
369 }
370
371 // Returns if the queue is empty
372 inline bool empty() const {
373 return size() == 0;
374 }
375
376 // Returns the number of items currently in the queue.
377 inline size_t size() const
378 {
379 size_t result = 0;
380 Block* frontBlock_ = frontBlock;
381 Block* block = frontBlock_;
382 do {
383 size_t blockFront = block->front;
384 size_t blockTail = block->tail;
385 result += (blockTail - blockFront) & block->sizeMask;
386 block = block->next;
387 } while (block != frontBlock_);
388 return result;
389 }
390
391 // Returns the total number of items that could be enqueued without incurring
392 // an allocation when this queue is empty.
393 inline size_t max_capacity() const {
394 size_t result = 0;
395 Block* frontBlock_ = frontBlock;
396 Block* block = frontBlock_;
397 do {
398 result += block->sizeMask;
399 block = block->next;
400 } while (block != frontBlock_);
401 return result;
402 }
403
404
405private:
406 enum AllocationMode { CanAlloc, CannotAlloc };
407
408 template<AllocationMode canAlloc, typename... Args>
409 bool inner_enqueue(Args&&... args)
410 {
411 // High-level pseudocode (assuming we're allowed to alloc a new block):
412 // If room in tail block, add to tail
413 // Else check next block
414 // If next block is not the head block, enqueue on next block
415 // Else create a new block and enqueue there
416 // Advance tail to the block we just enqueued to
417
418 Block* tailBlock_ = tailBlock;
419 size_t blockFront = tailBlock_->localFront;
420 size_t blockTail = tailBlock_->tail;
421
422 size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;
423 if (nextBlockTail != blockFront || nextBlockTail != (tailBlock_->localFront = tailBlock_->front)) {
424 // This block has room for at least one more element
425 char* location = tailBlock_->data + blockTail * sizeof(T);
426 new (location) T(std::forward<Args>(args)...);
427
428 tailBlock_->tail = nextBlockTail;
429 }
430 else {
431 if (tailBlock_->next != frontBlock) {
432 // Note that the reason we can't advance to the frontBlock and start adding new entries there
433 // is because if we did, then dequeue would stay in that block, eventually reading the new values,
434 // instead of advancing to the next full block (whose values were enqueued first and so should be
435 // consumed first).
436
437 // tailBlock is full, but there's a free block ahead, use it
438 Block* tailBlockNext = tailBlock_->next;
439 size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front;
440 nextBlockTail = tailBlockNext->tail;
441
442 // This block must be empty since it's not the head block and we
443 // go through the blocks in a circle
444 assert(nextBlockFront == nextBlockTail);
445 tailBlockNext->localFront = nextBlockFront;
446
447 char* location = tailBlockNext->data + nextBlockTail * sizeof(T);
448 new (location) T(std::forward<Args>(args)...);
449
450 tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;
451
452 tailBlock = tailBlockNext;
453 }
454 else if constexpr (canAlloc == CanAlloc) {
455 // tailBlock is full and there's no free block ahead; create a new block
456 auto newBlockSize = largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;
457 auto newBlock = make_block(newBlockSize);
458 if (newBlock == nullptr) {
459 // Could not allocate a block!
460 return false;
461 }
462 largestBlockSize = newBlockSize;
463
464 new (newBlock->data) T(std::forward<Args>(args)...);
465 assert(newBlock->front == 0);
466 newBlock->tail = newBlock->localTail = 1;
467
468 newBlock->next = tailBlock_->next;
469 tailBlock_->next = newBlock;
470
471 tailBlock = newBlock;
472 }
473 else if constexpr (canAlloc == CannotAlloc) {
474 // Would have had to allocate a new block to enqueue, but not allowed
475 return false;
476 }
477 else {
478 assert(false && "Should be unreachable code");
479 return false;
480 }
481 }
482
483 return true;
484 }
485
486 // Disable copying
487 FastQueue(FastQueue const&) = delete;
488
489 // Disable assignment
490 FastQueue& operator=(FastQueue const&) = delete;
491
492 static constexpr size_t ceilToPow2(size_t x)
493 {
494 // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
495 --x;
496 x |= x >> 1;
497 x |= x >> 2;
498 x |= x >> 4;
499 for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
500 x |= x >> (i << 3);
501 }
502 ++x;
503 return x;
504 }
505
506 template<typename U>
507 static WPI_FQ_FORCEINLINE char* align_for(char* ptr)
508 {
509 const std::size_t alignment = std::alignment_of<U>::value;
510 return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
511 }
512private:
513
514 struct Block
515 {
516 // Avoid false-sharing by putting highly contended variables on their own cache lines
517 size_t front; // Elements are read from here
518 size_t localTail; // An uncontended shadow copy of tail, owned by the consumer
519
520 size_t tail; // Elements are enqueued here
521 size_t localFront;
522
523 Block* next;
524
525 char* data; // Contents (on heap) are aligned to T's alignment
526
527 const size_t sizeMask;
528
529 // size must be a power of two (and greater than 0)
530 Block(size_t _size, char* _data)
531 : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), sizeMask(_size - 1)
532 {
533 }
534 };
535
536
537 static Block* make_block(size_t capacity)
538 {
539 // Allocate enough memory for the block itself, as well as all the elements it will contain
540 auto size = sizeof(Block);
541 size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;
542 auto newBlock = static_cast<char*>(std::malloc(size));
543 if (newBlock == nullptr) {
544 return nullptr;
545 }
546
547 auto newBlockData = align_for<T>(newBlock + sizeof(Block));
548 return new (newBlock) Block(capacity, newBlockData);
549 }
550
551private:
552 Block* frontBlock; // Elements are dequeued from this block
553 Block* tailBlock; // Elements are enqueued to this block
554 size_t largestBlockSize;
555};
556
557} // end namespace wpi
#define WPI_FQ_FORCEINLINE
Definition FastQueue.h:47
Definition FastQueue.h:60
WPI_FQ_FORCEINLINE bool try_enqueue(T &&element)
Definition FastQueue.h:192
size_t max_capacity() const
Definition FastQueue.h:393
bool empty() const
Definition FastQueue.h:372
T * peek() const
Definition FastQueue.h:294
WPI_FQ_FORCEINLINE bool try_enqueue(T const &element)
Definition FastQueue.h:184
bool try_dequeue(U &result)
Definition FastQueue.h:231
FastQueue(FastQueue &&other)
Definition FastQueue.h:131
WPI_FQ_FORCEINLINE bool enqueue(T const &element)
Definition FastQueue.h:207
FastQueue(size_t size=15)
Definition FastQueue.h:88
FastQueue & operator=(FastQueue &&other)
Definition FastQueue.h:146
bool pop()
Definition FastQueue.h:324
WPI_FQ_FORCEINLINE bool try_emplace(Args &&... args)
Definition FastQueue.h:199
WPI_FQ_FORCEINLINE bool emplace(Args &&... args)
Definition FastQueue.h:222
WPI_FQ_FORCEINLINE bool enqueue(T &&element)
Definition FastQueue.h:215
size_t size() const
Definition FastQueue.h:377
T value_type
Definition FastQueue.h:82
~FastQueue()
Definition FastQueue.h:158
auto ptr(T p) -> const void *
Converts p to const void* for pointer formatting.
Definition format.h:3821
SLEIPNIR_DLLEXPORT VariableMatrix Block(std::initializer_list< std::initializer_list< VariableMatrix > > list)
Assemble a VariableMatrix from a nested list of blocks.
Definition VariableMatrix.hpp:988
WPI_BASIC_JSON_TPL_DECLARATION void swap(wpi::WPI_BASIC_JSON_TPL &j1, wpi::WPI_BASIC_JSON_TPL &j2) noexcept(//NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp) is_nothrow_move_constructible< wpi::WPI_BASIC_JSON_TPL >::value &&//NOLINT(misc-redundant-expression, cppcoreguidelines-noexcept-swap, performance-noexcept-swap) is_nothrow_move_assignable< wpi::WPI_BASIC_JSON_TPL >::value)
exchanges the values of two JSON objects
Definition json.h:5258
Foonathan namespace.
Definition ntcore_cpp.h:26