WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
FastQueue.hpp
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::util {
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::util
#define WPI_FQ_FORCEINLINE
Definition FastQueue.hpp:47
you may not use this file except in compliance with the License You may obtain a copy of the License at software distributed under the License is distributed on an AS IS WITHOUT WARRANTIES OR CONDITIONS OF ANY either express or implied See the License for the specific language governing permissions and limitations under the License LLVM Exceptions to the Apache License As an if
Definition ThirdPartyNotices.txt:301
size_t size() const
Definition FastQueue.hpp:377
FastQueue & operator=(FastQueue &&other)
Definition FastQueue.hpp:146
WPI_FQ_FORCEINLINE bool try_emplace(Args &&... args)
Definition FastQueue.hpp:199
WPI_FQ_FORCEINLINE bool emplace(Args &&... args)
Definition FastQueue.hpp:222
T * peek() const
Definition FastQueue.hpp:294
WPI_FQ_FORCEINLINE bool try_enqueue(T const &element)
Definition FastQueue.hpp:184
WPI_FQ_FORCEINLINE bool enqueue(T &&element)
Definition FastQueue.hpp:215
bool empty() const
Definition FastQueue.hpp:372
T value_type
Definition FastQueue.hpp:82
FastQueue(FastQueue &&other)
Definition FastQueue.hpp:131
size_t max_capacity() const
Definition FastQueue.hpp:393
WPI_FQ_FORCEINLINE bool try_enqueue(T &&element)
Definition FastQueue.hpp:192
~FastQueue()
Definition FastQueue.hpp:158
bool pop()
Definition FastQueue.hpp:324
FastQueue(size_t size=15)
Definition FastQueue.hpp:88
WPI_FQ_FORCEINLINE bool enqueue(T const &element)
Definition FastQueue.hpp:207
bool try_dequeue(U &result)
Definition FastQueue.hpp:231
auto ptr(T p) -> const void *
Converts p to const void* for pointer formatting.
Definition format.h:3975
void swap(wpi::util::StringMap< T > &lhs, wpi::util::StringMap< T > &rhs)
Definition StringMap.hpp:775
Definition raw_os_ostream.hpp:19