1 /* 2 Copyright (c) 2005-2021 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #ifndef __TBB_concurrent_vector_H 18 #define __TBB_concurrent_vector_H 19 20 #include "detail/_namespace_injection.h" 21 #include "detail/_utils.h" 22 #include "detail/_assert.h" 23 #include "detail/_allocator_traits.h" 24 #include "detail/_segment_table.h" 25 #include "detail/_containers_helpers.h" 26 #include "blocked_range.h" 27 #include "cache_aligned_allocator.h" 28 29 #include <algorithm> 30 #include <utility> // std::move_if_noexcept 31 #include <algorithm> 32 #if __TBB_CPP20_COMPARISONS_PRESENT 33 #include <compare> 34 #endif 35 36 namespace tbb { 37 namespace detail { 38 namespace d1 { 39 40 template <typename Vector, typename Value> 41 class vector_iterator { 42 using vector_type = Vector; 43 44 public: 45 using value_type = Value; 46 using size_type = typename vector_type::size_type; 47 using difference_type = typename vector_type::difference_type; 48 using pointer = value_type*; 49 using reference = value_type&; 50 using iterator_category = std::random_access_iterator_tag; 51 52 template <typename Vec, typename Val> 53 friend vector_iterator<Vec, Val> operator+( typename vector_iterator<Vec, Val>::difference_type, const vector_iterator<Vec, Val>& ); 54 55 template <typename Vec, typename Val1, typename Val2> 56 friend typename vector_iterator<Vec, Val1>::difference_type operator-( const vector_iterator<Vec, Val1>&, const vector_iterator<Vec, Val2>& ); 57 58 template <typename Vec, typename Val1, typename Val2> 59 friend bool operator==( const vector_iterator<Vec, Val1>&, const vector_iterator<Vec, Val2>& ); 60 61 template <typename Vec, typename Val1, typename Val2> 62 friend bool operator<( const vector_iterator<Vec, Val1>&, const vector_iterator<Vec, Val2>& ); 63 64 template <typename Vec, typename Val> 65 friend class vector_iterator; 66 67 template <typename T, typename Allocator> 68 friend class concurrent_vector; 69 70 private: 71 vector_iterator( const vector_type& vector, size_type index, value_type* item = nullptr ) 72 : my_vector(const_cast<vector_type*>(&vector)), my_index(index), my_item(item) 73 {} 74 75 public: 76 vector_iterator() : my_vector(nullptr), my_index(~size_type(0)), my_item(nullptr) 77 {} 78 79 vector_iterator( const vector_iterator<vector_type, typename vector_type::value_type>& other ) 80 : my_vector(other.my_vector), my_index(other.my_index), my_item(other.my_item) 81 {} 82 83 vector_iterator& operator=( const vector_iterator<vector_type, typename vector_type::value_type>& other ) { 84 my_vector = other.my_vector; 85 my_index = other.my_index; 86 my_item = other.my_item; 87 return *this; 88 } 89 90 vector_iterator operator+( difference_type offset ) const { 91 return vector_iterator(*my_vector, my_index + offset); 92 } 93 94 vector_iterator& operator+=( difference_type offset ) { 95 my_index += offset; 96 my_item = nullptr; 97 return *this; 98 } 99 100 vector_iterator operator-( difference_type offset ) const { 101 return vector_iterator(*my_vector, my_index - offset); 102 } 103 104 vector_iterator& operator-=( difference_type offset ) { 105 my_index -= offset; 106 my_item = nullptr; 107 return *this; 108 } 109 110 reference operator*() const { 111 value_type *item = my_item; 112 if (item == nullptr) { 113 item = &my_vector->internal_subscript(my_index); 114 } else { 115 __TBB_ASSERT(item == &my_vector->internal_subscript(my_index), "corrupt cache"); 116 } 117 return *item; 118 } 119 120 pointer operator->() const { return &(operator*()); } 121 122 reference operator[]( difference_type k ) const { 123 return my_vector->internal_subscript(my_index + k); 124 } 125 126 vector_iterator& operator++() { 127 ++my_index; 128 if (my_item != nullptr) { 129 if (vector_type::is_first_element_in_segment(my_index)) { 130 // If the iterator crosses a segment boundary, the pointer become invalid 131 // as possibly next segment is in another memory location 132 my_item = nullptr; 133 } else { 134 ++my_item; 135 } 136 } 137 return *this; 138 } 139 140 vector_iterator operator++(int) { 141 vector_iterator result = *this; 142 ++(*this); 143 return result; 144 } 145 146 vector_iterator& operator--() { 147 __TBB_ASSERT(my_index > 0, "operator--() applied to iterator already at beginning of concurrent_vector"); 148 --my_index; 149 if (my_item != nullptr) { 150 if (vector_type::is_first_element_in_segment(my_index)) { 151 // If the iterator crosses a segment boundary, the pointer become invalid 152 // as possibly next segment is in another memory location 153 my_item = nullptr; 154 } else { 155 --my_item; 156 } 157 } 158 return *this; 159 } 160 161 vector_iterator operator--(int) { 162 vector_iterator result = *this; 163 --(*this); 164 return result; 165 } 166 167 private: 168 // concurrent_vector over which we are iterating. 169 vector_type* my_vector; 170 171 // Index into the vector 172 size_type my_index; 173 174 // Caches my_vector *it; 175 // If my_item == nullptr cached value is not available use internal_subscript(my_index) 176 mutable value_type* my_item; 177 }; // class vector_iterator 178 179 template <typename Vector, typename T> 180 vector_iterator<Vector, T> operator+( typename vector_iterator<Vector, T>::difference_type offset, 181 const vector_iterator<Vector, T>& v ) 182 { 183 return vector_iterator<Vector, T>(*v.my_vector, v.my_index + offset); 184 } 185 186 template <typename Vector, typename T, typename U> 187 typename vector_iterator<Vector, T>::difference_type operator-( const vector_iterator<Vector, T>& i, 188 const vector_iterator<Vector, U>& j ) 189 { 190 using difference_type = typename vector_iterator<Vector, T>::difference_type; 191 return static_cast<difference_type>(i.my_index) - static_cast<difference_type>(j.my_index); 192 } 193 194 template <typename Vector, typename T, typename U> 195 bool operator==( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 196 return i.my_vector == j.my_vector && i.my_index == j.my_index; 197 } 198 199 template <typename Vector, typename T, typename U> 200 bool operator!=( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 201 return !(i == j); 202 } 203 204 template <typename Vector, typename T, typename U> 205 bool operator<( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 206 return i.my_index < j.my_index; 207 } 208 209 template <typename Vector, typename T, typename U> 210 bool operator>( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 211 return j < i; 212 } 213 214 template <typename Vector, typename T, typename U> 215 bool operator>=( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 216 return !(i < j); 217 } 218 219 template <typename Vector, typename T, typename U> 220 bool operator<=( const vector_iterator<Vector, T>& i, const vector_iterator<Vector, U>& j ) { 221 return !(j < i); 222 } 223 224 static constexpr std::size_t embedded_table_num_segments = 3; 225 226 template <typename T, typename Allocator = tbb::cache_aligned_allocator<T>> 227 class concurrent_vector 228 : private segment_table<T, Allocator, concurrent_vector<T, Allocator>, embedded_table_num_segments> 229 { 230 using self_type = concurrent_vector<T, Allocator>; 231 using base_type = segment_table<T, Allocator, self_type, embedded_table_num_segments>; 232 233 friend class segment_table<T, Allocator, self_type, embedded_table_num_segments>; 234 235 template <typename Iterator> 236 class generic_range_type : public tbb::blocked_range<Iterator> { 237 using base_type = tbb::blocked_range<Iterator>; 238 public: 239 using value_type = T; 240 using reference = T&; 241 using const_reference = const T&; 242 using iterator = Iterator; 243 using difference_type = std::ptrdiff_t; 244 245 using base_type::base_type; 246 247 template<typename U> 248 generic_range_type( const generic_range_type<U>& r) : blocked_range<Iterator>(r.begin(), r.end(), r.grainsize()) {} 249 generic_range_type( generic_range_type& r, split ) : blocked_range<Iterator>(r, split()) {} 250 }; // class generic_range_type 251 252 static_assert(std::is_same<T, typename Allocator::value_type>::value, 253 "value_type of the container must be the same as its allocator's"); 254 using allocator_traits_type = tbb::detail::allocator_traits<Allocator>; 255 // Segment table for concurrent_vector can be extended 256 static constexpr bool allow_table_extending = true; 257 static constexpr bool is_noexcept_assignment = allocator_traits_type::propagate_on_container_move_assignment::value || 258 allocator_traits_type::is_always_equal::value; 259 static constexpr bool is_noexcept_swap = allocator_traits_type::propagate_on_container_swap::value || 260 allocator_traits_type::is_always_equal::value; 261 262 public: 263 using value_type = T; 264 using allocator_type = Allocator; 265 using size_type = std::size_t; 266 using difference_type = std::ptrdiff_t; 267 using reference = value_type&; 268 using const_reference = const value_type&; 269 270 using pointer = typename allocator_traits_type::pointer; 271 using const_pointer = typename allocator_traits_type::const_pointer; 272 273 using iterator = vector_iterator<concurrent_vector, value_type>; 274 using const_iterator = vector_iterator<concurrent_vector, const value_type>; 275 using reverse_iterator = std::reverse_iterator<iterator>; 276 using const_reverse_iterator = std::reverse_iterator<const_iterator>; 277 278 using range_type = generic_range_type<iterator>; 279 using const_range_type = generic_range_type<const_iterator>; 280 281 concurrent_vector() : concurrent_vector(allocator_type()) {} 282 283 explicit concurrent_vector( const allocator_type& alloc ) noexcept 284 : base_type(alloc) 285 {} 286 287 explicit concurrent_vector( size_type count, const value_type& value, 288 const allocator_type& alloc = allocator_type() ) 289 : concurrent_vector(alloc) 290 { 291 try_call( [&] { 292 grow_by(count, value); 293 } ).on_exception( [&] { 294 base_type::clear(); 295 }); 296 } 297 298 explicit concurrent_vector( size_type count, const allocator_type& alloc = allocator_type() ) 299 : concurrent_vector(alloc) 300 { 301 try_call( [&] { 302 grow_by(count); 303 } ).on_exception( [&] { 304 base_type::clear(); 305 }); 306 } 307 308 template <typename InputIterator> 309 concurrent_vector( InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type() ) 310 : concurrent_vector(alloc) 311 { 312 try_call( [&] { 313 grow_by(first, last); 314 } ).on_exception( [&] { 315 base_type::clear(); 316 }); 317 } 318 319 concurrent_vector( const concurrent_vector& other ) 320 : base_type(segment_table_allocator_traits::select_on_container_copy_construction(other.get_allocator())) 321 { 322 try_call( [&] { 323 grow_by(other.begin(), other.end()); 324 } ).on_exception( [&] { 325 base_type::clear(); 326 }); 327 } 328 329 concurrent_vector( const concurrent_vector& other, const allocator_type& alloc ) 330 : base_type(other, alloc) {} 331 332 concurrent_vector(concurrent_vector&& other) noexcept 333 : base_type(std::move(other)) 334 {} 335 336 concurrent_vector( concurrent_vector&& other, const allocator_type& alloc ) 337 : base_type(std::move(other), alloc) 338 {} 339 340 concurrent_vector( std::initializer_list<value_type> init, 341 const allocator_type& alloc = allocator_type() ) 342 : concurrent_vector(init.begin(), init.end(), alloc) 343 {} 344 345 ~concurrent_vector() {} 346 347 // Assignment 348 concurrent_vector& operator=( const concurrent_vector& other ) { 349 base_type::operator=(other); 350 return *this; 351 } 352 353 concurrent_vector& operator=( concurrent_vector&& other ) noexcept(is_noexcept_assignment) { 354 base_type::operator=(std::move(other)); 355 return *this; 356 } 357 358 concurrent_vector& operator=( std::initializer_list<value_type> init ) { 359 assign(init); 360 return *this; 361 } 362 363 void assign( size_type count, const value_type& value ) { 364 destroy_elements(); 365 grow_by(count, value); 366 } 367 368 template <typename InputIterator> 369 typename std::enable_if<is_input_iterator<InputIterator>::value, void>::type 370 assign( InputIterator first, InputIterator last ) { 371 destroy_elements(); 372 grow_by(first, last); 373 } 374 375 void assign( std::initializer_list<value_type> init ) { 376 destroy_elements(); 377 assign(init.begin(), init.end()); 378 } 379 380 // Concurrent growth 381 iterator grow_by( size_type delta ) { 382 return internal_grow_by_delta(delta); 383 } 384 385 iterator grow_by( size_type delta, const value_type& value ) { 386 return internal_grow_by_delta(delta, value); 387 } 388 389 template <typename ForwardIterator> 390 typename std::enable_if<is_input_iterator<ForwardIterator>::value, iterator>::type 391 grow_by( ForwardIterator first, ForwardIterator last ) { 392 auto delta = std::distance(first, last); 393 return internal_grow_by_delta(delta, first, last); 394 } 395 396 iterator grow_by( std::initializer_list<value_type> init ) { 397 return grow_by(init.begin(), init.end()); 398 } 399 400 iterator grow_to_at_least( size_type n ) { 401 return internal_grow_to_at_least(n); 402 } 403 iterator grow_to_at_least( size_type n, const value_type& value ) { 404 return internal_grow_to_at_least(n, value); 405 } 406 407 iterator push_back( const value_type& item ) { 408 return internal_emplace_back(item); 409 } 410 411 iterator push_back( value_type&& item ) { 412 return internal_emplace_back(std::move(item)); 413 } 414 415 template <typename... Args> 416 iterator emplace_back( Args&&... args ) { 417 return internal_emplace_back(std::forward<Args>(args)...); 418 } 419 420 // Items access 421 reference operator[]( size_type index ) { 422 return internal_subscript(index); 423 } 424 const_reference operator[]( size_type index ) const { 425 return internal_subscript(index); 426 } 427 428 reference at( size_type index ) { 429 return internal_subscript_with_exceptions(index); 430 } 431 const_reference at( size_type index ) const { 432 return internal_subscript_with_exceptions(index); 433 } 434 435 // Get range for iterating with parallel algorithms 436 range_type range( size_t grainsize = 1 ) { 437 return range_type(begin(), end(), grainsize); 438 } 439 440 // Get const range for iterating with parallel algorithms 441 const_range_type range( size_t grainsize = 1 ) const { 442 return const_range_type(begin(), end(), grainsize); 443 } 444 445 reference front() { 446 return internal_subscript(0); 447 } 448 449 const_reference front() const { 450 return internal_subscript(0); 451 } 452 453 reference back() { 454 return internal_subscript(size() - 1); 455 } 456 457 const_reference back() const { 458 return internal_subscript(size() - 1); 459 } 460 461 // Iterators 462 iterator begin() { return iterator(*this, 0); } 463 const_iterator begin() const { return const_iterator(*this, 0); } 464 const_iterator cbegin() const { return const_iterator(*this, 0); } 465 466 iterator end() { return iterator(*this, size()); } 467 const_iterator end() const { return const_iterator(*this, size()); } 468 const_iterator cend() const { return const_iterator(*this, size()); } 469 470 reverse_iterator rbegin() { return reverse_iterator(end()); } 471 const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } 472 const_reverse_iterator crbegin() const { return const_reverse_iterator(cend()); } 473 474 reverse_iterator rend() { return reverse_iterator(begin()); } 475 const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } 476 const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); } 477 478 allocator_type get_allocator() const { 479 return base_type::get_allocator(); 480 } 481 482 // Storage 483 bool empty() const noexcept { 484 return 0 == size(); 485 } 486 487 size_type size() const noexcept { 488 return std::min(this->my_size.load(std::memory_order_acquire), capacity()); 489 } 490 491 size_type max_size() const noexcept { 492 return allocator_traits_type::max_size(base_type::get_allocator()); 493 } 494 495 size_type capacity() const noexcept { 496 return base_type::capacity(); 497 } 498 499 void reserve( size_type n ) { 500 if (n == 0) return; 501 502 if (n > max_size()) { 503 tbb::detail::throw_exception(exception_id::reservation_length_error); 504 } 505 506 this->assign_first_block_if_necessary(this->segment_index_of(n - 1) + 1); 507 base_type::reserve(n); 508 } 509 510 void resize( size_type n ) { 511 internal_resize(n); 512 } 513 514 void resize( size_type n, const value_type& val ) { 515 internal_resize(n, val); 516 } 517 518 void shrink_to_fit() { 519 internal_compact(); 520 } 521 522 void swap(concurrent_vector& other) noexcept(is_noexcept_swap) { 523 base_type::swap(other); 524 } 525 526 void clear() { 527 destroy_elements(); 528 } 529 530 private: 531 using segment_type = typename base_type::segment_type; 532 using segment_table_type = typename base_type::segment_table_type; 533 using segment_table_allocator_traits = typename base_type::segment_table_allocator_traits; 534 using segment_index_type = typename base_type::segment_index_type; 535 536 using segment_element_type = typename base_type::value_type; 537 using segment_element_allocator_type = typename allocator_traits_type::template rebind_alloc<segment_element_type>; 538 using segment_element_allocator_traits = tbb::detail::allocator_traits<segment_element_allocator_type>; 539 540 segment_table_type allocate_long_table( const typename base_type::atomic_segment* embedded_table, size_type start_index ) { 541 __TBB_ASSERT(start_index <= this->embedded_table_size, "Start index out of embedded table"); 542 543 // If other threads are trying to set pointers in the short segment, wait for them to finish their 544 // assignments before we copy the short segment to the long segment. Note: grow_to_at_least depends on it 545 for (segment_index_type i = 0; this->segment_base(i) < start_index; ++i) { 546 spin_wait_while_eq(embedded_table[i], segment_type(nullptr)); 547 } 548 549 // It is possible that the table was extend by a thread allocating first_block, need to check this. 550 if (this->get_table() != embedded_table) { 551 return nullptr; 552 } 553 554 // Allocate long segment table and fill with null pointers 555 segment_table_type new_segment_table = segment_table_allocator_traits::allocate(base_type::get_allocator(), this->pointers_per_long_table); 556 // Copy segment pointers from the embedded table 557 for (size_type segment_index = 0; segment_index < this->pointers_per_embedded_table; ++segment_index) { 558 segment_table_allocator_traits::construct(base_type::get_allocator(), &new_segment_table[segment_index], 559 embedded_table[segment_index].load(std::memory_order_relaxed)); 560 } 561 for (size_type segment_index = this->pointers_per_embedded_table; segment_index < this->pointers_per_long_table; ++segment_index) { 562 segment_table_allocator_traits::construct(base_type::get_allocator(), &new_segment_table[segment_index], nullptr); 563 } 564 565 return new_segment_table; 566 } 567 568 // create_segment function is required by the segment_table base class 569 segment_type create_segment( segment_table_type table, segment_index_type seg_index, size_type index ) { 570 size_type first_block = this->my_first_block.load(std::memory_order_relaxed); 571 // First block allocation 572 if (seg_index < first_block) { 573 // If 0 segment is already allocated, then it remains to wait until the segments are filled to requested 574 if (table[0].load(std::memory_order_acquire) != nullptr) { 575 spin_wait_while_eq(table[seg_index], segment_type(nullptr)); 576 return nullptr; 577 } 578 579 segment_element_allocator_type segment_allocator(base_type::get_allocator()); 580 segment_type new_segment = nullptr; 581 size_type first_block_size = this->segment_size(first_block); 582 try_call( [&] { 583 new_segment = segment_element_allocator_traits::allocate(segment_allocator, first_block_size); 584 } ).on_exception( [&] { 585 segment_type disabled_segment = nullptr; 586 if (table[0].compare_exchange_strong(disabled_segment, this->segment_allocation_failure_tag)) { 587 size_type end_segment = table == this->my_embedded_table ? this->pointers_per_embedded_table : first_block; 588 for (size_type i = 1; i < end_segment; ++i) { 589 table[i].store(this->segment_allocation_failure_tag, std::memory_order_release); 590 } 591 } 592 }); 593 594 segment_type disabled_segment = nullptr; 595 if (table[0].compare_exchange_strong(disabled_segment, new_segment)) { 596 this->extend_table_if_necessary(table, 0, first_block_size); 597 for (size_type i = 1; i < first_block; ++i) { 598 table[i].store(new_segment, std::memory_order_release); 599 } 600 601 // Other threads can wait on a snapshot of an embedded table, need to fill it. 602 for (size_type i = 1; i < first_block && i < this->pointers_per_embedded_table; ++i) { 603 this->my_embedded_table[i].store(new_segment, std::memory_order_release); 604 } 605 } else if (new_segment != this->segment_allocation_failure_tag) { 606 // Deallocate the memory 607 segment_element_allocator_traits::deallocate(segment_allocator, new_segment, first_block_size); 608 // 0 segment is already allocated, then it remains to wait until the segments are filled to requested 609 spin_wait_while_eq(table[seg_index], segment_type(nullptr)); 610 } 611 } else { 612 size_type offset = this->segment_base(seg_index); 613 if (index == offset) { 614 __TBB_ASSERT(table[seg_index].load(std::memory_order_relaxed) == nullptr, "Only this thread can enable this segment"); 615 segment_element_allocator_type segment_allocator(base_type::get_allocator()); 616 segment_type new_segment = this->segment_allocation_failure_tag; 617 try_call( [&] { 618 new_segment = segment_element_allocator_traits::allocate(segment_allocator,this->segment_size(seg_index)); 619 // Shift base address to simplify access by index 620 new_segment -= this->segment_base(seg_index); 621 } ).on_completion( [&] { 622 table[seg_index].store(new_segment, std::memory_order_release); 623 }); 624 } else { 625 spin_wait_while_eq(table[seg_index], segment_type(nullptr)); 626 } 627 } 628 return nullptr; 629 } 630 631 // Returns the number of elements in the segment to be destroy 632 size_type number_of_elements_in_segment( segment_index_type seg_index ) { 633 size_type curr_vector_size = this->my_size.load(std::memory_order_relaxed); 634 size_type curr_segment_base = this->segment_base(seg_index); 635 636 if (seg_index == 0) { 637 return std::min(curr_vector_size, this->segment_size(seg_index)); 638 } else { 639 // Perhaps the segment is allocated, but there are no elements in it. 640 if (curr_vector_size < curr_segment_base) { 641 return 0; 642 } 643 return curr_segment_base * 2 > curr_vector_size ? curr_vector_size - curr_segment_base : curr_segment_base; 644 } 645 } 646 647 void deallocate_segment( segment_type address, segment_index_type seg_index ) { 648 segment_element_allocator_type segment_allocator(base_type::get_allocator()); 649 size_type first_block = this->my_first_block.load(std::memory_order_relaxed); 650 if (seg_index >= first_block) { 651 segment_element_allocator_traits::deallocate(segment_allocator, address, this->segment_size(seg_index)); 652 } 653 else if (seg_index == 0) { 654 size_type elements_to_deallocate = first_block > 0 ? this->segment_size(first_block) : this->segment_size(0); 655 segment_element_allocator_traits::deallocate(segment_allocator, address, elements_to_deallocate); 656 } 657 } 658 659 // destroy_segment function is required by the segment_table base class 660 void destroy_segment( segment_type address, segment_index_type seg_index ) { 661 size_type elements_to_destroy = number_of_elements_in_segment(seg_index); 662 segment_element_allocator_type segment_allocator(base_type::get_allocator()); 663 664 for (size_type i = 0; i < elements_to_destroy; ++i) { 665 segment_element_allocator_traits::destroy(segment_allocator, address + i); 666 } 667 668 deallocate_segment(address, seg_index); 669 } 670 671 // copy_segment function is required by the segment_table base class 672 void copy_segment( segment_index_type seg_index, segment_type from, segment_type to ) { 673 size_type i = 0; 674 try_call( [&] { 675 for (; i != number_of_elements_in_segment(seg_index); ++i) { 676 segment_table_allocator_traits::construct(base_type::get_allocator(), to + i, from[i]); 677 } 678 } ).on_exception( [&] { 679 // Zero-initialize items left not constructed after the exception 680 zero_unconstructed_elements(this->get_segment(seg_index) + i, this->segment_size(seg_index) - i); 681 682 segment_index_type last_segment = this->segment_index_of(this->my_size.load(std::memory_order_relaxed)); 683 auto table = this->get_table(); 684 for (segment_index_type j = seg_index + 1; j != last_segment; ++j) { 685 auto curr_segment = table[j].load(std::memory_order_relaxed); 686 if (curr_segment) { 687 zero_unconstructed_elements(curr_segment + this->segment_base(j), this->segment_size(j)); 688 } 689 } 690 this->my_size.store(this->segment_size(seg_index) + i, std::memory_order_relaxed); 691 }); 692 } 693 694 // move_segment function is required by the segment_table base class 695 void move_segment( segment_index_type seg_index, segment_type from, segment_type to ) { 696 size_type i = 0; 697 try_call( [&] { 698 for (; i != number_of_elements_in_segment(seg_index); ++i) { 699 segment_table_allocator_traits::construct(base_type::get_allocator(), to + i, std::move(from[i])); 700 } 701 } ).on_exception( [&] { 702 // Zero-initialize items left not constructed after the exception 703 zero_unconstructed_elements(this->get_segment(seg_index) + i, this->segment_size(seg_index) - i); 704 705 segment_index_type last_segment = this->segment_index_of(this->my_size.load(std::memory_order_relaxed)); 706 auto table = this->get_table(); 707 for (segment_index_type j = seg_index + 1; j != last_segment; ++j) { 708 auto curr_segment = table[j].load(std::memory_order_relaxed); 709 if (curr_segment) { 710 zero_unconstructed_elements(curr_segment + this->segment_base(j), this->segment_size(j)); 711 } 712 } 713 this->my_size.store(this->segment_size(seg_index) + i, std::memory_order_relaxed); 714 }); 715 } 716 717 static constexpr bool is_first_element_in_segment( size_type index ) { 718 // An element is the first in a segment if its index is equal to a power of two 719 return is_power_of_two_at_least(index, 2); 720 } 721 722 const_reference internal_subscript( size_type index ) const { 723 return const_cast<self_type*>(this)->internal_subscript(index); 724 } 725 726 reference internal_subscript( size_type index ) { 727 __TBB_ASSERT(index < this->my_size.load(std::memory_order_relaxed), "Invalid subscript index"); 728 return base_type::template internal_subscript</*allow_out_of_range_access=*/false>(index); 729 } 730 731 const_reference internal_subscript_with_exceptions( size_type index ) const { 732 return const_cast<self_type*>(this)->internal_subscript_with_exceptions(index); 733 } 734 735 reference internal_subscript_with_exceptions( size_type index ) { 736 if (index >= this->my_size.load(std::memory_order_acquire)) { 737 tbb::detail::throw_exception(exception_id::out_of_range); 738 } 739 740 segment_table_type table = this->my_segment_table.load(std::memory_order_acquire); 741 742 size_type seg_index = this->segment_index_of(index); 743 if (base_type::number_of_segments(table) < seg_index) { 744 tbb::detail::throw_exception(exception_id::out_of_range); 745 } 746 747 if (table[seg_index] <= this->segment_allocation_failure_tag) { 748 tbb::detail::throw_exception(exception_id::out_of_range); 749 } 750 751 return base_type::template internal_subscript</*allow_out_of_range_access=*/false>(index); 752 } 753 754 static void zero_unconstructed_elements( pointer start, size_type count ) { 755 std::memset(static_cast<void *>(start), 0, count * sizeof(value_type)); 756 } 757 758 template <typename... Args> 759 iterator internal_emplace_back( Args&&... args ) { 760 size_type old_size = this->my_size++; 761 this->assign_first_block_if_necessary(default_first_block_size); 762 auto element_address = &base_type::template internal_subscript</*allow_out_of_range_access=*/true>(old_size); 763 764 // try_call API is not convenient here due to broken 765 // variadic capture on GCC 4.8.5 766 auto value_guard = make_raii_guard([&] { 767 zero_unconstructed_elements(element_address, /*count =*/1); 768 }); 769 770 segment_table_allocator_traits::construct(base_type::get_allocator(), element_address, std::forward<Args>(args)...); 771 value_guard.dismiss(); 772 return iterator(*this, old_size, element_address); 773 } 774 775 template <typename... Args> 776 void internal_loop_construct( segment_table_type table, size_type start_idx, size_type end_idx, const Args&... args ) { 777 static_assert(sizeof...(Args) < 2, "Too many parameters"); 778 for (size_type idx = start_idx; idx < end_idx; ++idx) { 779 auto element_address = &base_type::template internal_subscript</*allow_out_of_range_access=*/true>(idx); 780 // try_call API is not convenient here due to broken 781 // variadic capture on GCC 4.8.5 782 auto value_guard = make_raii_guard( [&] { 783 segment_index_type last_allocated_segment = this->find_last_allocated_segment(table); 784 size_type segment_size = this->segment_size(last_allocated_segment); 785 end_idx = end_idx < segment_size ? end_idx : segment_size; 786 for (size_type i = idx; i < end_idx; ++i) { 787 zero_unconstructed_elements(&this->internal_subscript(i), /*count =*/1); 788 } 789 }); 790 segment_table_allocator_traits::construct(base_type::get_allocator(), element_address, args...); 791 value_guard.dismiss(); 792 } 793 } 794 795 template <typename ForwardIterator> 796 void internal_loop_construct( segment_table_type table, size_type start_idx, size_type end_idx, ForwardIterator first, ForwardIterator ) { 797 for (size_type idx = start_idx; idx < end_idx; ++idx) { 798 auto element_address = &base_type::template internal_subscript</*allow_out_of_range_access=*/true>(idx); 799 try_call( [&] { 800 segment_table_allocator_traits::construct(base_type::get_allocator(), element_address, *first++); 801 } ).on_exception( [&] { 802 segment_index_type last_allocated_segment = this->find_last_allocated_segment(table); 803 size_type segment_size = this->segment_size(last_allocated_segment); 804 end_idx = end_idx < segment_size ? end_idx : segment_size; 805 for (size_type i = idx; i < end_idx; ++i) { 806 zero_unconstructed_elements(&this->internal_subscript(i), /*count =*/1); 807 } 808 }); 809 } 810 } 811 812 template <typename... Args> 813 iterator internal_grow( size_type start_idx, size_type end_idx, const Args&... args ) { 814 this->assign_first_block_if_necessary(this->segment_index_of(end_idx - 1) + 1); 815 size_type seg_index = this->segment_index_of(end_idx - 1); 816 segment_table_type table = this->get_table(); 817 this->extend_table_if_necessary(table, start_idx, end_idx); 818 819 if (seg_index > this->my_first_block.load(std::memory_order_relaxed)) { 820 // So that other threads be able to work with the last segment of grow_by, allocate it immediately. 821 // If the last segment is not less than the first block 822 if (table[seg_index].load(std::memory_order_relaxed) == nullptr) { 823 size_type first_element = this->segment_base(seg_index); 824 if (first_element >= start_idx && first_element < end_idx) { 825 segment_type segment = table[seg_index].load(std::memory_order_relaxed); 826 base_type::enable_segment(segment, table, seg_index, first_element); 827 } 828 } 829 } 830 831 internal_loop_construct(table, start_idx, end_idx, args...); 832 833 return iterator(*this, start_idx, &base_type::template internal_subscript</*allow_out_of_range_access=*/false>(start_idx)); 834 } 835 836 837 template <typename... Args> 838 iterator internal_grow_by_delta( size_type delta, const Args&... args ) { 839 if (delta == size_type(0)) { 840 return end(); 841 } 842 size_type start_idx = this->my_size.fetch_add(delta); 843 size_type end_idx = start_idx + delta; 844 845 return internal_grow(start_idx, end_idx, args...); 846 } 847 848 template <typename... Args> 849 iterator internal_grow_to_at_least( size_type new_size, const Args&... args ) { 850 size_type old_size = this->my_size.load(std::memory_order_relaxed); 851 if (new_size == size_type(0)) return iterator(*this, 0); 852 while (old_size < new_size && !this->my_size.compare_exchange_weak(old_size, new_size)) 853 {} 854 855 int delta = static_cast<int>(new_size) - static_cast<int>(old_size); 856 if (delta > 0) { 857 return internal_grow(old_size, new_size, args...); 858 } 859 860 size_type end_segment = this->segment_index_of(new_size - 1); 861 862 // Check/wait for segments allocation completes 863 if (end_segment >= this->pointers_per_embedded_table && 864 this->get_table() == this->my_embedded_table) 865 { 866 spin_wait_while_eq(this->my_segment_table, this->my_embedded_table); 867 } 868 869 for (segment_index_type seg_idx = 0; seg_idx <= end_segment; ++seg_idx) { 870 if (this->get_table()[seg_idx].load(std::memory_order_relaxed) == nullptr) { 871 atomic_backoff backoff(true); 872 while (this->get_table()[seg_idx].load(std::memory_order_relaxed) == nullptr) { 873 backoff.pause(); 874 } 875 } 876 } 877 878 #if TBB_USE_DEBUG 879 size_type cap = capacity(); 880 __TBB_ASSERT( cap >= new_size, NULL); 881 #endif 882 return iterator(*this, size()); 883 } 884 885 template <typename... Args> 886 void internal_resize( size_type n, const Args&... args ) { 887 if (n == 0) { 888 clear(); 889 return; 890 } 891 892 size_type old_size = this->my_size.load(std::memory_order_acquire); 893 if (n > old_size) { 894 reserve(n); 895 grow_to_at_least(n, args...); 896 } else { 897 if (old_size == n) { 898 return; 899 } 900 size_type last_segment = this->segment_index_of(old_size - 1); 901 // Delete segments 902 for (size_type seg_idx = this->segment_index_of(n - 1) + 1; seg_idx <= last_segment; ++seg_idx) { 903 this->delete_segment(seg_idx); 904 } 905 906 // If n > segment_size(n) => we need to destroy all of the items in the first segment 907 // Otherwise, we need to destroy only items with the index < n 908 size_type n_segment = this->segment_index_of(n - 1); 909 size_type last_index_to_destroy = std::min(this->segment_base(n_segment) + this->segment_size(n_segment), old_size); 910 // Destroy elements in curr segment 911 for (size_type idx = n; idx < last_index_to_destroy; ++idx) { 912 segment_table_allocator_traits::destroy(base_type::get_allocator(), &base_type::template internal_subscript</*allow_out_of_range_access=*/false>(idx)); 913 } 914 this->my_size.store(n, std::memory_order_release); 915 } 916 } 917 918 void destroy_elements() { 919 allocator_type alloc(base_type::get_allocator()); 920 for (size_type i = 0; i < this->my_size.load(std::memory_order_relaxed); ++i) { 921 allocator_traits_type::destroy(alloc, &base_type::template internal_subscript</*allow_out_of_range_access=*/false>(i)); 922 } 923 this->my_size.store(0, std::memory_order_relaxed); 924 } 925 926 static bool incompact_predicate( size_type size ) { 927 // memory page size 928 const size_type page_size = 4096; 929 return size < page_size || ((size - 1) % page_size < page_size / 2 && size < page_size * 128); 930 } 931 932 void internal_compact() { 933 const size_type curr_size = this->my_size.load(std::memory_order_relaxed); 934 segment_table_type table = this->get_table(); 935 const segment_index_type k_end = this->find_last_allocated_segment(table); // allocated segments 936 const segment_index_type k_stop = curr_size ? this->segment_index_of(curr_size - 1) + 1 : 0; // number of segments to store existing items: 0=>0; 1,2=>1; 3,4=>2; [5-8]=>3;.. 937 const segment_index_type first_block = this->my_first_block; // number of merged segments, getting values from atomics 938 939 segment_index_type k = first_block; 940 if (k_stop < first_block) { 941 k = k_stop; 942 } 943 else { 944 while (k < k_stop && incompact_predicate(this->segment_size(k) * sizeof(value_type))) k++; 945 } 946 947 if (k_stop == k_end && k == first_block) { 948 return; 949 } 950 951 // First segment optimization 952 if (k != first_block && k) { 953 size_type max_block = std::max(first_block, k); 954 955 auto buffer_table = segment_table_allocator_traits::allocate(base_type::get_allocator(), max_block); 956 957 for (size_type seg_idx = 0; seg_idx < max_block; ++seg_idx) { 958 segment_table_allocator_traits::construct(base_type::get_allocator(), &buffer_table[seg_idx], 959 table[seg_idx].load(std::memory_order_relaxed)); 960 table[seg_idx].store(nullptr, std::memory_order_relaxed); 961 } 962 963 this->my_first_block.store(k, std::memory_order_relaxed); 964 size_type index = 0; 965 try_call( [&] { 966 for (; index < std::min(this->segment_size(max_block), curr_size); ++index) { 967 auto element_address = &static_cast<base_type*>(this)->operator[](index); 968 segment_index_type seg_idx = this->segment_index_of(index); 969 segment_table_allocator_traits::construct(base_type::get_allocator(), element_address, 970 std::move_if_noexcept(buffer_table[seg_idx].load(std::memory_order_relaxed)[index])); 971 } 972 } ).on_exception( [&] { 973 segment_element_allocator_type allocator(base_type::get_allocator()); 974 for (size_type i = 0; i < index; ++i) { 975 auto element_adress = &this->operator[](i); 976 segment_element_allocator_traits::destroy(allocator, element_adress); 977 } 978 segment_element_allocator_traits::deallocate(allocator, 979 table[0].load(std::memory_order_relaxed), this->segment_size(max_block)); 980 981 for (size_type seg_idx = 0; seg_idx < max_block; ++seg_idx) { 982 table[seg_idx].store(buffer_table[seg_idx].load(std::memory_order_relaxed), 983 std::memory_order_relaxed); 984 buffer_table[seg_idx].store(nullptr, std::memory_order_relaxed); 985 } 986 segment_table_allocator_traits::deallocate(base_type::get_allocator(), 987 buffer_table, max_block); 988 this->my_first_block.store(first_block, std::memory_order_relaxed); 989 }); 990 991 // Need to correct deallocate old segments 992 // Method destroy_segment respect active first_block, therefore, 993 // in order for the segment deletion to work correctly, set the first_block size that was earlier, 994 // destroy the unnecessary segments. 995 this->my_first_block.store(first_block, std::memory_order_relaxed); 996 for (size_type seg_idx = max_block; seg_idx > 0 ; --seg_idx) { 997 auto curr_segment = buffer_table[seg_idx - 1].load(std::memory_order_relaxed); 998 if (curr_segment != nullptr) { 999 destroy_segment(buffer_table[seg_idx - 1].load(std::memory_order_relaxed) + this->segment_base(seg_idx - 1), 1000 seg_idx - 1); 1001 } 1002 } 1003 1004 this->my_first_block.store(k, std::memory_order_relaxed); 1005 1006 for (size_type seg_idx = 0; seg_idx < max_block; ++seg_idx) { 1007 segment_table_allocator_traits::destroy(base_type::get_allocator(), &buffer_table[seg_idx]); 1008 } 1009 1010 segment_table_allocator_traits::deallocate(base_type::get_allocator(), buffer_table, max_block); 1011 } 1012 // free unnecessary segments allocated by reserve() call 1013 if (k_stop < k_end) { 1014 for (size_type seg_idx = k_end; seg_idx != k_stop; --seg_idx) { 1015 if (table[seg_idx - 1].load(std::memory_order_relaxed) != nullptr) { 1016 this->delete_segment(seg_idx - 1); 1017 } 1018 } 1019 if (!k) this->my_first_block.store(0, std::memory_order_relaxed);; 1020 } 1021 } 1022 1023 // Lever for adjusting the size of first_block at the very first insertion. 1024 // TODO: consider >1 value, check performance 1025 static constexpr size_type default_first_block_size = 1; 1026 1027 template <typename Vector, typename Value> 1028 friend class vector_iterator; 1029 }; // class concurrent_vector 1030 1031 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1032 // Deduction guide for the constructor from two iterators 1033 template <typename It, typename Alloc = tbb::cache_aligned_allocator<iterator_value_t<It>>, 1034 typename = std::enable_if_t<is_input_iterator_v<It>>, 1035 typename = std::enable_if_t<is_allocator_v<Alloc>>> 1036 concurrent_vector( It, It, Alloc = Alloc() ) 1037 -> concurrent_vector<iterator_value_t<It>, Alloc>; 1038 #endif 1039 1040 template <typename T, typename Allocator> 1041 void swap(concurrent_vector<T, Allocator> &lhs, 1042 concurrent_vector<T, Allocator> &rhs) 1043 { 1044 lhs.swap(rhs); 1045 } 1046 1047 template <typename T, typename Allocator> 1048 bool operator==(const concurrent_vector<T, Allocator> &lhs, 1049 const concurrent_vector<T, Allocator> &rhs) 1050 { 1051 return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); 1052 } 1053 1054 #if !__TBB_CPP20_COMPARISONS_PRESENT 1055 template <typename T, typename Allocator> 1056 bool operator!=(const concurrent_vector<T, Allocator> &lhs, 1057 const concurrent_vector<T, Allocator> &rhs) 1058 { 1059 return !(lhs == rhs); 1060 } 1061 #endif // !__TBB_CPP20_COMPARISONS_PRESENT 1062 1063 #if __TBB_CPP20_COMPARISONS_PRESENT && __TBB_CPP20_CONCEPTS_PRESENT 1064 template <typename T, typename Allocator> 1065 tbb::detail::synthesized_three_way_result<typename concurrent_vector<T, Allocator>::value_type> 1066 operator<=>(const concurrent_vector<T, Allocator> &lhs, 1067 const concurrent_vector<T, Allocator> &rhs) 1068 { 1069 return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), 1070 rhs.begin(), rhs.end(), 1071 tbb::detail::synthesized_three_way_comparator{}); 1072 } 1073 1074 #else 1075 1076 template <typename T, typename Allocator> 1077 bool operator<(const concurrent_vector<T, Allocator> &lhs, 1078 const concurrent_vector<T, Allocator> &rhs) 1079 { 1080 return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 1081 } 1082 1083 template <typename T, typename Allocator> 1084 bool operator<=(const concurrent_vector<T, Allocator> &lhs, 1085 const concurrent_vector<T, Allocator> &rhs) 1086 { 1087 return !(rhs < lhs); 1088 } 1089 1090 template <typename T, typename Allocator> 1091 bool operator>(const concurrent_vector<T, Allocator> &lhs, 1092 const concurrent_vector<T, Allocator> &rhs) 1093 { 1094 return rhs < lhs; 1095 } 1096 1097 template <typename T, typename Allocator> 1098 bool operator>=(const concurrent_vector<T, Allocator> &lhs, 1099 const concurrent_vector<T, Allocator> &rhs) 1100 { 1101 return !(lhs < rhs); 1102 } 1103 #endif // __TBB_CPP20_COMPARISONS_PRESENT && __TBB_CPP20_CONCEPTS_PRESENT 1104 1105 } // namespace d1 1106 } // namespace detail 1107 1108 inline namespace v1 { 1109 using detail::d1::concurrent_vector; 1110 } // namespace v1 1111 1112 } // namespace tbb 1113 1114 #endif // __TBB_concurrent_vector_H 1115