dispenso 1.4.1
A library for task parallelism
Loading...
Searching...
No Matches
concurrent_vector.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
54#pragma once
55
56#include <algorithm>
57#include <cassert>
58#include <climits>
59#include <cstring>
60#include <initializer_list>
61#include <stdexcept>
62#include <utility>
63
64#include <dispenso/detail/math.h>
65#include <dispenso/platform.h>
67
68// Whether to use a non-atomic buffer pointer cache for read-hot paths.
69// Disabled on ARM where cache-line writes on every growth operation cause store buffer
70// pressure that exceeds the read-path benefit (acquire loads are effectively free for
71// sequential access patterns on ARM).
72#if !defined(__aarch64__) && !defined(_M_ARM64)
73#define DISPENSO_HAS_CACHED_PTRS 1
74#else
75#define DISPENSO_HAS_CACHED_PTRS 0
76#endif
77
78namespace dispenso {
79
91enum class ConcurrentVectorReallocStrategy { kFullBufferAhead, kHalfBufferAhead, kAsNeeded };
92
93struct ReserveTagS {};
99
100// Textual inclusion. Includes undocumented implementation details, e.g. iterators.
101#include <dispenso/detail/concurrent_vector_impl.h>
102
107template <typename T>
113 static constexpr size_t kDefaultCapacity = (sizeof(T) >= 256) ? 2 : 512 / sizeof(T);
114
122 static constexpr size_t kMaxVectorSize =
123 (size_t{1} << (sizeof(size_t) * CHAR_BIT > 47 ? 47 : sizeof(size_t) * CHAR_BIT - 1)) /
124 sizeof(T);
125};
126
139 static constexpr bool kPreferBuffersInline = true;
140
152 ConcurrentVectorReallocStrategy::kAsNeeded;
153
163 static constexpr bool kIteratorPreferSpeed = true;
164};
165
171template <
172 typename T,
173 typename Traits = DefaultConcurrentVectorTraits,
174 typename SizeTraits = DefaultConcurrentVectorSizeTraits<T>>
176 public:
177 using value_type = T;
178 using reference = T&;
179 using const_reference = const T&;
180 using size_type = size_t;
181 using difference_type = ssize_t;
182 using reference_type = T&;
183 using const_reference_type = const T&;
184 using pointer = T*;
185 using const_pointer = const T*;
186 using iterator = std::conditional_t<
187 Traits::kIteratorPreferSpeed,
188 cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, false>,
189 cv::CompactCVecIterator<ConcurrentVector<T, Traits>, T, false>>;
190 using const_iterator = std::conditional_t<
191 Traits::kIteratorPreferSpeed,
192 cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, true>,
193 cv::CompactCVecIterator<ConcurrentVector<T, Traits>, T, true>>;
194 using reverse_iterator = std::reverse_iterator<iterator>;
195 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
196
200 ConcurrentVector() : ConcurrentVector(SizeTraits::kDefaultCapacity / 2, ReserveTag) {}
201
207 ConcurrentVector(size_t startCapacity, ReserveTagS)
208 : firstBucketShift_(
209 detail::log2(
210 detail::nextPow2(std::max(startCapacity, SizeTraits::kDefaultCapacity / 2)))),
211 firstBucketLen_(size_type{1} << firstBucketShift_) {
212 T* firstTwo = cv::alloc<T>(2 * firstBucketLen_);
213 initCachedPtrs();
214 setCachedPtr(0, firstTwo);
215 setCachedPtr(1, firstTwo + firstBucketLen_);
216 buffers_[0].store(firstTwo, std::memory_order_release);
217 buffers_[1].store(firstTwo + firstBucketLen_, std::memory_order_release);
218 }
219
223 explicit ConcurrentVector(size_t startSize) : ConcurrentVector(startSize, ReserveTag) {
224 size_.store(startSize, std::memory_order_relaxed);
225 T* buf = buffers_[0].load(std::memory_order_relaxed);
226 for (size_t i = 0; i < startSize; ++i) {
227 new (buf + i) T();
228 }
229 }
230
234 ConcurrentVector(size_t startSize, const T& defaultValue)
235 : ConcurrentVector(startSize, ReserveTag) {
236 size_.store(startSize, std::memory_order_relaxed);
237 T* buf = buffers_[0].load(std::memory_order_relaxed);
238 for (size_t i = 0; i < startSize; ++i) {
239 new (buf + i) T(defaultValue);
240 }
241 }
242
246 template <typename InIterator>
247 ConcurrentVector(InIterator start, InIterator end)
248 : ConcurrentVector(std::distance(start, end), start, end) {}
249
255 template <typename InIterator>
256 ConcurrentVector(size_type startSize, InIterator start, InIterator end)
257 : ConcurrentVector(startSize, ReserveTag) {
258 size_.store(startSize, std::memory_order_relaxed);
259 assert(std::distance(start, end) == static_cast<difference_type>(startSize));
260 internalInit(start, end, begin());
261 }
262
266 ConcurrentVector(std::initializer_list<T> l)
267 : ConcurrentVector(l.size(), std::begin(l), std::end(l)) {}
268
273 : ConcurrentVector(other.size(), other.cbegin(), other.cend()) {}
274
279 : buffers_(std::move(other.buffers_)),
280 firstBucketShift_(other.firstBucketShift_),
281 firstBucketLen_(other.firstBucketLen_),
282 size_(other.size_.load(std::memory_order_relaxed)) {
283 moveCachedPtrsFrom(other);
284 other.size_.store(0, std::memory_order_relaxed);
285 // This is possibly unnecessary overhead, but enables the "other" vector to be in a valid,
286 // usable state right away, no empty check or clear required, as it is for std::vector.
287 T* firstTwo = cv::alloc<T>(2 * firstBucketLen_);
288 other.setCachedPtr(0, firstTwo);
289 other.setCachedPtr(1, firstTwo + firstBucketLen_);
290 other.buffers_[0].store(firstTwo, std::memory_order_relaxed);
291 other.buffers_[1].store(firstTwo + firstBucketLen_, std::memory_order_relaxed);
292 }
293
297 ConcurrentVector& operator=(const ConcurrentVector& other) {
298 if (&other == this) {
299 return *this;
300 }
301
302 clear();
303 reserve(other.size());
304 size_.store(other.size(), std::memory_order_relaxed);
305 internalInit(other.cbegin(), other.cend(), begin());
306
307 return *this;
308 }
309
313 ConcurrentVector& operator=(ConcurrentVector&& other) {
314 using std::swap;
315 if (&other == this) {
316 return *this;
317 }
318
319 clear();
320 swap(firstBucketShift_, other.firstBucketShift_);
321 swap(firstBucketLen_, other.firstBucketLen_);
322 buffers_ = std::move(other.buffers_);
323 swapCachedPtrs(other);
324 size_t curLen = size_.load(std::memory_order_relaxed);
325 size_.store(other.size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
326 other.size_.store(curLen, std::memory_order_relaxed);
327
328 return *this;
329 }
330
337 void assign(size_type count, const T& value) {
338 clear();
339 reserve(count);
340 size_.store(count, std::memory_order_relaxed);
341 internalFillN(begin(), count, value);
342 }
343
350 template <
351 typename It,
352 typename = typename std::iterator_traits<It>::difference_type,
353 typename = typename std::iterator_traits<It>::pointer,
354 typename = typename std::iterator_traits<It>::reference,
355 typename = typename std::iterator_traits<It>::value_type,
356 typename = typename std::iterator_traits<It>::iterator_category>
357 void assign(It start, It end) {
358 clear();
359 auto count = std::distance(start, end);
360 reserve(count);
361 size_.store(count, std::memory_order_relaxed);
362 internalInit(start, end, begin());
363 }
364
370 void reserve(difference_type capacity) {
371 auto bend = bucketAndSubIndex(capacity);
372 allocateBufferRange({0, 0, firstBucketLen_}, capacity, bend);
373 }
374
380 void resize(difference_type len) {
381 difference_type curLen = static_cast<difference_type>(size_.load(std::memory_order_relaxed));
382 if (curLen < len) {
383 grow_to_at_least(len);
384 } else if (curLen > len) {
385 auto it = end();
386 auto newEnd = begin() + len;
387 do {
388 --it;
389 it->~T();
390 } while (it != newEnd);
391 size_.store(len, std::memory_order_relaxed);
392 }
393 }
394
401 void resize(difference_type len, const T& value) {
402 difference_type curLen = static_cast<difference_type>(size_.load(std::memory_order_relaxed));
403 if (curLen < len) {
404 grow_to_at_least(len, value);
405 } else if (curLen > len) {
406 auto it = end();
407 auto newEnd = begin() + len;
408 do {
409 --it;
410 it->~T();
411 } while (it != newEnd);
412 size_.store(len, std::memory_order_relaxed);
413 }
414 }
415
420 size_type default_capacity() const {
421 return 2 * firstBucketLen_;
422 }
423
428 size_type capacity() const {
429 size_t cap = 2 * firstBucketLen_;
430 for (size_t b = 2; b < kMaxBuffers; ++b) {
431 if (!buffers_[b].load(std::memory_order_relaxed)) {
432 break;
433 }
434 cap *= 2;
435 }
436 return cap;
437 }
438
443 void clear() {
444 auto binfo = bucketAndSubIndex(size_.load(std::memory_order_relaxed));
445 size_t len = binfo.bucketIndex;
446 size_t cap = binfo.bucketCapacity;
447 size_t b = binfo.bucket;
448 do {
449 T* buf = buffers_[b].load(std::memory_order_relaxed);
450 T* t = buf + len;
451
452 while (t != buf) {
453 --t;
454 t->~T();
455 }
456 cap >>= int{b > 1};
457 len = cap;
458 } while (b--);
459 size_.store(0, std::memory_order_release);
460 }
461
467 constexpr size_t kMaxExtra = 2;
468 auto binfo = bucketAndSubIndex(size_.load(std::memory_order_relaxed));
469
470 // We need to at least skip the first two buckets, since we have those as a single allocation.
471 size_t startBucket = std::max<size_t>(2, binfo.bucket + kMaxExtra);
472
473 for (size_t b = startBucket; b < kMaxBuffers; ++b) {
474 T* ptr = buffers_[b].load(std::memory_order_relaxed);
475 if (!ptr) {
476 break;
477 }
478 if (buffers_.shouldDealloc(b)) {
479 cv::dealloc<T>(ptr);
480 }
481 clearCachedPtr(b);
482 buffers_[b].store(nullptr, std::memory_order_release);
483 }
484 }
485
490 clear();
492 cv::dealloc<T>(buffers_[0].load(std::memory_order_acquire));
493 }
494
501 iterator insert(const_iterator pos, const T& value) {
502 auto it = insertPartial(pos);
503 new (&*it) T(value);
504 return it;
505 }
506
513 iterator insert(const_iterator pos, T&& value) {
514 auto it = insertPartial(pos);
515 new (&*it) T(std::move(value));
516 return it;
517 }
518
526 iterator insert(const_iterator pos, size_type count, const T& value) {
527 auto it = insertPartial(pos, count);
528 std::fill_n(it, count, value);
529 return it;
530 }
531
539 template <
540 typename InputIt,
541 typename = typename std::iterator_traits<InputIt>::difference_type,
542 typename = typename std::iterator_traits<InputIt>::pointer,
543 typename = typename std::iterator_traits<InputIt>::reference,
544 typename = typename std::iterator_traits<InputIt>::value_type,
545 typename = typename std::iterator_traits<InputIt>::iterator_category>
546 iterator insert(const_iterator pos, InputIt first, InputIt last) {
547 size_t len = std::distance(first, last);
548 auto it = insertPartial(pos, len);
549 std::copy_n(first, len, it);
550 return it;
551 }
552
559 iterator insert(const_iterator pos, std::initializer_list<T> ilist) {
560 return insert(pos, ilist.begin(), ilist.end());
561 }
562
569 iterator erase(const_iterator pos) {
570 auto e = end();
571 if (e == pos) {
572 return e;
573 }
574 size_.fetch_sub(1, std::memory_order_relaxed);
575 --e;
576 if (e == pos) {
577 e->~T();
578 return e;
579 }
580 ++e;
581 auto it = begin();
582 it += (pos - it);
583 return std::move(pos + 1, const_iterator(e), it);
584 }
585
594 iterator erase(const_iterator first, const_iterator last) {
595 size_t len = std::distance(first, last);
596 auto it = begin();
597 size_t startIdx = first - it;
598 if (len == 0) {
599 return it + (startIdx + len);
600 }
601 it += startIdx;
602
603 auto e_it = std::move(last, cend(), it);
604
605 if (e_it < last) {
606 // remove any values that were not already moved into
607 do {
608 --last;
609 last->~T();
610 } while (e_it != last);
611 }
612 size_.fetch_sub(len, std::memory_order_relaxed);
613 return e_it;
614 }
615
621 iterator push_back(const T& val) {
622 return emplace_back(val);
623 }
624
630 iterator push_back(T&& val) {
631 return emplace_back(std::move(val));
632 }
633
639 template <typename... Args>
640 iterator emplace_back(Args&&... args) {
641 auto index = size_.fetch_add(1, std::memory_order_relaxed);
642 auto binfo = bucketAndSubIndex(index);
643
644 allocateBuffer(binfo);
645
646 iterator ret{this, index, binfo};
647
648 if (Traits::kIteratorPreferSpeed) {
649 new (&*ret) T(std::forward<Args>(args)...);
650 } else {
651 new (buffers_[binfo.bucket] + binfo.bucketIndex) T(std::forward<Args>(args)...);
652 }
653
654 return ret;
655 }
656
664 template <typename Gen>
665 iterator grow_by_generator(size_type delta, Gen gen) {
666 iterator ret = growByUninitialized(delta);
667 for (auto it = ret; delta--; ++it) {
668 new (&*it) T(gen());
669 }
670 return ret;
671 }
672
679 iterator grow_by(size_type delta, const T& t) {
680 iterator ret = growByUninitialized(delta);
681 internalFillN(ret, delta, t);
682 return ret;
683 }
684
690 iterator grow_by(size_type delta) {
691 iterator ret = growByUninitialized(delta);
692 internalFillDefaultN(ret, delta);
693 return ret;
694 }
695
702 template <
703 typename It,
704 typename = typename std::iterator_traits<It>::difference_type,
705 typename = typename std::iterator_traits<It>::pointer,
706 typename = typename std::iterator_traits<It>::reference,
707 typename = typename std::iterator_traits<It>::value_type,
708 typename = typename std::iterator_traits<It>::iterator_category>
709 iterator grow_by(It start, It end) {
710 iterator ret = growByUninitialized(std::distance(start, end));
711 internalInit(start, end, ret);
712 return ret;
713 }
714
720 iterator grow_by(std::initializer_list<T> initList) {
721 return grow_by(std::begin(initList), std::end(initList));
722 }
723
730 iterator grow_to_at_least(size_type n) {
731 size_t curSize = size_.load(std::memory_order_relaxed);
732 if (curSize < n) {
733 return grow_by(n - curSize);
734 }
735 return {this, n - 1, bucketAndSubIndex(n - 1)};
736 }
737
745 iterator grow_to_at_least(size_type n, const T& t) {
746 size_t curSize = size_.load(std::memory_order_relaxed);
747 if (curSize < n) {
748 return grow_by(n - curSize, t);
749 }
750 return {this, n - 1, bucketAndSubIndex(n - 1)};
751 }
752
756 void pop_back() {
757 auto binfo = bucketAndSubIndex(size_.fetch_sub(1, std::memory_order_relaxed) - 1);
758 T* elt = buffers_[binfo.bucket].load(std::memory_order_relaxed) + binfo.bucketIndex;
759 elt->~T();
760 }
761
770 const T& operator[](size_type index) const {
771 auto binfo = bucketAndSubIndexForIndex(index);
772 T* buf = cachedBuffer(binfo.bucket);
773 return buf[binfo.bucketIndex];
774 }
775
784 T& operator[](size_type index) {
785 auto binfo = bucketAndSubIndexForIndex(index);
786 T* buf = cachedBuffer(binfo.bucket);
787 return buf[binfo.bucketIndex];
788 }
789
799 const T& at(size_type index) const {
800#if defined(__cpp_exceptions)
801 if (index >= size_.load(std::memory_order_relaxed)) {
802 throw std::out_of_range("Index too large");
803 }
804#endif
805 return operator[](index);
806 }
807
817 T& at(size_type index) {
818#if defined(__cpp_exceptions)
819 if (index >= size_.load(std::memory_order_relaxed)) {
820 throw std::out_of_range("Index too large");
821 }
822#endif
823 return operator[](index);
824 }
825
833 iterator begin() {
834 return {this, 0, {0, 0, firstBucketLen_}};
835 }
836
845 iterator end() {
846 size_t curSize = size_.load(std::memory_order_relaxed);
847 auto binfo = bucketAndSubIndex(curSize);
848 return {this, curSize, binfo};
849 }
850
858 const_iterator begin() const {
859 return {this, 0, {0, 0, firstBucketLen_}};
860 }
861
870 const_iterator end() const {
871 size_t curSize = size_.load(std::memory_order_relaxed);
872 auto binfo = bucketAndSubIndex(curSize);
873 return {this, curSize, binfo};
874 }
875
883 const_iterator cbegin() const {
884 return {this, 0, {0, 0, firstBucketLen_}};
885 }
886
895 const_iterator cend() const {
896 size_t curSize = size_.load(std::memory_order_relaxed);
897 auto binfo = bucketAndSubIndex(curSize);
898 return {this, curSize, binfo};
899 }
900
909 reverse_iterator rbegin() {
910 return reverse_iterator(end());
911 }
912
920 reverse_iterator rend() {
921 return reverse_iterator(begin());
922 }
923
932 const_reverse_iterator rbegin() const {
933 return const_reverse_iterator(cend());
934 }
935
943 const_reverse_iterator rend() const {
944 return const_reverse_iterator(cbegin());
945 }
946
952 bool empty() const {
953 return size_.load(std::memory_order_relaxed) == 0;
954 }
955
961 constexpr size_type max_size() const noexcept {
962 return Traits::kMaxVectorSize;
963 }
964
970 size_type size() const {
971 return size_.load(std::memory_order_relaxed);
972 }
973
978 T& front() {
979 return *buffers_[0].load(std::memory_order_relaxed);
980 }
981
986 const T& front() const {
987 return *buffers_[0].load(std::memory_order_relaxed);
988 }
989
995 T& back() {
996 return *(end() - 1);
997 }
998
1004 const T& back() const {
1005 return *(end() - 1);
1006 }
1007
1012 void swap(ConcurrentVector& oth) {
1013 using std::swap;
1014 swap(firstBucketShift_, oth.firstBucketShift_);
1015 swap(firstBucketLen_, oth.firstBucketLen_);
1016 size_t othlen = oth.size_.load(std::memory_order_relaxed);
1017 oth.size_.store(size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
1018 size_.store(othlen, std::memory_order_relaxed);
1019
1020 // okay, this relies on the fact that we're essentially swapping in the move operator.
1021 buffers_ = std::move(oth.buffers_);
1022 swapCachedPtrs(oth);
1023 }
1024
1025 private:
1026 DISPENSO_INLINE cv::BucketInfo bucketAndSubIndexForIndex(size_t index) const {
1027#if defined(_MSC_VER) || defined(__aarch64__) || defined(_M_ARM64)
1028 // Branching fast path — benefits MSVC and ARM where branch prediction handles
1029 // sequential access patterns well, avoiding log2/division for the common case.
1030 if (index < firstBucketLen_) {
1031 return {0, index, firstBucketLen_};
1032 }
1033
1034 size_t l2idx = detail::log2(index);
1035 size_t bucket = (l2idx + 1) - firstBucketShift_;
1036 size_t bucketCapacity = size_t{1} << l2idx;
1037 size_t bucketIndex = index - bucketCapacity;
1038
1039 return {bucket, bucketIndex, bucketCapacity};
1040#else
1041 return bucketAndSubIndex(index);
1042#endif // _MSC_VER || __aarch64__ || _M_ARM64
1043 }
1044
1045 DISPENSO_INLINE cv::BucketInfo bucketAndSubIndex(size_t index) const {
1046 size_t l2idx = detail::log2(index | 1);
1047 size_t bucket = (l2idx + 1) - firstBucketShift_;
1048 size_t bucketCapacity = size_t{1} << l2idx;
1049 size_t bucketIndex = index - bucketCapacity;
1050
1051 bucket = index < firstBucketLen_ ? 0 : bucket;
1052 bucketIndex = index < firstBucketLen_ ? index : bucketIndex;
1053 bucketCapacity = index < firstBucketLen_ ? firstBucketLen_ : bucketCapacity;
1054
1055 return {bucket, bucketIndex, bucketCapacity};
1056 }
1057
1058 template <typename InIterator>
1059 void internalInit(InIterator start, InIterator end, iterator it) {
1060 while (start != end) {
1061 new (&*it) T(*start);
1062 ++it;
1063 ++start;
1064 }
1065 }
1066
1067 void internalFillN(iterator it, size_t len, const T& value) {
1068 for (; len--; ++it) {
1069 new (&*it) T(value);
1070 }
1071 }
1072
1073 void internalFillDefaultN(iterator it, size_t len) {
1074 for (; len--; ++it) {
1075 new (&*it) T();
1076 }
1077 }
1078
1079 iterator growByUninitialized(size_type delta) {
1080 auto index = size_.fetch_add(delta, std::memory_order_relaxed);
1081 auto binfo = bucketAndSubIndex(index);
1082 auto bend = bucketAndSubIndex(index + delta);
1083 allocateBufferRange(binfo, delta, bend);
1084 return {this, index, binfo};
1085 }
1086
1087 iterator insertPartial(const_iterator pos) {
1088 auto e = end();
1089 auto index = size_.fetch_add(1, std::memory_order_relaxed);
1090 auto binfo = bucketAndSubIndex(index);
1091 allocateBuffer(binfo);
1092 new (&*e) T();
1093 return std::move_backward(pos, const_iterator(e), e + 1) - 1;
1094 }
1095
1096 iterator insertPartial(const_iterator pos, size_t len) {
1097 auto e = end();
1098 auto index = size_.fetch_add(len, std::memory_order_relaxed);
1099 auto binfo = bucketAndSubIndex(index);
1100 auto bend = bucketAndSubIndex(index + len);
1101 allocateBufferRange(binfo, len, bend);
1102 for (auto it = e + len; it != e;) {
1103 --it;
1104 new (&*it) T();
1105 }
1106 return std::move_backward(pos, const_iterator(e), e + len) - len;
1107 }
1108
1109 alignas(kCacheLineSize) cv::ConVecBuffer<
1110 T,
1111 SizeTraits::kDefaultCapacity / 2,
1112 SizeTraits::kMaxVectorSize,
1113 Traits::kPreferBuffersInline,
1114 Traits::kReallocStrategy> buffers_;
1115
1116 static constexpr size_t kMaxBuffers =
1117 detail::log2const(SizeTraits::kMaxVectorSize / (SizeTraits::kDefaultCapacity / 2)) + 1;
1118
1119#if DISPENSO_HAS_CACHED_PTRS
1120 // Non-atomic cache of buffer pointers for read-hot paths. Buffer pointers are write-once
1121 // (never change after allocation) and are stored here BEFORE the release store to buffers_[],
1122 // so any thread that acquires from buffers_[] is guaranteed to see the cached value.
1123 // All concurrent writes to a given slot store the same value.
1124 // Disabled on ARM where cache-line invalidation pressure exceeds the read-path benefit.
1125 alignas(kCacheLineSize) mutable T* cachedPtrs_[kMaxBuffers];
1126#endif
1127
1128 size_t firstBucketShift_;
1129 size_t firstBucketLen_;
1130
1131 alignas(kCacheLineSize) std::atomic<size_t> size_{0};
1132
1133 DISPENSO_INLINE T* cachedBuffer(size_t bucket) const {
1134#if DISPENSO_HAS_CACHED_PTRS
1135 return cachedPtrs_[bucket];
1136#else
1137 // Relaxed is sufficient: callers only access indices that have been fully constructed, which
1138 // requires an external happens-before with the writing thread. That synchronization
1139 // transitively carries the buffer pointer visibility (which was release-stored during
1140 // allocation). This matches the original pre-cache memory ordering and avoids the cost of
1141 // ldar (load-acquire) on AArch64.
1142 return buffers_[bucket].load(std::memory_order_relaxed);
1143#endif
1144 }
1145
1146 DISPENSO_INLINE void allocateBuffer(const cv::BucketInfo& binfo) {
1147#if DISPENSO_HAS_CACHED_PTRS
1148 buffers_.allocAsNecessary(binfo, cachedPtrs_);
1149#else
1150 buffers_.allocAsNecessary(binfo);
1151#endif
1152 }
1153
1154 DISPENSO_INLINE void
1155 allocateBufferRange(const cv::BucketInfo& binfo, ssize_t rangeLen, const cv::BucketInfo& bend) {
1156#if DISPENSO_HAS_CACHED_PTRS
1157 buffers_.allocAsNecessary(binfo, rangeLen, bend, cachedPtrs_);
1158#else
1159 buffers_.allocAsNecessary(binfo, rangeLen, bend);
1160#endif
1161 }
1162
1163 DISPENSO_INLINE void initCachedPtrs() {
1164#if DISPENSO_HAS_CACHED_PTRS
1165 for (size_t i = 0; i < kMaxBuffers; ++i) {
1166 cachedPtrs_[i] = nullptr;
1167 }
1168#endif
1169 }
1170
1171 DISPENSO_INLINE void setCachedPtr(size_t bucket, T* ptr) {
1172#if DISPENSO_HAS_CACHED_PTRS
1173 cachedPtrs_[bucket] = ptr;
1174#else
1175 (void)bucket;
1176 (void)ptr;
1177#endif
1178 }
1179
1180 DISPENSO_INLINE void clearCachedPtr(size_t bucket) {
1181#if DISPENSO_HAS_CACHED_PTRS
1182 cachedPtrs_[bucket] = nullptr;
1183#else
1184 (void)bucket;
1185#endif
1186 }
1187
1188 DISPENSO_INLINE void moveCachedPtrsFrom(ConcurrentVector& other) {
1189#if DISPENSO_HAS_CACHED_PTRS
1190 for (size_t i = 0; i < kMaxBuffers; ++i) {
1191 cachedPtrs_[i] = other.cachedPtrs_[i];
1192 other.cachedPtrs_[i] = nullptr;
1193 }
1194#else
1195 (void)other;
1196#endif
1197 }
1198
1199 DISPENSO_INLINE void swapCachedPtrs(ConcurrentVector& other) {
1200#if DISPENSO_HAS_CACHED_PTRS
1201 for (size_t i = 0; i < kMaxBuffers; ++i) {
1202 T* cur = cachedPtrs_[i];
1203 cachedPtrs_[i] = other.cachedPtrs_[i];
1204 other.cachedPtrs_[i] = cur;
1205 }
1206#else
1207 (void)other;
1208#endif
1209 }
1210
1211 friend class cv::ConVecIterBase<ConcurrentVector<T, Traits>, T>;
1212 friend class cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, false>;
1213 friend class cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, true>;
1214};
1215
1216template <typename T, class Traits1, class Traits2>
1217inline bool operator==(
1218 const ConcurrentVector<T, Traits1>& a,
1219 const ConcurrentVector<T, Traits2>& b) {
1220 return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
1221}
1222
1223template <typename T, class Traits1, class Traits2>
1224inline bool operator!=(
1225 const ConcurrentVector<T, Traits1>& a,
1226 const ConcurrentVector<T, Traits2>& b) {
1227 return !(a == b);
1228}
1229
1230template <typename T, class Traits1, class Traits2>
1231inline bool operator<(
1232 const ConcurrentVector<T, Traits1>& a,
1233 const ConcurrentVector<T, Traits2>& b) {
1234 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
1235}
1236
1237template <typename T, class Traits1, class Traits2>
1238inline bool operator>(
1239 const ConcurrentVector<T, Traits1>& a,
1240 const ConcurrentVector<T, Traits2>& b) {
1241 return b < a;
1242}
1243
1244template <typename T, class Traits1, class Traits2>
1245inline bool operator<=(
1246 const ConcurrentVector<T, Traits1>& a,
1247 const ConcurrentVector<T, Traits2>& b) {
1248 return !(b < a);
1249}
1250
1251template <typename T, class Traits1, class Traits2>
1252inline bool operator>=(
1253 const ConcurrentVector<T, Traits1>& a,
1254 const ConcurrentVector<T, Traits2>& b) {
1255 return !(a < b);
1256}
1257
1258template <typename T, class Traits>
1259inline void swap(ConcurrentVector<T, Traits>& a, ConcurrentVector<T, Traits>& b) {
1260 a.swap(b);
1261}
1262
1263// Textual inclusion. Includes undocumented implementation details, e.g. iterators.
1264#include <dispenso/detail/concurrent_vector_impl2.h>
1265
1266} // namespace dispenso
iterator insert(const_iterator pos, const T &value)
iterator grow_by(It start, It end)
const_reverse_iterator rend() const
iterator emplace_back(Args &&... args)
iterator push_back(const T &val)
iterator erase(const_iterator first, const_iterator last)
const_iterator end() const
ConcurrentVector(size_t startSize, const T &defaultValue)
void assign(size_type count, const T &value)
void resize(difference_type len)
void reserve(difference_type capacity)
iterator insert(const_iterator pos, T &&value)
iterator insert(const_iterator pos, size_type count, const T &value)
const_reverse_iterator rbegin() const
iterator grow_to_at_least(size_type n)
ConcurrentVector(size_t startSize)
ConcurrentVector(const ConcurrentVector &other)
iterator grow_to_at_least(size_type n, const T &t)
const_iterator cend() const
constexpr size_type max_size() const noexcept
void resize(difference_type len, const T &value)
const_iterator begin() const
void assign(It start, It end)
const_iterator cbegin() const
size_type default_capacity() const
iterator insert(const_iterator pos, InputIt first, InputIt last)
iterator grow_by(std::initializer_list< T > initList)
ConcurrentVector(size_type startSize, InIterator start, InIterator end)
ConcurrentVector(std::initializer_list< T > l)
iterator insert(const_iterator pos, std::initializer_list< T > ilist)
iterator grow_by(size_type delta)
iterator grow_by_generator(size_type delta, Gen gen)
ConcurrentVector(InIterator start, InIterator end)
ConcurrentVector(size_t startCapacity, ReserveTagS)
iterator erase(const_iterator pos)
const T & at(size_type index) const
ConcurrentVector(ConcurrentVector &&other)
iterator grow_by(size_type delta, const T &t)
ConcurrentVectorReallocStrategy
constexpr ReserveTagS ReserveTag
constexpr size_t kCacheLineSize
A constant that defines a safe number of bytes+alignment to avoid false sharing.
Definition platform.h:97
static constexpr size_t kMaxVectorSize
The maximum possible size for the vector.
static constexpr bool kPreferBuffersInline
Prefer to place the pointers to the buffers inline in the class.
static constexpr ConcurrentVectorReallocStrategy kReallocStrategy
How far/whether to allocate before memory is required.
static constexpr bool kIteratorPreferSpeed
Should we prefer faster, but larger iterators, or slower, but smaller iterators.
uint32_t log2(uint64_t v)
Compute log base 2 of a value (runtime).
Definition util.h:193
constexpr uint64_t nextPow2(uint64_t v)
Round up to the next power of 2.
Definition util.h:151