dispenso 1.5.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(DISPENSO_HAS_CACHED_PTRS)
73#if !defined(__aarch64__) && !defined(_M_ARM64)
74#define DISPENSO_HAS_CACHED_PTRS 1
75#else
76#define DISPENSO_HAS_CACHED_PTRS 0
77#endif
78#endif
79
80namespace dispenso {
81
93enum class ConcurrentVectorReallocStrategy { kFullBufferAhead, kHalfBufferAhead, kAsNeeded };
94
95struct ReserveTagS {};
101
102// Textual inclusion. Includes undocumented implementation details, e.g. iterators.
103#include <dispenso/detail/concurrent_vector_impl.h>
104
109template <typename T>
115 static constexpr size_t kDefaultCapacity = (sizeof(T) >= 256) ? 2 : 512 / sizeof(T);
116
124 static constexpr size_t kMaxVectorSize =
125 (size_t{1} << (sizeof(size_t) * CHAR_BIT > 47 ? 47 : sizeof(size_t) * CHAR_BIT - 1)) /
126 sizeof(T);
127};
128
141 static constexpr bool kPreferBuffersInline = true;
142
154 ConcurrentVectorReallocStrategy::kAsNeeded;
155
165 static constexpr bool kIteratorPreferSpeed = true;
166};
167
173template <
174 typename T,
175 typename Traits = DefaultConcurrentVectorTraits,
176 typename SizeTraits = DefaultConcurrentVectorSizeTraits<T>>
178 public:
179 using value_type = T;
180 using reference = T&;
181 using const_reference = const T&;
182 using size_type = size_t;
183 using difference_type = ssize_t;
184 using reference_type = T&;
185 using const_reference_type = const T&;
186 using pointer = T*;
187 using const_pointer = const T*;
188 using iterator = std::conditional_t<
189 Traits::kIteratorPreferSpeed,
190 cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, false>,
191 cv::CompactCVecIterator<ConcurrentVector<T, Traits>, T, false>>;
192 using const_iterator = std::conditional_t<
193 Traits::kIteratorPreferSpeed,
194 cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, true>,
195 cv::CompactCVecIterator<ConcurrentVector<T, Traits>, T, true>>;
196 using reverse_iterator = std::reverse_iterator<iterator>;
197 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
198
202 ConcurrentVector() : ConcurrentVector(SizeTraits::kDefaultCapacity / 2, ReserveTag) {}
203
209 ConcurrentVector(size_t startCapacity, ReserveTagS)
210 : firstBucketShift_(
211 detail::log2(
212 detail::nextPow2(std::max(startCapacity, SizeTraits::kDefaultCapacity / 2)))),
213 firstBucketLen_(size_type{1} << firstBucketShift_) {
214 T* firstTwo = cv::alloc<T>(2 * firstBucketLen_);
215 initCachedPtrs();
216 setCachedPtr(0, firstTwo);
217 setCachedPtr(1, firstTwo + firstBucketLen_);
218 buffers_[0].store(firstTwo, std::memory_order_release);
219 buffers_[1].store(firstTwo + firstBucketLen_, std::memory_order_release);
220 }
221
225 explicit ConcurrentVector(size_t startSize) : ConcurrentVector(startSize, ReserveTag) {
226 size_.store(startSize, std::memory_order_relaxed);
227 T* buf = buffers_[0].load(std::memory_order_relaxed);
228 for (size_t i = 0; i < startSize; ++i) {
229 new (buf + i) T();
230 }
231 }
232
236 ConcurrentVector(size_t startSize, const T& defaultValue)
237 : ConcurrentVector(startSize, ReserveTag) {
238 size_.store(startSize, std::memory_order_relaxed);
239 T* buf = buffers_[0].load(std::memory_order_relaxed);
240 for (size_t i = 0; i < startSize; ++i) {
241 new (buf + i) T(defaultValue);
242 }
243 }
244
248 template <typename InIterator>
249 ConcurrentVector(InIterator start, InIterator end)
250 : ConcurrentVector(std::distance(start, end), start, end) {}
251
257 template <typename InIterator>
258 ConcurrentVector(size_type startSize, InIterator start, InIterator end)
259 : ConcurrentVector(startSize, ReserveTag) {
260 size_.store(startSize, std::memory_order_relaxed);
261 assert(std::distance(start, end) == static_cast<difference_type>(startSize));
262 internalInit(start, end, begin());
263 }
264
268 ConcurrentVector(std::initializer_list<T> l)
269 : ConcurrentVector(l.size(), std::begin(l), std::end(l)) {}
270
275 : ConcurrentVector(other.size(), other.cbegin(), other.cend()) {}
276
281 : buffers_(std::move(other.buffers_)),
282 firstBucketShift_(other.firstBucketShift_),
283 firstBucketLen_(other.firstBucketLen_),
284 size_(other.size_.load(std::memory_order_relaxed)) {
285 moveCachedPtrsFrom(other);
286 other.size_.store(0, std::memory_order_relaxed);
287 // This is possibly unnecessary overhead, but enables the "other" vector to be in a valid,
288 // usable state right away, no empty check or clear required, as it is for std::vector.
289 T* firstTwo = cv::alloc<T>(2 * firstBucketLen_);
290 other.setCachedPtr(0, firstTwo);
291 other.setCachedPtr(1, firstTwo + firstBucketLen_);
292 other.buffers_[0].store(firstTwo, std::memory_order_relaxed);
293 other.buffers_[1].store(firstTwo + firstBucketLen_, std::memory_order_relaxed);
294 }
295
299 ConcurrentVector& operator=(const ConcurrentVector& other) {
300 if (&other == this) {
301 return *this;
302 }
303
304 clear();
305 reserve(other.size());
306 size_.store(other.size(), std::memory_order_relaxed);
307 internalInit(other.cbegin(), other.cend(), begin());
308
309 return *this;
310 }
311
315 ConcurrentVector& operator=(ConcurrentVector&& other) {
316 using std::swap;
317 if (&other == this) {
318 return *this;
319 }
320
321 clear();
322 swap(firstBucketShift_, other.firstBucketShift_);
323 swap(firstBucketLen_, other.firstBucketLen_);
324 buffers_ = std::move(other.buffers_);
325 swapCachedPtrs(other);
326 size_t curLen = size_.load(std::memory_order_relaxed);
327 size_.store(other.size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
328 other.size_.store(curLen, std::memory_order_relaxed);
329
330 return *this;
331 }
332
339 void assign(size_type count, const T& value) {
340 clear();
341 reserve(count);
342 size_.store(count, std::memory_order_relaxed);
343 internalFillN(begin(), count, value);
344 }
345
352 template <
353 typename It,
354 typename = typename std::iterator_traits<It>::difference_type,
355 typename = typename std::iterator_traits<It>::pointer,
356 typename = typename std::iterator_traits<It>::reference,
357 typename = typename std::iterator_traits<It>::value_type,
358 typename = typename std::iterator_traits<It>::iterator_category>
359 void assign(It start, It end) {
360 clear();
361 auto count = std::distance(start, end);
362 reserve(count);
363 size_.store(count, std::memory_order_relaxed);
364 internalInit(start, end, begin());
365 }
366
372 void reserve(difference_type capacity) {
373 auto bend = bucketAndSubIndex(capacity);
374 allocateBufferRange({0, 0, firstBucketLen_}, capacity, bend);
375 }
376
382 void resize(difference_type len) {
383 difference_type curLen = static_cast<difference_type>(size_.load(std::memory_order_relaxed));
384 if (curLen < len) {
385 grow_to_at_least(len);
386 } else if (curLen > len) {
387 auto it = end();
388 auto newEnd = begin() + len;
389 do {
390 --it;
391 it->~T();
392 } while (it != newEnd);
393 size_.store(len, std::memory_order_relaxed);
394 }
395 }
396
403 void resize(difference_type len, const T& value) {
404 difference_type curLen = static_cast<difference_type>(size_.load(std::memory_order_relaxed));
405 if (curLen < len) {
406 grow_to_at_least(len, value);
407 } else if (curLen > len) {
408 auto it = end();
409 auto newEnd = begin() + len;
410 do {
411 --it;
412 it->~T();
413 } while (it != newEnd);
414 size_.store(len, std::memory_order_relaxed);
415 }
416 }
417
422 size_type default_capacity() const {
423 return 2 * firstBucketLen_;
424 }
425
430 size_type capacity() const {
431 size_t cap = 2 * firstBucketLen_;
432 for (size_t b = 2; b < kMaxBuffers; ++b) {
433 if (!buffers_[b].load(std::memory_order_relaxed)) {
434 break;
435 }
436 cap *= 2;
437 }
438 return cap;
439 }
440
445 void clear() {
446 auto binfo = bucketAndSubIndex(size_.load(std::memory_order_relaxed));
447 size_t len = binfo.bucketIndex;
448 size_t cap = binfo.bucketCapacity;
449 size_t b = binfo.bucket;
450 do {
451 T* buf = buffers_[b].load(std::memory_order_relaxed);
452 T* t = buf + len;
453
454 while (t != buf) {
455 --t;
456 t->~T();
457 }
458 cap >>= int{b > 1};
459 len = cap;
460 } while (b--);
461 size_.store(0, std::memory_order_release);
462 }
463
469 constexpr size_t kMaxExtra = 2;
470 auto binfo = bucketAndSubIndex(size_.load(std::memory_order_relaxed));
471
472 // We need to at least skip the first two buckets, since we have those as a single allocation.
473 size_t startBucket = std::max<size_t>(2, binfo.bucket + kMaxExtra);
474
475 for (size_t b = startBucket; b < kMaxBuffers; ++b) {
476 T* ptr = buffers_[b].load(std::memory_order_relaxed);
477 if (!ptr) {
478 break;
479 }
480 if (buffers_.shouldDealloc(b)) {
481 cv::dealloc<T>(ptr);
482 }
483 clearCachedPtr(b);
484 buffers_[b].store(nullptr, std::memory_order_release);
485 }
486 }
487
492 clear();
494 cv::dealloc<T>(buffers_[0].load(std::memory_order_acquire));
495 }
496
503 iterator insert(const_iterator pos, const T& value) {
504 auto it = insertPartial(pos);
505 new (&*it) T(value);
506 return it;
507 }
508
515 iterator insert(const_iterator pos, T&& value) {
516 auto it = insertPartial(pos);
517 new (&*it) T(std::move(value));
518 return it;
519 }
520
528 iterator insert(const_iterator pos, size_type count, const T& value) {
529 auto it = insertPartial(pos, count);
530 std::fill_n(it, count, value);
531 return it;
532 }
533
541 template <
542 typename InputIt,
543 typename = typename std::iterator_traits<InputIt>::difference_type,
544 typename = typename std::iterator_traits<InputIt>::pointer,
545 typename = typename std::iterator_traits<InputIt>::reference,
546 typename = typename std::iterator_traits<InputIt>::value_type,
547 typename = typename std::iterator_traits<InputIt>::iterator_category>
548 iterator insert(const_iterator pos, InputIt first, InputIt last) {
549 size_t len = std::distance(first, last);
550 auto it = insertPartial(pos, len);
551 std::copy_n(first, len, it);
552 return it;
553 }
554
561 iterator insert(const_iterator pos, std::initializer_list<T> ilist) {
562 return insert(pos, ilist.begin(), ilist.end());
563 }
564
571 iterator erase(const_iterator pos) {
572 auto e = end();
573 if (e == pos) {
574 return e;
575 }
576 size_.fetch_sub(1, std::memory_order_relaxed);
577 --e;
578 if (e == pos) {
579 e->~T();
580 return e;
581 }
582 ++e;
583 auto it = begin();
584 it += (pos - it);
585 return std::move(pos + 1, const_iterator(e), it);
586 }
587
596 iterator erase(const_iterator first, const_iterator last) {
597 size_t len = std::distance(first, last);
598 auto it = begin();
599 size_t startIdx = first - it;
600 if (len == 0) {
601 return it + (startIdx + len);
602 }
603 it += startIdx;
604
605 auto e_it = std::move(last, cend(), it);
606
607 if (e_it < last) {
608 // remove any values that were not already moved into
609 do {
610 --last;
611 last->~T();
612 } while (e_it != last);
613 }
614 size_.fetch_sub(len, std::memory_order_relaxed);
615 return e_it;
616 }
617
623 iterator push_back(const T& val) {
624 return emplace_back(val);
625 }
626
632 iterator push_back(T&& val) {
633 return emplace_back(std::move(val));
634 }
635
641 template <typename... Args>
642 iterator emplace_back(Args&&... args) {
643 auto index = size_.fetch_add(1, std::memory_order_relaxed);
644 auto binfo = bucketAndSubIndex(index);
645
646 allocateBuffer(binfo);
647
648 iterator ret{this, index, binfo};
649
650 if (Traits::kIteratorPreferSpeed) {
651 new (&*ret) T(std::forward<Args>(args)...);
652 } else {
653 new (buffers_[binfo.bucket] + binfo.bucketIndex) T(std::forward<Args>(args)...);
654 }
655
656 return ret;
657 }
658
666 template <typename Gen>
667 iterator grow_by_generator(size_type delta, Gen gen) {
668 iterator ret = growByUninitialized(delta);
669 for (auto it = ret; delta--; ++it) {
670 new (&*it) T(gen());
671 }
672 return ret;
673 }
674
681 iterator grow_by(size_type delta, const T& t) {
682 iterator ret = growByUninitialized(delta);
683 internalFillN(ret, delta, t);
684 return ret;
685 }
686
692 iterator grow_by(size_type delta) {
693 iterator ret = growByUninitialized(delta);
694 internalFillDefaultN(ret, delta);
695 return ret;
696 }
697
704 template <
705 typename It,
706 typename = typename std::iterator_traits<It>::difference_type,
707 typename = typename std::iterator_traits<It>::pointer,
708 typename = typename std::iterator_traits<It>::reference,
709 typename = typename std::iterator_traits<It>::value_type,
710 typename = typename std::iterator_traits<It>::iterator_category>
711 iterator grow_by(It start, It end) {
712 iterator ret = growByUninitialized(std::distance(start, end));
713 internalInit(start, end, ret);
714 return ret;
715 }
716
722 iterator grow_by(std::initializer_list<T> initList) {
723 return grow_by(std::begin(initList), std::end(initList));
724 }
725
732 iterator grow_to_at_least(size_type n) {
733 size_t curSize = size_.load(std::memory_order_relaxed);
734 if (curSize < n) {
735 return grow_by(n - curSize);
736 }
737 return {this, n - 1, bucketAndSubIndex(n - 1)};
738 }
739
747 iterator grow_to_at_least(size_type n, const T& t) {
748 size_t curSize = size_.load(std::memory_order_relaxed);
749 if (curSize < n) {
750 return grow_by(n - curSize, t);
751 }
752 return {this, n - 1, bucketAndSubIndex(n - 1)};
753 }
754
758 void pop_back() {
759 auto binfo = bucketAndSubIndex(size_.fetch_sub(1, std::memory_order_relaxed) - 1);
760 T* elt = buffers_[binfo.bucket].load(std::memory_order_relaxed) + binfo.bucketIndex;
761 elt->~T();
762 }
763
772 const T& operator[](size_type index) const {
773 auto binfo = bucketAndSubIndexForIndex(index);
774 T* buf = cachedBuffer(binfo.bucket);
775 return buf[binfo.bucketIndex];
776 }
777
786 T& operator[](size_type index) {
787 auto binfo = bucketAndSubIndexForIndex(index);
788 T* buf = cachedBuffer(binfo.bucket);
789 return buf[binfo.bucketIndex];
790 }
791
801 const T& at(size_type index) const {
802#if defined(__cpp_exceptions)
803 if (index >= size_.load(std::memory_order_relaxed)) {
804 throw std::out_of_range("Index too large");
805 }
806#endif
807 return operator[](index);
808 }
809
819 T& at(size_type index) {
820#if defined(__cpp_exceptions)
821 if (index >= size_.load(std::memory_order_relaxed)) {
822 throw std::out_of_range("Index too large");
823 }
824#endif
825 return operator[](index);
826 }
827
835 iterator begin() {
836 return {this, 0, {0, 0, firstBucketLen_}};
837 }
838
847 iterator end() {
848 size_t curSize = size_.load(std::memory_order_relaxed);
849 auto binfo = bucketAndSubIndex(curSize);
850 return {this, curSize, binfo};
851 }
852
860 const_iterator begin() const {
861 return {this, 0, {0, 0, firstBucketLen_}};
862 }
863
872 const_iterator end() const {
873 size_t curSize = size_.load(std::memory_order_relaxed);
874 auto binfo = bucketAndSubIndex(curSize);
875 return {this, curSize, binfo};
876 }
877
885 const_iterator cbegin() const {
886 return {this, 0, {0, 0, firstBucketLen_}};
887 }
888
897 const_iterator cend() const {
898 size_t curSize = size_.load(std::memory_order_relaxed);
899 auto binfo = bucketAndSubIndex(curSize);
900 return {this, curSize, binfo};
901 }
902
911 reverse_iterator rbegin() {
912 return reverse_iterator(end());
913 }
914
922 reverse_iterator rend() {
923 return reverse_iterator(begin());
924 }
925
934 const_reverse_iterator rbegin() const {
935 return const_reverse_iterator(cend());
936 }
937
945 const_reverse_iterator rend() const {
946 return const_reverse_iterator(cbegin());
947 }
948
954 bool empty() const {
955 return size_.load(std::memory_order_relaxed) == 0;
956 }
957
963 constexpr size_type max_size() const noexcept {
964 return Traits::kMaxVectorSize;
965 }
966
972 size_type size() const {
973 return size_.load(std::memory_order_relaxed);
974 }
975
980 T& front() {
981 return *buffers_[0].load(std::memory_order_relaxed);
982 }
983
988 const T& front() const {
989 return *buffers_[0].load(std::memory_order_relaxed);
990 }
991
997 T& back() {
998 return *(end() - 1);
999 }
1000
1006 const T& back() const {
1007 return *(end() - 1);
1008 }
1009
1014 void swap(ConcurrentVector& oth) {
1015 using std::swap;
1016 swap(firstBucketShift_, oth.firstBucketShift_);
1017 swap(firstBucketLen_, oth.firstBucketLen_);
1018 size_t othlen = oth.size_.load(std::memory_order_relaxed);
1019 oth.size_.store(size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
1020 size_.store(othlen, std::memory_order_relaxed);
1021
1022 // okay, this relies on the fact that we're essentially swapping in the move operator.
1023 buffers_ = std::move(oth.buffers_);
1024 swapCachedPtrs(oth);
1025 }
1026
1027 private:
1028 DISPENSO_INLINE cv::BucketInfo bucketAndSubIndexForIndex(size_t index) const {
1029#if defined(_MSC_VER) || defined(__aarch64__) || defined(_M_ARM64)
1030 // Branching fast path — benefits MSVC and ARM where branch prediction handles
1031 // sequential access patterns well, avoiding log2/division for the common case.
1032 if (index < firstBucketLen_) {
1033 return {0, index, firstBucketLen_};
1034 }
1035
1036 size_t l2idx = detail::log2(index);
1037 size_t bucket = (l2idx + 1) - firstBucketShift_;
1038 size_t bucketCapacity = size_t{1} << l2idx;
1039 size_t bucketIndex = index - bucketCapacity;
1040
1041 return {bucket, bucketIndex, bucketCapacity};
1042#else
1043 return bucketAndSubIndex(index);
1044#endif // _MSC_VER || __aarch64__ || _M_ARM64
1045 }
1046
1047 DISPENSO_INLINE cv::BucketInfo bucketAndSubIndex(size_t index) const {
1048 size_t l2idx = detail::log2(index | 1);
1049 size_t bucket = (l2idx + 1) - firstBucketShift_;
1050 size_t bucketCapacity = size_t{1} << l2idx;
1051 size_t bucketIndex = index - bucketCapacity;
1052
1053 bucket = index < firstBucketLen_ ? 0 : bucket;
1054 bucketIndex = index < firstBucketLen_ ? index : bucketIndex;
1055 bucketCapacity = index < firstBucketLen_ ? firstBucketLen_ : bucketCapacity;
1056
1057 return {bucket, bucketIndex, bucketCapacity};
1058 }
1059
1060 template <typename InIterator>
1061 void internalInit(InIterator start, InIterator end, iterator it) {
1062 while (start != end) {
1063 new (&*it) T(*start);
1064 ++it;
1065 ++start;
1066 }
1067 }
1068
1069 void internalFillN(iterator it, size_t len, const T& value) {
1070 for (; len--; ++it) {
1071 new (&*it) T(value);
1072 }
1073 }
1074
1075 void internalFillDefaultN(iterator it, size_t len) {
1076 for (; len--; ++it) {
1077 new (&*it) T();
1078 }
1079 }
1080
1081 iterator growByUninitialized(size_type delta) {
1082 auto index = size_.fetch_add(delta, std::memory_order_relaxed);
1083 auto binfo = bucketAndSubIndex(index);
1084 auto bend = bucketAndSubIndex(index + delta);
1085 allocateBufferRange(binfo, delta, bend);
1086 return {this, index, binfo};
1087 }
1088
1089 iterator insertPartial(const_iterator pos) {
1090 auto e = end();
1091 auto index = size_.fetch_add(1, std::memory_order_relaxed);
1092 auto binfo = bucketAndSubIndex(index);
1093 allocateBuffer(binfo);
1094 new (&*e) T();
1095 return std::move_backward(pos, const_iterator(e), e + 1) - 1;
1096 }
1097
1098 iterator insertPartial(const_iterator pos, size_t len) {
1099 auto e = end();
1100 auto index = size_.fetch_add(len, std::memory_order_relaxed);
1101 auto binfo = bucketAndSubIndex(index);
1102 auto bend = bucketAndSubIndex(index + len);
1103 allocateBufferRange(binfo, len, bend);
1104 for (auto it = e + len; it != e;) {
1105 --it;
1106 new (&*it) T();
1107 }
1108 return std::move_backward(pos, const_iterator(e), e + len) - len;
1109 }
1110
1111 alignas(kCacheLineSize) cv::ConVecBuffer<
1112 T,
1113 SizeTraits::kDefaultCapacity / 2,
1114 SizeTraits::kMaxVectorSize,
1115 Traits::kPreferBuffersInline,
1116 Traits::kReallocStrategy> buffers_;
1117
1118 static constexpr size_t kMaxBuffers =
1119 detail::log2const(SizeTraits::kMaxVectorSize / (SizeTraits::kDefaultCapacity / 2)) + 1;
1120
1121#if DISPENSO_HAS_CACHED_PTRS
1122 // Non-atomic cache of buffer pointers for read-hot paths. Buffer pointers are write-once
1123 // (never change after allocation) and are stored here BEFORE the release store to buffers_[],
1124 // so any thread that acquires from buffers_[] is guaranteed to see the cached value.
1125 // All concurrent writes to a given slot store the same value.
1126 // Disabled on ARM where cache-line invalidation pressure exceeds the read-path benefit.
1127 alignas(kCacheLineSize) mutable T* cachedPtrs_[kMaxBuffers];
1128#endif
1129
1130 size_t firstBucketShift_;
1131 size_t firstBucketLen_;
1132
1133 alignas(kCacheLineSize) std::atomic<size_t> size_{0};
1134
1135 DISPENSO_INLINE T* cachedBuffer(size_t bucket) const {
1136#if DISPENSO_HAS_CACHED_PTRS
1137 return cachedPtrs_[bucket];
1138#else
1139 // Relaxed is sufficient: callers only access indices that have been fully constructed, which
1140 // requires an external happens-before with the writing thread. That synchronization
1141 // transitively carries the buffer pointer visibility (which was release-stored during
1142 // allocation). This matches the original pre-cache memory ordering and avoids the cost of
1143 // ldar (load-acquire) on AArch64.
1144 return buffers_[bucket].load(std::memory_order_relaxed);
1145#endif
1146 }
1147
1148 DISPENSO_INLINE void allocateBuffer(const cv::BucketInfo& binfo) {
1149#if DISPENSO_HAS_CACHED_PTRS
1150 buffers_.allocAsNecessary(binfo, cachedPtrs_);
1151#else
1152 buffers_.allocAsNecessary(binfo);
1153#endif
1154 }
1155
1156 DISPENSO_INLINE void
1157 allocateBufferRange(const cv::BucketInfo& binfo, ssize_t rangeLen, const cv::BucketInfo& bend) {
1158#if DISPENSO_HAS_CACHED_PTRS
1159 buffers_.allocAsNecessary(binfo, rangeLen, bend, cachedPtrs_);
1160#else
1161 buffers_.allocAsNecessary(binfo, rangeLen, bend);
1162#endif
1163 }
1164
1165 DISPENSO_INLINE void initCachedPtrs() {
1166#if DISPENSO_HAS_CACHED_PTRS
1167 for (size_t i = 0; i < kMaxBuffers; ++i) {
1168 cachedPtrs_[i] = nullptr;
1169 }
1170#endif
1171 }
1172
1173 DISPENSO_INLINE void setCachedPtr(size_t bucket, T* ptr) {
1174#if DISPENSO_HAS_CACHED_PTRS
1175 cachedPtrs_[bucket] = ptr;
1176#else
1177 (void)bucket;
1178 (void)ptr;
1179#endif
1180 }
1181
1182 DISPENSO_INLINE void clearCachedPtr(size_t bucket) {
1183#if DISPENSO_HAS_CACHED_PTRS
1184 cachedPtrs_[bucket] = nullptr;
1185#else
1186 (void)bucket;
1187#endif
1188 }
1189
1190 DISPENSO_INLINE void moveCachedPtrsFrom(ConcurrentVector& other) {
1191#if DISPENSO_HAS_CACHED_PTRS
1192 for (size_t i = 0; i < kMaxBuffers; ++i) {
1193 cachedPtrs_[i] = other.cachedPtrs_[i];
1194 other.cachedPtrs_[i] = nullptr;
1195 }
1196#else
1197 (void)other;
1198#endif
1199 }
1200
1201 DISPENSO_INLINE void swapCachedPtrs(ConcurrentVector& other) {
1202#if DISPENSO_HAS_CACHED_PTRS
1203 for (size_t i = 0; i < kMaxBuffers; ++i) {
1204 T* cur = cachedPtrs_[i];
1205 cachedPtrs_[i] = other.cachedPtrs_[i];
1206 other.cachedPtrs_[i] = cur;
1207 }
1208#else
1209 (void)other;
1210#endif
1211 }
1212
1213 friend class cv::ConVecIterBase<ConcurrentVector<T, Traits>, T>;
1214 friend class cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, false>;
1215 friend class cv::ConcurrentVectorIterator<ConcurrentVector<T, Traits>, T, true>;
1216};
1217
1218template <typename T, class Traits1, class Traits2>
1219inline bool operator==(
1220 const ConcurrentVector<T, Traits1>& a,
1221 const ConcurrentVector<T, Traits2>& b) {
1222 return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
1223}
1224
1225template <typename T, class Traits1, class Traits2>
1226inline bool operator!=(
1227 const ConcurrentVector<T, Traits1>& a,
1228 const ConcurrentVector<T, Traits2>& b) {
1229 return !(a == b);
1230}
1231
1232template <typename T, class Traits1, class Traits2>
1233inline bool operator<(
1234 const ConcurrentVector<T, Traits1>& a,
1235 const ConcurrentVector<T, Traits2>& b) {
1236 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
1237}
1238
1239template <typename T, class Traits1, class Traits2>
1240inline bool operator>(
1241 const ConcurrentVector<T, Traits1>& a,
1242 const ConcurrentVector<T, Traits2>& b) {
1243 return b < a;
1244}
1245
1246template <typename T, class Traits1, class Traits2>
1247inline bool operator<=(
1248 const ConcurrentVector<T, Traits1>& a,
1249 const ConcurrentVector<T, Traits2>& b) {
1250 return !(b < a);
1251}
1252
1253template <typename T, class Traits1, class Traits2>
1254inline bool operator>=(
1255 const ConcurrentVector<T, Traits1>& a,
1256 const ConcurrentVector<T, Traits2>& b) {
1257 return !(a < b);
1258}
1259
1260template <typename T, class Traits>
1261inline void swap(ConcurrentVector<T, Traits>& a, ConcurrentVector<T, Traits>& b) {
1262 a.swap(b);
1263}
1264
1265// Textual inclusion. Includes undocumented implementation details, e.g. iterators.
1266#include <dispenso/detail/concurrent_vector_impl2.h>
1267
1268} // 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