1 // Copyright 2016 The Android Open Source Project 2 // 3 // This software is licensed under the terms of the GNU General Public 4 // License version 2, as published by the Free Software Foundation, and 5 // may be copied, distributed, and modified under those terms. 6 // 7 // This program is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 // GNU General Public License for more details. 11 12 #pragma once 13 14 #include "android/base/TypeTraits.h" 15 16 #include <algorithm> 17 #include <initializer_list> 18 #include <memory> 19 #include <type_traits> 20 #include <utility> 21 22 #include <stddef.h> 23 #include <stdlib.h> 24 25 // 26 // SmallVector<T>, SmallFixedVector<T, SmallSize> 27 // 28 // This header defines a replacement for a std::vector<> that uses small buffer 29 // optimization technique - for some preset number of elements |SmallSize| it 30 // stores them inside of the object, and falls back to the dynamically allocated 31 // array only if one needs to add more elements. 32 // This is useful for the performance-critical places where common number of 33 // processed items is small, but it may still be quite large for a stack array. 34 // 35 // SmallFixedVector<> is the class you use to store elements, while 36 // SmallVector<> is its base class that erases the small size from the type. 37 // 38 // NOTE: SmallVector<> cannot guarantee std::vector<>'s iterator invalidation 39 // rules for move() and swap() operations - std::vector<>s just exchange 40 // their iterators on swap() and pass the moved ones over, while SmallVector<> 41 // may leave the iterators pointing to nowhere if they were for the in-place 42 // array storage. 43 // 44 // Currenly only a limited subset of std::vector<>'s operations is implemented, 45 // but fill free to add the ones you need. 46 // 47 48 namespace android { 49 namespace base { 50 51 // 52 // Forward-declare the 'real' small vector class. 53 template <class T, size_t S> 54 class SmallFixedVector; 55 56 // 57 // SmallVector<T> - an interface for a small-buffer-optimized vector. 58 // It hides the fixed size from its type, so one can use it to pass small 59 // vectors around and not leak the buffer size to all callers: 60 // 61 // void process(SmallVector<Foo>& data); 62 // ... 63 // ... 64 // SmallFixedVector<Foo, 100> aLittleBitOfFoos = ...; 65 // process(aLittleBitOfFoos); 66 // ... 67 // SmallFixedVector<Foo, 1000> moreFoos = ...; 68 // process(moreFoos); 69 // 70 template <class T> 71 class SmallVector { 72 // Make them friends so SmallFixedVector is able to refer to SmallVector's 73 // protected members in static_assert()s. 74 template <class U, size_t S> 75 friend class SmallFixedVector; 76 77 public: 78 // Common set of type aliases. 79 using value_type = T; 80 using iterator = T*; 81 using const_iterator = const T*; 82 using pointer = T*; 83 using const_pointer = const T*; 84 using reference = T&; 85 using const_reference = const T&; 86 using size_type = size_t; 87 88 // It's ok to delete SmallVector<> through the base class - dtor() actually 89 // takes care of all living elements and the allocated memory. ~SmallVector()90 ~SmallVector() { dtor(); } 91 92 // std::vector<> interface operations. begin()93 iterator begin() { return mBegin; } begin()94 const_iterator begin() const { return mBegin; } cbegin()95 const_iterator cbegin() const { return mBegin; } 96 end()97 iterator end() { return mEnd; } end()98 const_iterator end() const { return mEnd; } cend()99 const_iterator cend() const { return mEnd; } 100 size()101 size_type size() const { return end() - begin(); } capacity()102 size_type capacity() const { return mCapacity; } empty()103 bool empty() const { return begin() == end(); } 104 front()105 reference front() { return *begin(); } front()106 const_reference front() const { return *cbegin(); } back()107 reference back() { return *(end() - 1); } back()108 const_reference back() const { return *(cend() - 1); } 109 110 reference operator[](size_t i) { return *(begin() + i); } 111 const_reference operator[](size_t i) const { return *(cbegin() + i); } 112 data()113 pointer data() { return mBegin; } data()114 const_pointer data() const { return mBegin; } cdata()115 const_pointer cdata() const { return mBegin; } 116 117 template <class... Args> emplace_back(Args &&...args)118 void emplace_back(Args&&... args) { 119 grow_for_size(size() + 1); 120 new (mEnd) T(std::forward<Args>(args)...); 121 ++mEnd; 122 } 123 push_back(const T & t)124 void push_back(const T& t) { emplace_back(t); } push_back(T && t)125 void push_back(T&& t) { emplace_back(std::move(t)); } 126 pop_back()127 void pop_back() { 128 destruct(mEnd - 1, mEnd); 129 --mEnd; 130 } 131 clear()132 void clear() { 133 destruct(begin(), end()); 134 mEnd = mBegin; 135 } 136 reserve(size_type newCap)137 void reserve(size_type newCap) { 138 if (newCap <= this->capacity()) { 139 return; 140 } 141 set_capacity(newCap); 142 } 143 resize(size_type newSize)144 void resize(size_type newSize) { resize_impl<true>(newSize); } 145 146 // This version of resizing doesn't initialize the newly allocated elements 147 // Useful for the cases when value-initialization is noticeably slow and 148 // one wants to directly construct or memcpy the elements into the resized 149 // place. resize_noinit(size_type newSize)150 void resize_noinit(size_type newSize) { resize_impl<false>(newSize); } 151 152 // Returns if the current vector's buffer is dynamically allocated. isAllocated()153 bool isAllocated() const { return this->cbegin() != smallBufferStart(); } 154 155 protected: 156 // Hide the default constructor so only SmallFixedVector can be 157 // instantiated. 158 SmallVector() = default; 159 160 // Destroy all elements in the vector and free the array if it was allocated 161 // dynamically. dtor()162 void dtor() { 163 this->destruct(this->begin(), this->end()); 164 if (isAllocated()) { 165 free(this->mBegin); 166 } 167 } 168 169 // Just a convenience setter function to init all members at once. init(iterator begin,iterator end,size_type capacity)170 void init(iterator begin, iterator end, size_type capacity) { 171 this->mBegin = begin; 172 this->mEnd = end; 173 this->mCapacity = capacity; 174 } 175 176 // An implementation of different resizing versions. 177 template <bool init> resize_impl(size_type newSize)178 void resize_impl(size_type newSize) { 179 if (newSize < this->size()) { 180 const auto newEnd = this->begin() + newSize; 181 this->destruct(newEnd, this->end()); 182 this->mEnd = newEnd; 183 } else if (newSize > this->size()) { 184 grow_for_size(newSize); 185 const auto newEnd = this->begin() + newSize; 186 if (init) { 187 std::uninitialized_fill(this->end(), newEnd, T()); 188 } 189 this->mEnd = newEnd; 190 } 191 } 192 193 // Templated append operation for a range of elements. 194 template <class Iter> insert_back(Iter b,Iter e)195 void insert_back(Iter b, Iter e) { 196 if (b == e) { 197 return; 198 } 199 const auto newSize = this->size() + (e - b); 200 grow_for_size(newSize); 201 this->mEnd = std::uninitialized_copy(b, e, this->mEnd); 202 } 203 204 // Multiplicative grow for the internal array so it can hold |newSize| 205 // elements. 206 // Doesn't change size(), only capacity(). grow_for_size(size_type newSize)207 void grow_for_size(size_type newSize) { 208 // Grow by 1.5x by default. 209 if (newSize > capacity()) { 210 set_capacity(std::max(newSize, capacity() + capacity() / 2)); 211 } 212 } 213 214 // Sets the capacity() to be exacly |newCap|. Allocates the array 215 // dynamically, moves all elements over and (potentially) deallocates the 216 // old array. 217 // Doesn't change size(), only capacity(). set_capacity(size_type newCap)218 void set_capacity(size_type newCap) { 219 // Here we can only be switching to the dynamic vector, as static one 220 // always has its capacity on the maximum. 221 const auto newBegin = (T*)malloc(sizeof(T) * newCap); 222 if (!newBegin) { 223 abort(); // what else can we do here? 224 } 225 const auto newEnd = std::uninitialized_copy( 226 std::make_move_iterator(this->begin()), 227 std::make_move_iterator(this->end()), newBegin); 228 dtor(); 229 this->mBegin = newBegin; 230 this->mEnd = newEnd; 231 this->mCapacity = newCap; 232 } 233 234 // A convenience function to call destructor for a range of elements. destruct(T * b,T * e)235 static void destruct(T* b, T* e) { 236 if (!std::is_trivially_destructible<T>::value) { 237 for (; b != e; ++b) { 238 b->~T(); 239 } 240 } 241 } 242 243 // By design of the class, SmallFixedVector<> will be inheriting from 244 // SmallVector<>, so its in-place storage array is going to be the very next 245 // member after the last one here. 246 // This function returns that address, and SmallFixedVector<> has a static 247 // assert to make sure it remains correct. smallBufferStart()248 constexpr const void* smallBufferStart() const { 249 return (const void*)(&mCapacity + 1); 250 } 251 252 // Standard set of members for a vector - begin, end and capacity. 253 // These point to the currently used chunk of memory, no matter if it's a 254 // heap-allocated one or an in-place array. 255 iterator mBegin; 256 iterator mEnd; 257 size_type mCapacity; 258 }; 259 260 // The implementation of a SmallVector with a fixed in-place size, |SmallSize|. 261 template <class T, size_t SmallSize> 262 class SmallFixedVector : public SmallVector<T> { 263 using base = SmallVector<T>; 264 265 public: 266 // Grab these from the base class. 267 using value_type = typename base::value_type; 268 using iterator = typename base::iterator; 269 using const_iterator = typename base::const_iterator; 270 using pointer = typename base::pointer; 271 using const_pointer = typename base::const_pointer; 272 using reference = typename base::reference; 273 using const_reference = typename base::const_reference; 274 using size_type = typename base::size_type; 275 276 static constexpr size_type kSmallSize = SmallSize; 277 278 // Default constructor - set up an empty vector with capacity at full 279 // internal array size. SmallFixedVector()280 SmallFixedVector() { 281 // Make sure that the small array starts exactly where base class 282 // expects it: right after the |mCapacity|. 283 284 // We can't use a static_assert with offsetof() because in msvc, it uses 285 // reinterpret_cast. 286 // TODO: Add runtime assertion instead? 287 // https://developercommunity.visualstudio.com/content/problem/22196/static-assert-cannot-compile-constexprs-method-tha.html 288 #ifndef _MSC_VER 289 static_assert(offsetof(base, mCapacity) + sizeof(base::mCapacity) == 290 offsetof(SmallFixedVector, mData) && 291 offsetof(Data, array) == 0, 292 "SmallFixedVector<> class layout is wrong, " 293 "|mData| needs to follow |mCapacity|"); 294 #endif 295 296 init_inplace(); 297 } 298 299 // Ctor from a range of iterators 300 template <class Iter> SmallFixedVector(Iter b,Iter e)301 SmallFixedVector(Iter b, Iter e) : SmallFixedVector() { 302 this->insert_back(b, e); 303 } 304 305 // Ctor from a range - anything that has begin and end. 306 // Note: template constructor is never a copy/move-ctor. 307 template <class Range, 308 class = enable_if_c<!std::is_same<Range, T>::value && 309 is_range<Range>::value>> SmallFixedVector(const Range & r)310 explicit SmallFixedVector(const Range& r) 311 : SmallFixedVector(std::begin(r), std::end(r)) {} 312 template <class Range, 313 class = enable_if_c<!std::is_same<Range, T>::value && 314 is_range<Range>::value>> SmallFixedVector(Range && r)315 explicit SmallFixedVector(Range&& r) 316 : SmallFixedVector(std::make_move_iterator(std::begin(r)), 317 std::make_move_iterator(std::end(r))) {} 318 template <class U, class = enable_if_convertible<U, T>> SmallFixedVector(std::initializer_list<U> list)319 SmallFixedVector(std::initializer_list<U> list) 320 : SmallFixedVector(std::begin(list), std::end(list)) {} 321 SmallFixedVector(const SmallFixedVector & other)322 SmallFixedVector(const SmallFixedVector& other) 323 : SmallFixedVector(other.begin(), other.end()) {} 324 SmallFixedVector(SmallFixedVector && other)325 SmallFixedVector(SmallFixedVector&& other) { 326 if (other.isAllocated()) { 327 // Just steal the allocated memory from the |other|. 328 this->mBegin = other.mBegin; 329 this->mEnd = other.mEnd; 330 this->mCapacity = other.mCapacity; 331 other.init_inplace(); 332 } else { 333 // Have to move individual elements. 334 this->mBegin = mData.array; 335 this->mEnd = std::uninitialized_copy( 336 std::make_move_iterator(other.begin()), 337 std::make_move_iterator(other.end()), this->begin()); 338 this->mCapacity = kSmallSize; 339 } 340 } 341 342 SmallFixedVector& operator=(const SmallFixedVector& other) { 343 if (&other != this) { 344 this->clear(); 345 this->insert_back(other.begin(), other.end()); 346 } 347 return *this; 348 } 349 350 SmallFixedVector& operator=(SmallFixedVector&& other) { 351 if (other.isAllocated()) { 352 // Steal it and we're done. 353 this->dtor(); 354 this->mBegin = other.mBegin; 355 this->mEnd = other.mEnd; 356 this->mCapacity = other.mCapacity; 357 other.init_inplace(); 358 return *this; 359 } 360 361 if (this->isAllocated() && this->mCapacity < other.size()) { 362 // Not enough dynamic memory, switch to in-place. 363 this->dtor(); 364 init_inplace(); 365 } else { 366 // This could potentially be improved by move-assigning 367 // only needed items and destroying the rest, but 368 // destroy-all+construct-all is just simpler. For PODs it actually 369 // is even faster as it's always a single memcpy(). 370 this->destruct(this->begin(), this->end()); 371 } 372 373 // Move the whole |other| into the pre-cleaned memory 374 const auto newEnd = std::uninitialized_copy( 375 std::make_move_iterator(other.begin()), 376 std::make_move_iterator(other.end()), this->mBegin); 377 this->mEnd = newEnd; 378 // |other| is valid as-is. 379 return *this; 380 } 381 382 // Make sure we don't end up trying to move from an interface - it's just 383 // inefficient with the current code. 384 SmallFixedVector(base&& other) = delete; 385 SmallFixedVector& operator=(base&& other) = delete; 386 387 private: 388 // A shortcut for initialization for in-place storage. init_inplace()389 void init_inplace() { this->init(mData.array, mData.array, kSmallSize); } 390 391 // A union with empty constructor and destructor makes sure that the array 392 // elements are not default-constructed in ctor and not destructed in dtor: 393 // the class needs to be able manage their lifetime more precisely. 394 union Data { 395 alignas(size_type) T array[kSmallSize]; 396 Data()397 Data() {} ~Data()398 ~Data() {} 399 } mData; 400 }; 401 402 } // namespace base 403 } // namespace android 404