SegmentedVector.h (11243B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // A simple segmented vector class. 8 // 9 // This class should be used in preference to mozilla::Vector or nsTArray when 10 // you are simply gathering items in order to later iterate over them. 11 // 12 // - In the case where you don't know the final size in advance, using 13 // SegmentedVector avoids the need to repeatedly allocate increasingly large 14 // buffers and copy the data into them. 15 // 16 // - In the case where you know the final size in advance and so can set the 17 // capacity appropriately, using SegmentedVector still avoids the need for 18 // large allocations (which can trigger OOMs). 19 20 #ifndef mozilla_SegmentedVector_h 21 #define mozilla_SegmentedVector_h 22 23 #include <new> // for placement new 24 #include <utility> 25 26 #include "mozilla/AllocPolicy.h" 27 #include "mozilla/Attributes.h" 28 #include "mozilla/LinkedList.h" 29 #include "mozilla/MemoryReporting.h" 30 #include "mozilla/OperatorNewExtensions.h" 31 32 #ifdef IMPL_LIBXUL 33 # include "mozilla/Likely.h" 34 # include "mozilla/mozalloc_oom.h" 35 #endif // IMPL_LIBXUL 36 37 namespace mozilla { 38 39 // |IdealSegmentSize| specifies how big each segment will be in bytes (or as 40 // close as is possible). Use the following guidelines to choose a size. 41 // 42 // - It should be a power-of-two, to avoid slop. 43 // 44 // - It should not be too small, so that segment allocations are infrequent, 45 // and so that per-segment bookkeeping overhead is low. Typically each 46 // segment should be able to hold hundreds of elements, at least. 47 // 48 // - It should not be too large, so that OOMs are unlikely when allocating 49 // segments, and so that not too much space is wasted when the final segment 50 // is not full. 51 // 52 // The ideal size depends on how the SegmentedVector is used and the size of 53 // |T|, but reasonable sizes include 1024, 4096 (the default), 8192, and 16384. 54 // 55 template <typename T, size_t IdealSegmentSize = 4096, 56 typename AllocPolicy = MallocAllocPolicy> 57 class SegmentedVector : private AllocPolicy { 58 template <size_t SegmentCapacity> 59 struct SegmentImpl 60 : public mozilla::LinkedListElement<SegmentImpl<SegmentCapacity>> { 61 private: 62 uint32_t mLength; 63 alignas(T) MOZ_INIT_OUTSIDE_CTOR 64 unsigned char mData[sizeof(T) * SegmentCapacity]; 65 66 // Some versions of GCC treat it as a -Wstrict-aliasing violation (ergo a 67 // -Werror compile error) to reinterpret_cast<> |mData| to |T*|, even 68 // through |void*|. Placing the latter cast in these separate functions 69 // breaks the chain such that affected GCC versions no longer warn/error. 70 void* RawData() { return mData; } 71 72 public: 73 SegmentImpl() : mLength(0) {} 74 75 ~SegmentImpl() { 76 for (uint32_t i = 0; i < mLength; i++) { 77 (*this)[i].~T(); 78 } 79 } 80 81 uint32_t Length() const { return mLength; } 82 83 T* Elems() { return reinterpret_cast<T*>(RawData()); } 84 85 T& operator[](size_t aIndex) { 86 MOZ_ASSERT(aIndex < mLength); 87 return Elems()[aIndex]; 88 } 89 90 const T& operator[](size_t aIndex) const { 91 MOZ_ASSERT(aIndex < mLength); 92 return Elems()[aIndex]; 93 } 94 95 template <typename U> 96 void Append(U&& aU) { 97 MOZ_ASSERT(mLength < SegmentCapacity); 98 // Pre-increment mLength so that the bounds-check in operator[] passes. 99 mLength++; 100 T* elem = &(*this)[mLength - 1]; 101 new (KnownNotNull, elem) T(std::forward<U>(aU)); 102 } 103 104 void PopLast() { 105 MOZ_ASSERT(mLength > 0); 106 (*this)[mLength - 1].~T(); 107 mLength--; 108 } 109 }; 110 111 // See how many we elements we can fit in a segment of IdealSegmentSize. If 112 // IdealSegmentSize is too small, it'll be just one. The +1 is because 113 // kSingleElementSegmentSize already accounts for one element. 114 static const size_t kSingleElementSegmentSize = sizeof(SegmentImpl<1>); 115 static const size_t kSegmentCapacity = 116 kSingleElementSegmentSize <= IdealSegmentSize 117 ? (IdealSegmentSize - kSingleElementSegmentSize) / sizeof(T) + 1 118 : 1; 119 120 public: 121 typedef SegmentImpl<kSegmentCapacity> Segment; 122 123 // The |aIdealSegmentSize| is only for sanity checking. If it's specified, we 124 // check that the actual segment size is as close as possible to it. This 125 // serves as a sanity check for SegmentedVectorCapacity's capacity 126 // computation. 127 explicit SegmentedVector(size_t aIdealSegmentSize = 0) { 128 // The difference between the actual segment size and the ideal segment 129 // size should be less than the size of a single element... unless the 130 // ideal size was too small, in which case the capacity should be one. 131 MOZ_ASSERT_IF( 132 aIdealSegmentSize != 0, 133 (sizeof(Segment) > aIdealSegmentSize && kSegmentCapacity == 1) || 134 aIdealSegmentSize - sizeof(Segment) < sizeof(T)); 135 } 136 137 SegmentedVector(SegmentedVector&& aOther) 138 : mSegments(std::move(aOther.mSegments)) {} 139 SegmentedVector& operator=(SegmentedVector&& aOther) { 140 if (&aOther != this) { 141 this->~SegmentedVector(); 142 new (this) SegmentedVector(std::move(aOther)); 143 } 144 return *this; 145 } 146 147 ~SegmentedVector() { Clear(); } 148 149 bool IsEmpty() const { return !mSegments.getFirst(); } 150 151 // Note that this is O(n) rather than O(1), but the constant factor is very 152 // small because it only has to do one addition per segment. 153 size_t Length() const { 154 size_t n = 0; 155 for (auto segment = mSegments.getFirst(); segment; 156 segment = segment->getNext()) { 157 n += segment->Length(); 158 } 159 return n; 160 } 161 162 // Returns false if the allocation failed. (If you are using an infallible 163 // allocation policy, use InfallibleAppend() instead.) 164 template <typename U> 165 [[nodiscard]] bool Append(U&& aU) { 166 Segment* last = mSegments.getLast(); 167 if (!last || last->Length() == kSegmentCapacity) { 168 last = this->template pod_malloc<Segment>(1); 169 if (!last) { 170 return false; 171 } 172 new (KnownNotNull, last) Segment(); 173 mSegments.insertBack(last); 174 } 175 last->Append(std::forward<U>(aU)); 176 return true; 177 } 178 179 // You should probably only use this instead of Append() if you are using an 180 // infallible allocation policy. It will crash if the allocation fails. 181 template <typename U> 182 void InfallibleAppend(U&& aU) { 183 bool ok = Append(std::forward<U>(aU)); 184 185 #ifdef IMPL_LIBXUL 186 if (MOZ_UNLIKELY(!ok)) { 187 mozalloc_handle_oom(sizeof(Segment)); 188 } 189 #else 190 MOZ_RELEASE_ASSERT(ok); 191 #endif // MOZ_INTERNAL_API 192 } 193 194 void Clear() { 195 Segment* segment; 196 while ((segment = mSegments.popFirst())) { 197 segment->~Segment(); 198 this->free_(segment, 1); 199 } 200 } 201 202 T& GetLast() { 203 MOZ_ASSERT(!IsEmpty()); 204 Segment* last = mSegments.getLast(); 205 return (*last)[last->Length() - 1]; 206 } 207 208 const T& GetLast() const { 209 MOZ_ASSERT(!IsEmpty()); 210 Segment* last = mSegments.getLast(); 211 return (*last)[last->Length() - 1]; 212 } 213 214 void PopLast() { 215 MOZ_ASSERT(!IsEmpty()); 216 Segment* last = mSegments.getLast(); 217 last->PopLast(); 218 if (!last->Length()) { 219 mSegments.popLast(); 220 last->~Segment(); 221 this->free_(last, 1); 222 } 223 } 224 225 // Equivalent to calling |PopLast| |aNumElements| times, but potentially 226 // more efficient. It is safe to call this even when aNumElements > Length(). 227 void PopLastN(uint32_t aNumElements) { 228 Segment* last; 229 230 // Pop full segments for as long as we can. Note that this loop 231 // cleanly handles the case when the initial last segment is not 232 // full and we are popping more elements than said segment contains. 233 do { 234 last = mSegments.getLast(); 235 236 // The list is empty. We're all done. 237 if (!last) { 238 return; 239 } 240 241 // Check to see if the list contains too many elements. Handle 242 // that in the epilogue. 243 uint32_t segmentLen = last->Length(); 244 if (segmentLen > aNumElements) { 245 break; 246 } 247 248 // Destroying the segment destroys all elements contained therein. 249 mSegments.popLast(); 250 last->~Segment(); 251 this->free_(last, 1); 252 253 MOZ_ASSERT(aNumElements >= segmentLen); 254 aNumElements -= segmentLen; 255 if (aNumElements == 0) { 256 return; 257 } 258 } while (true); 259 260 // Handle the case where the last segment contains more elements 261 // than we want to pop. 262 MOZ_ASSERT(last); 263 MOZ_ASSERT(last == mSegments.getLast()); 264 MOZ_ASSERT(aNumElements < last->Length()); 265 for (uint32_t i = 0; i < aNumElements; ++i) { 266 last->PopLast(); 267 } 268 MOZ_ASSERT(last->Length() != 0); 269 } 270 271 // Use this class to iterate over a SegmentedVector, like so: 272 // 273 // for (auto iter = v.Iter(); !iter.Done(); iter.Next()) { 274 // MyElem& elem = iter.Get(); 275 // f(elem); 276 // } 277 // 278 // Note, adding new entries to the SegmentedVector while using iterators 279 // is supported, but removing is not! 280 // If an iterator has entered Done() state, adding more entries to the 281 // vector doesn't affect it. 282 class IterImpl { 283 friend class SegmentedVector; 284 285 Segment* mSegment; 286 size_t mIndex; 287 288 explicit IterImpl(SegmentedVector* aVector, bool aFromFirst) 289 : mSegment(aFromFirst ? aVector->mSegments.getFirst() 290 : aVector->mSegments.getLast()), 291 mIndex(aFromFirst ? 0 : (mSegment ? mSegment->Length() - 1 : 0)) { 292 MOZ_ASSERT_IF(mSegment, mSegment->Length() > 0); 293 } 294 295 public: 296 bool Done() const { 297 MOZ_ASSERT_IF(mSegment, mSegment->isInList()); 298 MOZ_ASSERT_IF(mSegment, mIndex < mSegment->Length()); 299 return !mSegment; 300 } 301 302 T& Get() { 303 MOZ_ASSERT(!Done()); 304 return (*mSegment)[mIndex]; 305 } 306 307 const T& Get() const { 308 MOZ_ASSERT(!Done()); 309 return (*mSegment)[mIndex]; 310 } 311 312 void Next() { 313 MOZ_ASSERT(!Done()); 314 mIndex++; 315 if (mIndex == mSegment->Length()) { 316 mSegment = mSegment->getNext(); 317 mIndex = 0; 318 } 319 } 320 321 void Prev() { 322 MOZ_ASSERT(!Done()); 323 if (mIndex == 0) { 324 mSegment = mSegment->getPrevious(); 325 if (mSegment) { 326 mIndex = mSegment->Length() - 1; 327 } 328 } else { 329 --mIndex; 330 } 331 } 332 }; 333 334 IterImpl Iter() { return IterImpl(this, true); } 335 IterImpl IterFromLast() { return IterImpl(this, false); } 336 337 // Measure the memory consumption of the vector excluding |this|. Note that 338 // it only measures the vector itself. If the vector elements contain 339 // pointers to other memory blocks, those blocks must be measured separately 340 // during a subsequent iteration over the vector. 341 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 342 return mSegments.sizeOfExcludingThis(aMallocSizeOf); 343 } 344 345 // Like sizeOfExcludingThis(), but measures |this| as well. 346 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 347 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 348 } 349 350 private: 351 mozilla::LinkedList<Segment> mSegments; 352 }; 353 354 } // namespace mozilla 355 356 #endif /* mozilla_SegmentedVector_h */