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