1 /* 2 Copyright (c) 2005-2022 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_detail__concurrent_unordered_base_H 18 #define __TBB_detail__concurrent_unordered_base_H 19 20 #if !defined(__TBB_concurrent_unordered_map_H) && !defined(__TBB_concurrent_unordered_set_H) 21 #error Do not #include this internal file directly; use public TBB headers instead. 22 #endif 23 24 #include "_range_common.h" 25 #include "_containers_helpers.h" 26 #include "_segment_table.h" 27 #include "_hash_compare.h" 28 #include "_allocator_traits.h" 29 #include "_node_handle.h" 30 #include "_assert.h" 31 #include "_utils.h" 32 #include "_exception.h" 33 #include <iterator> 34 #include <utility> 35 #include <functional> 36 #include <initializer_list> 37 #include <atomic> 38 #include <type_traits> 39 #include <memory> 40 #include <algorithm> 41 42 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) 43 #pragma warning(push) 44 #pragma warning(disable: 4127) // warning C4127: conditional expression is constant 45 #endif 46 47 namespace tbb { 48 namespace detail { 49 namespace d1 { 50 51 template <typename Traits> 52 class concurrent_unordered_base; 53 54 template<typename Container, typename Value> 55 class solist_iterator { 56 private: 57 using node_ptr = typename Container::value_node_ptr; 58 template <typename T, typename Allocator> 59 friend class split_ordered_list; 60 template<typename M, typename V> 61 friend class solist_iterator; 62 template <typename Traits> 63 friend class concurrent_unordered_base; 64 template<typename M, typename T, typename U> 65 friend bool operator==( const solist_iterator<M,T>& i, const solist_iterator<M,U>& j ); 66 template<typename M, typename T, typename U> 67 friend bool operator!=( const solist_iterator<M,T>& i, const solist_iterator<M,U>& j ); 68 public: 69 using value_type = Value; 70 using difference_type = typename Container::difference_type; 71 using pointer = value_type*; 72 using reference = value_type&; 73 using iterator_category = std::forward_iterator_tag; 74 75 solist_iterator() : my_node_ptr(nullptr) {} 76 solist_iterator( const solist_iterator<Container, typename Container::value_type>& other ) 77 : my_node_ptr(other.my_node_ptr) {} 78 79 solist_iterator& operator=( const solist_iterator<Container, typename Container::value_type>& other ) { 80 my_node_ptr = other.my_node_ptr; 81 return *this; 82 } 83 84 reference operator*() const { 85 return my_node_ptr->value(); 86 } 87 88 pointer operator->() const { 89 return my_node_ptr->storage(); 90 } 91 92 solist_iterator& operator++() { 93 auto next_node = my_node_ptr->next(); 94 while(next_node && next_node->is_dummy()) { 95 next_node = next_node->next(); 96 } 97 my_node_ptr = static_cast<node_ptr>(next_node); 98 return *this; 99 } 100 101 solist_iterator operator++(int) { 102 solist_iterator tmp = *this; 103 ++*this; 104 return tmp; 105 } 106 107 private: 108 solist_iterator( node_ptr pnode ) : my_node_ptr(pnode) {} 109 110 node_ptr get_node_ptr() const { return my_node_ptr; } 111 112 node_ptr my_node_ptr; 113 }; 114 115 template<typename Solist, typename T, typename U> 116 bool operator==( const solist_iterator<Solist, T>& i, const solist_iterator<Solist, U>& j ) { 117 return i.my_node_ptr == j.my_node_ptr; 118 } 119 120 template<typename Solist, typename T, typename U> 121 bool operator!=( const solist_iterator<Solist, T>& i, const solist_iterator<Solist, U>& j ) { 122 return i.my_node_ptr != j.my_node_ptr; 123 } 124 125 template <typename SokeyType> 126 class list_node { 127 public: 128 using node_ptr = list_node*; 129 using sokey_type = SokeyType; 130 131 list_node(sokey_type key) : my_next(nullptr), my_order_key(key) {} 132 133 void init( sokey_type key ) { 134 my_order_key = key; 135 } 136 137 sokey_type order_key() const { 138 return my_order_key; 139 } 140 141 bool is_dummy() { 142 // The last bit of order key is unset for dummy nodes 143 return (my_order_key & 0x1) == 0; 144 } 145 146 node_ptr next() const { 147 return my_next.load(std::memory_order_acquire); 148 } 149 150 void set_next( node_ptr next_node ) { 151 my_next.store(next_node, std::memory_order_release); 152 } 153 154 bool try_set_next( node_ptr expected_next, node_ptr new_next ) { 155 return my_next.compare_exchange_strong(expected_next, new_next); 156 } 157 158 private: 159 std::atomic<node_ptr> my_next; 160 sokey_type my_order_key; 161 }; // class list_node 162 163 template <typename ValueType, typename SokeyType> 164 class value_node : public list_node<SokeyType> 165 { 166 public: 167 using base_type = list_node<SokeyType>; 168 using sokey_type = typename base_type::sokey_type; 169 using value_type = ValueType; 170 171 value_node( sokey_type ord_key ) : base_type(ord_key) {} 172 ~value_node() {} 173 value_type* storage() { 174 return reinterpret_cast<value_type*>(&my_value); 175 } 176 177 value_type& value() { 178 return *storage(); 179 } 180 181 private: 182 using aligned_storage_type = typename std::aligned_storage<sizeof(value_type)>::type; 183 aligned_storage_type my_value; 184 }; // class value_node 185 186 template <typename Traits> 187 class concurrent_unordered_base { 188 using self_type = concurrent_unordered_base<Traits>; 189 using traits_type = Traits; 190 using hash_compare_type = typename traits_type::hash_compare_type; 191 class unordered_segment_table; 192 public: 193 using value_type = typename traits_type::value_type; 194 using key_type = typename traits_type::key_type; 195 using allocator_type = typename traits_type::allocator_type; 196 197 private: 198 using allocator_traits_type = tbb::detail::allocator_traits<allocator_type>; 199 // TODO: check assert conditions for different C++ standards 200 static_assert(std::is_same<typename allocator_traits_type::value_type, value_type>::value, 201 "value_type of the container must be the same as its allocator"); 202 using sokey_type = std::size_t; 203 204 public: 205 using size_type = std::size_t; 206 using difference_type = std::ptrdiff_t; 207 208 using iterator = solist_iterator<self_type, value_type>; 209 using const_iterator = solist_iterator<self_type, const value_type>; 210 using local_iterator = iterator; 211 using const_local_iterator = const_iterator; 212 213 using reference = value_type&; 214 using const_reference = const value_type&; 215 using pointer = typename allocator_traits_type::pointer; 216 using const_pointer = typename allocator_traits_type::const_pointer; 217 218 using hasher = typename hash_compare_type::hasher; 219 using key_equal = typename hash_compare_type::key_equal; 220 221 private: 222 using list_node_type = list_node<sokey_type>; 223 using value_node_type = value_node<value_type, sokey_type>; 224 using node_ptr = list_node_type*; 225 using value_node_ptr = value_node_type*; 226 227 using value_node_allocator_type = typename allocator_traits_type::template rebind_alloc<value_node_type>; 228 using node_allocator_type = typename allocator_traits_type::template rebind_alloc<list_node_type>; 229 230 using node_allocator_traits = tbb::detail::allocator_traits<node_allocator_type>; 231 using value_node_allocator_traits = tbb::detail::allocator_traits<value_node_allocator_type>; 232 233 static constexpr size_type round_up_to_power_of_two( size_type bucket_count ) { 234 return size_type(1) << size_type(tbb::detail::log2(uintptr_t(bucket_count == 0 ? 1 : bucket_count) * 2 - 1)); 235 } 236 237 template <typename T> 238 using is_transparent = dependent_bool<has_transparent_key_equal<key_type, hasher, key_equal>, T>; 239 public: 240 using node_type = node_handle<key_type, value_type, value_node_type, allocator_type>; 241 242 explicit concurrent_unordered_base( size_type bucket_count, const hasher& hash = hasher(), 243 const key_equal& equal = key_equal(), const allocator_type& alloc = allocator_type() ) 244 : my_size(0), 245 my_bucket_count(round_up_to_power_of_two(bucket_count)), 246 my_max_load_factor(float(initial_max_load_factor)), 247 my_hash_compare(hash, equal), 248 my_head(sokey_type(0)), 249 my_segments(alloc) {} 250 251 concurrent_unordered_base() : concurrent_unordered_base(initial_bucket_count) {} 252 253 concurrent_unordered_base( size_type bucket_count, const allocator_type& alloc ) 254 : concurrent_unordered_base(bucket_count, hasher(), key_equal(), alloc) {} 255 256 concurrent_unordered_base( size_type bucket_count, const hasher& hash, const allocator_type& alloc ) 257 : concurrent_unordered_base(bucket_count, hash, key_equal(), alloc) {} 258 259 explicit concurrent_unordered_base( const allocator_type& alloc ) 260 : concurrent_unordered_base(initial_bucket_count, hasher(), key_equal(), alloc) {} 261 262 template <typename InputIterator> 263 concurrent_unordered_base( InputIterator first, InputIterator last, 264 size_type bucket_count = initial_bucket_count, const hasher& hash = hasher(), 265 const key_equal& equal = key_equal(), const allocator_type& alloc = allocator_type() ) 266 : concurrent_unordered_base(bucket_count, hash, equal, alloc) 267 { 268 insert(first, last); 269 } 270 271 template <typename InputIterator> 272 concurrent_unordered_base( InputIterator first, InputIterator last, 273 size_type bucket_count, const allocator_type& alloc ) 274 : concurrent_unordered_base(first, last, bucket_count, hasher(), key_equal(), alloc) {} 275 276 template <typename InputIterator> 277 concurrent_unordered_base( InputIterator first, InputIterator last, 278 size_type bucket_count, const hasher& hash, const allocator_type& alloc ) 279 : concurrent_unordered_base(first, last, bucket_count, hash, key_equal(), alloc) {} 280 281 concurrent_unordered_base( const concurrent_unordered_base& other ) 282 : my_size(other.my_size.load(std::memory_order_relaxed)), 283 my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)), 284 my_max_load_factor(other.my_max_load_factor), 285 my_hash_compare(other.my_hash_compare), 286 my_head(other.my_head.order_key()), 287 my_segments(other.my_segments) 288 { 289 try_call( [&] { 290 internal_copy(other); 291 } ).on_exception( [&] { 292 clear(); 293 }); 294 } 295 296 concurrent_unordered_base( const concurrent_unordered_base& other, const allocator_type& alloc ) 297 : my_size(other.my_size.load(std::memory_order_relaxed)), 298 my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)), 299 my_max_load_factor(other.my_max_load_factor), 300 my_hash_compare(other.my_hash_compare), 301 my_head(other.my_head.order_key()), 302 my_segments(other.my_segments, alloc) 303 { 304 try_call( [&] { 305 internal_copy(other); 306 } ).on_exception( [&] { 307 clear(); 308 }); 309 } 310 311 concurrent_unordered_base( concurrent_unordered_base&& other ) 312 : my_size(other.my_size.load(std::memory_order_relaxed)), 313 my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)), 314 my_max_load_factor(std::move(other.my_max_load_factor)), 315 my_hash_compare(std::move(other.my_hash_compare)), 316 my_head(other.my_head.order_key()), 317 my_segments(std::move(other.my_segments)) 318 { 319 move_content(std::move(other)); 320 } 321 322 concurrent_unordered_base( concurrent_unordered_base&& other, const allocator_type& alloc ) 323 : my_size(other.my_size.load(std::memory_order_relaxed)), 324 my_bucket_count(other.my_bucket_count.load(std::memory_order_relaxed)), 325 my_max_load_factor(std::move(other.my_max_load_factor)), 326 my_hash_compare(std::move(other.my_hash_compare)), 327 my_head(other.my_head.order_key()), 328 my_segments(std::move(other.my_segments), alloc) 329 { 330 using is_always_equal = typename allocator_traits_type::is_always_equal; 331 internal_move_construct_with_allocator(std::move(other), alloc, is_always_equal()); 332 } 333 334 concurrent_unordered_base( std::initializer_list<value_type> init, 335 size_type bucket_count = initial_bucket_count, 336 const hasher& hash = hasher(), const key_equal& equal = key_equal(), 337 const allocator_type& alloc = allocator_type() ) 338 : concurrent_unordered_base(init.begin(), init.end(), bucket_count, hash, equal, alloc) {} 339 340 concurrent_unordered_base( std::initializer_list<value_type> init, 341 size_type bucket_count, const allocator_type& alloc ) 342 : concurrent_unordered_base(init, bucket_count, hasher(), key_equal(), alloc) {} 343 344 concurrent_unordered_base( std::initializer_list<value_type> init, 345 size_type bucket_count, const hasher& hash, const allocator_type& alloc ) 346 : concurrent_unordered_base(init, bucket_count, hash, key_equal(), alloc) {} 347 348 ~concurrent_unordered_base() { 349 internal_clear(); 350 } 351 352 concurrent_unordered_base& operator=( const concurrent_unordered_base& other ) { 353 if (this != &other) { 354 clear(); 355 my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); 356 my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed); 357 my_max_load_factor = other.my_max_load_factor; 358 my_hash_compare = other.my_hash_compare; 359 my_segments = other.my_segments; 360 internal_copy(other); // TODO: guards for exceptions? 361 } 362 return *this; 363 } 364 365 concurrent_unordered_base& operator=( concurrent_unordered_base&& other ) noexcept(unordered_segment_table::is_noexcept_assignment) { 366 if (this != &other) { 367 clear(); 368 my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); 369 my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed); 370 my_max_load_factor = std::move(other.my_max_load_factor); 371 my_hash_compare = std::move(other.my_hash_compare); 372 my_segments = std::move(other.my_segments); 373 374 using pocma_type = typename allocator_traits_type::propagate_on_container_move_assignment; 375 using is_always_equal = typename allocator_traits_type::is_always_equal; 376 internal_move_assign(std::move(other), tbb::detail::disjunction<pocma_type, is_always_equal>()); 377 } 378 return *this; 379 } 380 381 concurrent_unordered_base& operator=( std::initializer_list<value_type> init ) { 382 clear(); 383 insert(init); 384 return *this; 385 } 386 387 void swap( concurrent_unordered_base& other ) noexcept(unordered_segment_table::is_noexcept_swap) { 388 if (this != &other) { 389 using pocs_type = typename allocator_traits_type::propagate_on_container_swap; 390 using is_always_equal = typename allocator_traits_type::is_always_equal; 391 internal_swap(other, tbb::detail::disjunction<pocs_type, is_always_equal>()); 392 } 393 } 394 395 allocator_type get_allocator() const noexcept { return my_segments.get_allocator(); } 396 397 iterator begin() noexcept { return iterator(first_value_node(&my_head)); } 398 const_iterator begin() const noexcept { return const_iterator(first_value_node(const_cast<node_ptr>(&my_head))); } 399 const_iterator cbegin() const noexcept { return const_iterator(first_value_node(const_cast<node_ptr>(&my_head))); } 400 401 iterator end() noexcept { return iterator(nullptr); } 402 const_iterator end() const noexcept { return const_iterator(nullptr); } 403 const_iterator cend() const noexcept { return const_iterator(nullptr); } 404 405 __TBB_nodiscard bool empty() const noexcept { return size() == 0; } 406 size_type size() const noexcept { return my_size.load(std::memory_order_relaxed); } 407 size_type max_size() const noexcept { return allocator_traits_type::max_size(get_allocator()); } 408 409 void clear() noexcept { 410 internal_clear(); 411 } 412 413 std::pair<iterator, bool> insert( const value_type& value ) { 414 return internal_insert_value(value); 415 } 416 417 std::pair<iterator, bool> insert( value_type&& value ) { 418 return internal_insert_value(std::move(value)); 419 } 420 421 iterator insert( const_iterator, const value_type& value ) { 422 // Ignore hint 423 return insert(value).first; 424 } 425 426 iterator insert( const_iterator, value_type&& value ) { 427 // Ignore hint 428 return insert(std::move(value)).first; 429 } 430 431 template <typename InputIterator> 432 void insert( InputIterator first, InputIterator last ) { 433 for (; first != last; ++first) { 434 insert(*first); 435 } 436 } 437 438 void insert( std::initializer_list<value_type> init ) { 439 insert(init.begin(), init.end()); 440 } 441 442 std::pair<iterator, bool> insert( node_type&& nh ) { 443 if (!nh.empty()) { 444 value_node_ptr insert_node = node_handle_accessor::get_node_ptr(nh); 445 auto init_node = [&insert_node]( sokey_type order_key )->value_node_ptr { 446 insert_node->init(order_key); 447 return insert_node; 448 }; 449 auto insert_result = internal_insert(insert_node->value(), init_node); 450 if (insert_result.inserted) { 451 // If the insertion succeeded - set node handle to the empty state 452 __TBB_ASSERT(insert_result.remaining_node == nullptr, 453 "internal_insert_node should not return the remaining node if the insertion succeeded"); 454 node_handle_accessor::deactivate(nh); 455 } 456 return { iterator(insert_result.node_with_equal_key), insert_result.inserted }; 457 } 458 return {end(), false}; 459 } 460 461 iterator insert( const_iterator, node_type&& nh ) { 462 // Ignore hint 463 return insert(std::move(nh)).first; 464 } 465 466 template <typename... Args> 467 std::pair<iterator, bool> emplace( Args&&... args ) { 468 // Create a node with temporary order_key 0, which will be reinitialize 469 // in internal_insert after the hash calculation 470 value_node_ptr insert_node = create_node(0, std::forward<Args>(args)...); 471 472 auto init_node = [&insert_node]( sokey_type order_key )->value_node_ptr { 473 insert_node->init(order_key); 474 return insert_node; 475 }; 476 477 auto insert_result = internal_insert(insert_node->value(), init_node); 478 479 if (!insert_result.inserted) { 480 // If the insertion failed - destroy the node which was created 481 insert_node->init(split_order_key_regular(1)); 482 destroy_node(insert_node); 483 } 484 485 return { iterator(insert_result.node_with_equal_key), insert_result.inserted }; 486 } 487 488 template <typename... Args> 489 iterator emplace_hint( const_iterator, Args&&... args ) { 490 // Ignore hint 491 return emplace(std::forward<Args>(args)...).first; 492 } 493 494 iterator unsafe_erase( const_iterator pos ) { 495 return iterator(first_value_node(internal_erase(pos.get_node_ptr()))); 496 } 497 498 iterator unsafe_erase( iterator pos ) { 499 return iterator(first_value_node(internal_erase(pos.get_node_ptr()))); 500 } 501 502 iterator unsafe_erase( const_iterator first, const_iterator last ) { 503 while(first != last) { 504 first = unsafe_erase(first); 505 } 506 return iterator(first.get_node_ptr()); 507 } 508 509 size_type unsafe_erase( const key_type& key ) { 510 return internal_erase_by_key(key); 511 } 512 513 template <typename K> 514 typename std::enable_if<is_transparent<K>::value 515 && !std::is_convertible<K, const_iterator>::value 516 && !std::is_convertible<K, iterator>::value, 517 size_type>::type unsafe_erase( const K& key ) 518 { 519 return internal_erase_by_key(key); 520 } 521 522 node_type unsafe_extract( const_iterator pos ) { 523 internal_extract(pos.get_node_ptr()); 524 return node_handle_accessor::construct<node_type>(pos.get_node_ptr()); 525 } 526 527 node_type unsafe_extract( iterator pos ) { 528 internal_extract(pos.get_node_ptr()); 529 return node_handle_accessor::construct<node_type>(pos.get_node_ptr()); 530 } 531 532 node_type unsafe_extract( const key_type& key ) { 533 iterator item = find(key); 534 return item == end() ? node_type() : unsafe_extract(item); 535 } 536 537 template <typename K> 538 typename std::enable_if<is_transparent<K>::value 539 && !std::is_convertible<K, const_iterator>::value 540 && !std::is_convertible<K, iterator>::value, 541 node_type>::type unsafe_extract( const K& key ) 542 { 543 iterator item = find(key); 544 return item == end() ? node_type() : unsafe_extract(item); 545 } 546 547 // Lookup functions 548 iterator find( const key_type& key ) { 549 value_node_ptr result = internal_find(key); 550 return result == nullptr ? end() : iterator(result); 551 } 552 553 const_iterator find( const key_type& key ) const { 554 value_node_ptr result = const_cast<self_type*>(this)->internal_find(key); 555 return result == nullptr ? end() : const_iterator(result); 556 } 557 558 template <typename K> 559 typename std::enable_if<is_transparent<K>::value, iterator>::type find( const K& key ) { 560 value_node_ptr result = internal_find(key); 561 return result == nullptr ? end() : iterator(result); 562 } 563 564 template <typename K> 565 typename std::enable_if<is_transparent<K>::value, const_iterator>::type find( const K& key ) const { 566 value_node_ptr result = const_cast<self_type*>(this)->internal_find(key); 567 return result == nullptr ? end() : const_iterator(result); 568 } 569 570 std::pair<iterator, iterator> equal_range( const key_type& key ) { 571 auto result = internal_equal_range(key); 572 return std::make_pair(iterator(result.first), iterator(result.second)); 573 } 574 575 std::pair<const_iterator, const_iterator> equal_range( const key_type& key ) const { 576 auto result = const_cast<self_type*>(this)->internal_equal_range(key); 577 return std::make_pair(const_iterator(result.first), const_iterator(result.second)); 578 } 579 580 template <typename K> 581 typename std::enable_if<is_transparent<K>::value, std::pair<iterator, iterator>>::type equal_range( const K& key ) { 582 auto result = internal_equal_range(key); 583 return std::make_pair(iterator(result.first), iterator(result.second)); 584 } 585 586 template <typename K> 587 typename std::enable_if<is_transparent<K>::value, std::pair<const_iterator, const_iterator>>::type equal_range( const K& key ) const { 588 auto result = const_cast<self_type*>(this)->internal_equal_range(key); 589 return std::make_pair(iterator(result.first), iterator(result.second)); 590 } 591 592 size_type count( const key_type& key ) const { 593 return internal_count(key); 594 } 595 596 template <typename K> 597 typename std::enable_if<is_transparent<K>::value, size_type>::type count( const K& key ) const { 598 return internal_count(key); 599 } 600 601 bool contains( const key_type& key ) const { 602 return find(key) != end(); 603 } 604 605 template <typename K> 606 typename std::enable_if<is_transparent<K>::value, bool>::type contains( const K& key ) const { 607 return find(key) != end(); 608 } 609 610 // Bucket interface 611 local_iterator unsafe_begin( size_type n ) { 612 return local_iterator(first_value_node(get_bucket(n))); 613 } 614 615 const_local_iterator unsafe_begin( size_type n ) const { 616 auto bucket_begin = first_value_node(const_cast<self_type*>(this)->get_bucket(n)); 617 return const_local_iterator(bucket_begin); 618 } 619 620 const_local_iterator unsafe_cbegin( size_type n ) const { 621 auto bucket_begin = first_value_node(const_cast<self_type*>(this)->get_bucket(n)); 622 return const_local_iterator(bucket_begin); 623 } 624 625 local_iterator unsafe_end( size_type n ) { 626 size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed); 627 return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : local_iterator(nullptr); 628 } 629 630 const_local_iterator unsafe_end( size_type n ) const { 631 size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed); 632 return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : const_local_iterator(nullptr); 633 } 634 635 const_local_iterator unsafe_cend( size_type n ) const { 636 size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed); 637 return n != bucket_count - 1 ? unsafe_begin(get_next_bucket_index(n)) : const_local_iterator(nullptr); 638 } 639 640 size_type unsafe_bucket_count() const { return my_bucket_count.load(std::memory_order_relaxed); } 641 642 size_type unsafe_max_bucket_count() const { 643 return max_size(); 644 } 645 646 size_type unsafe_bucket_size( size_type n ) const { 647 return size_type(std::distance(unsafe_begin(n), unsafe_end(n))); 648 } 649 650 size_type unsafe_bucket( const key_type& key ) const { 651 return my_hash_compare(key) % my_bucket_count.load(std::memory_order_relaxed); 652 } 653 654 // Hash policy 655 float load_factor() const { 656 return float(size() / float(my_bucket_count.load(std::memory_order_acquire))); 657 } 658 659 float max_load_factor() const { return my_max_load_factor; } 660 661 void max_load_factor( float mlf ) { 662 if (mlf != mlf || mlf < 0) { 663 tbb::detail::throw_exception(exception_id::invalid_load_factor); 664 } 665 my_max_load_factor = mlf; 666 } // TODO: unsafe? 667 668 void rehash( size_type bucket_count ) { 669 size_type current_bucket_count = my_bucket_count.load(std::memory_order_acquire); 670 if (current_bucket_count < bucket_count) { 671 // TODO: do we need do-while here? 672 my_bucket_count.compare_exchange_strong(current_bucket_count, round_up_to_power_of_two(bucket_count)); 673 } 674 } 675 676 void reserve( size_type elements_count ) { 677 size_type current_bucket_count = my_bucket_count.load(std::memory_order_acquire); 678 size_type necessary_bucket_count = current_bucket_count; 679 680 do { 681 // TODO: Log2 seems useful here 682 while (necessary_bucket_count * max_load_factor() < elements_count) { 683 necessary_bucket_count <<= 1; 684 } 685 } while (current_bucket_count >= necessary_bucket_count || 686 !my_bucket_count.compare_exchange_strong(current_bucket_count, necessary_bucket_count)); 687 } 688 689 // Observers 690 hasher hash_function() const { return my_hash_compare.hash_function(); } 691 key_equal key_eq() const { return my_hash_compare.key_eq(); } 692 693 class const_range_type { 694 private: 695 const concurrent_unordered_base& my_instance; 696 node_ptr my_begin_node; // may be node* const 697 node_ptr my_end_node; 698 mutable node_ptr my_midpoint_node; 699 public: 700 using size_type = typename concurrent_unordered_base::size_type; 701 using value_type = typename concurrent_unordered_base::value_type; 702 using reference = typename concurrent_unordered_base::reference; 703 using difference_type = typename concurrent_unordered_base::difference_type; 704 using iterator = typename concurrent_unordered_base::const_iterator; 705 706 bool empty() const { return my_begin_node == my_end_node; } 707 708 bool is_divisible() const { 709 return my_midpoint_node != my_end_node; 710 } 711 712 size_type grainsize() const { return 1; } 713 714 const_range_type( const_range_type& range, split ) 715 : my_instance(range.my_instance), 716 my_begin_node(range.my_midpoint_node), 717 my_end_node(range.my_end_node) 718 { 719 range.my_end_node = my_begin_node; 720 __TBB_ASSERT(!empty(), "Splitting despite the range is not divisible"); 721 __TBB_ASSERT(!range.empty(), "Splitting despite the range is not divisible"); 722 set_midpoint(); 723 range.set_midpoint(); 724 } 725 726 iterator begin() const { return iterator(my_instance.first_value_node(my_begin_node)); } 727 iterator end() const { return iterator(my_instance.first_value_node(my_end_node)); } 728 729 const_range_type( const concurrent_unordered_base& table ) 730 : my_instance(table), my_begin_node(my_instance.first_value_node(const_cast<node_ptr>(&table.my_head))), my_end_node(nullptr) 731 { 732 set_midpoint(); 733 } 734 private: 735 void set_midpoint() const { 736 if (empty()) { 737 my_midpoint_node = my_end_node; 738 } else { 739 sokey_type invalid_key = ~sokey_type(0); 740 sokey_type begin_key = my_begin_node != nullptr ? my_begin_node->order_key() : invalid_key; 741 sokey_type end_key = my_end_node != nullptr ? my_end_node->order_key() : invalid_key; 742 743 size_type mid_bucket = reverse_bits(begin_key + (end_key - begin_key) / 2) % 744 my_instance.my_bucket_count.load(std::memory_order_relaxed); 745 while( my_instance.my_segments[mid_bucket].load(std::memory_order_relaxed) == nullptr) { 746 mid_bucket = my_instance.get_parent(mid_bucket); 747 } 748 if (reverse_bits(mid_bucket) > begin_key) { 749 // Found a dummy node between begin and end 750 my_midpoint_node = my_instance.first_value_node( 751 my_instance.my_segments[mid_bucket].load(std::memory_order_relaxed)); 752 } else { 753 // Didn't find a dummy node between begin and end 754 my_midpoint_node = my_end_node; 755 } 756 } 757 } 758 }; // class const_range_type 759 760 class range_type : public const_range_type { 761 public: 762 using iterator = typename concurrent_unordered_base::iterator; 763 using const_range_type::const_range_type; 764 765 iterator begin() const { return iterator(const_range_type::begin().get_node_ptr()); } 766 iterator end() const { return iterator(const_range_type::end().get_node_ptr()); } 767 }; // class range_type 768 769 // Parallel iteration 770 range_type range() { 771 return range_type(*this); 772 } 773 774 const_range_type range() const { 775 return const_range_type(*this); 776 } 777 protected: 778 static constexpr bool allow_multimapping = traits_type::allow_multimapping; 779 780 private: 781 static constexpr size_type initial_bucket_count = 8; 782 static constexpr float initial_max_load_factor = 4; // TODO: consider 1? 783 static constexpr size_type pointers_per_embedded_table = sizeof(size_type) * 8 - 1; 784 785 class unordered_segment_table 786 : public segment_table<std::atomic<node_ptr>, allocator_type, unordered_segment_table, pointers_per_embedded_table> 787 { 788 using self_type = unordered_segment_table; 789 using atomic_node_ptr = std::atomic<node_ptr>; 790 using base_type = segment_table<std::atomic<node_ptr>, allocator_type, unordered_segment_table, pointers_per_embedded_table>; 791 using segment_type = typename base_type::segment_type; 792 using base_allocator_type = typename base_type::allocator_type; 793 794 using segment_allocator_type = typename allocator_traits_type::template rebind_alloc<atomic_node_ptr>; 795 using segment_allocator_traits = tbb::detail::allocator_traits<segment_allocator_type>; 796 public: 797 // Segment table for unordered containers should not be extended in the wait- free implementation 798 static constexpr bool allow_table_extending = false; 799 static constexpr bool is_noexcept_assignment = std::is_nothrow_move_assignable<hasher>::value && 800 std::is_nothrow_move_assignable<key_equal>::value && 801 segment_allocator_traits::is_always_equal::value; 802 static constexpr bool is_noexcept_swap = tbb::detail::is_nothrow_swappable<hasher>::value && 803 tbb::detail::is_nothrow_swappable<key_equal>::value && 804 segment_allocator_traits::is_always_equal::value; 805 806 // TODO: using base_type::base_type is not compiling on Windows and Intel Compiler - investigate 807 unordered_segment_table( const base_allocator_type& alloc = base_allocator_type() ) 808 : base_type(alloc) {} 809 810 unordered_segment_table( const unordered_segment_table& ) = default; 811 812 unordered_segment_table( const unordered_segment_table& other, const base_allocator_type& alloc ) 813 : base_type(other, alloc) {} 814 815 unordered_segment_table( unordered_segment_table&& ) = default; 816 817 unordered_segment_table( unordered_segment_table&& other, const base_allocator_type& alloc ) 818 : base_type(std::move(other), alloc) {} 819 820 unordered_segment_table& operator=( const unordered_segment_table& ) = default; 821 822 unordered_segment_table& operator=( unordered_segment_table&& ) = default; 823 824 segment_type create_segment( typename base_type::segment_table_type, typename base_type::segment_index_type segment_index, size_type ) { 825 segment_allocator_type alloc(this->get_allocator()); 826 size_type seg_size = this->segment_size(segment_index); 827 segment_type new_segment = segment_allocator_traits::allocate(alloc, seg_size); 828 for (size_type i = 0; i != seg_size; ++i) { 829 segment_allocator_traits::construct(alloc, new_segment + i, nullptr); 830 } 831 return new_segment; 832 } 833 834 segment_type nullify_segment( typename base_type::segment_table_type table, size_type segment_index ) { 835 segment_type target_segment = table[segment_index].load(std::memory_order_relaxed); 836 table[segment_index].store(nullptr, std::memory_order_relaxed); 837 return target_segment; 838 } 839 840 // deallocate_segment is required by the segment_table base class, but 841 // in unordered, it is also necessary to call the destructor during deallocation 842 void deallocate_segment( segment_type address, size_type index ) { 843 destroy_segment(address, index); 844 } 845 846 void destroy_segment( segment_type address, size_type index ) { 847 segment_allocator_type alloc(this->get_allocator()); 848 for (size_type i = 0; i != this->segment_size(index); ++i) { 849 segment_allocator_traits::destroy(alloc, address + i); 850 } 851 segment_allocator_traits::deallocate(alloc, address, this->segment_size(index)); 852 } 853 854 855 void copy_segment( size_type index, segment_type, segment_type to ) { 856 if (index == 0) { 857 // The first element in the first segment is embedded into the table (my_head) 858 // so the first pointer should not be stored here 859 // It would be stored during move ctor/assignment operation 860 to[1].store(nullptr, std::memory_order_relaxed); 861 } else { 862 for (size_type i = 0; i != this->segment_size(index); ++i) { 863 to[i].store(nullptr, std::memory_order_relaxed); 864 } 865 } 866 } 867 868 void move_segment( size_type index, segment_type from, segment_type to ) { 869 if (index == 0) { 870 // The first element in the first segment is embedded into the table (my_head) 871 // so the first pointer should not be stored here 872 // It would be stored during move ctor/assignment operation 873 to[1].store(from[1].load(std::memory_order_relaxed), std::memory_order_relaxed); 874 } else { 875 for (size_type i = 0; i != this->segment_size(index); ++i) { 876 to[i].store(from[i].load(std::memory_order_relaxed), std::memory_order_relaxed); 877 from[i].store(nullptr, std::memory_order_relaxed); 878 } 879 } 880 } 881 882 // allocate_long_table is required by the segment_table base class, but unused for unordered containers 883 typename base_type::segment_table_type allocate_long_table( const typename base_type::atomic_segment*, size_type ) { 884 __TBB_ASSERT(false, "This method should never been called"); 885 // TableType is a pointer 886 return nullptr; 887 } 888 889 // destroy_elements is required by the segment_table base class, but unused for unordered containers 890 // this function call but do nothing 891 void destroy_elements() {} 892 }; // struct unordered_segment_table 893 894 void internal_clear() { 895 // TODO: consider usefulness of two versions of clear() - with dummy nodes deallocation and without it 896 node_ptr next = my_head.next(); 897 node_ptr curr = next; 898 899 my_head.set_next(nullptr); 900 901 while (curr != nullptr) { 902 next = curr->next(); 903 destroy_node(curr); 904 curr = next; 905 } 906 907 my_size.store(0, std::memory_order_relaxed); 908 my_segments.clear(); 909 } 910 911 void destroy_node( node_ptr node ) { 912 if (node->is_dummy()) { 913 node_allocator_type dummy_node_allocator(my_segments.get_allocator()); 914 // Destroy the node 915 node_allocator_traits::destroy(dummy_node_allocator, node); 916 // Deallocate the memory 917 node_allocator_traits::deallocate(dummy_node_allocator, node, 1); 918 } else { 919 // GCC 11.1 issues a warning here that incorrect destructor might be called for dummy_nodes 920 #if (__TBB_GCC_VERSION >= 110100 && __TBB_GCC_VERSION < 120000 ) && !__clang__ && !__INTEL_COMPILER 921 volatile 922 #endif 923 value_node_ptr val_node = static_cast<value_node_ptr>(node); 924 value_node_allocator_type value_node_allocator(my_segments.get_allocator()); 925 // Destroy the value 926 value_node_allocator_traits::destroy(value_node_allocator, val_node->storage()); 927 // Destroy the node 928 value_node_allocator_traits::destroy(value_node_allocator, val_node); 929 // Deallocate the memory 930 value_node_allocator_traits::deallocate(value_node_allocator, val_node, 1); 931 } 932 } 933 934 struct internal_insert_return_type { 935 // If the insertion failed - the remaining_node points to the node, which was failed to insert 936 // This node can be allocated in process of insertion 937 value_node_ptr remaining_node; 938 // If the insertion failed - node_with_equal_key points to the node in the list with the 939 // key, equivalent to the inserted, otherwise it points to the node, which was inserted. 940 value_node_ptr node_with_equal_key; 941 // Insertion status 942 // NOTE: if it is true - remaining_node should be nullptr 943 bool inserted; 944 }; // struct internal_insert_return_type 945 946 // Inserts the value into the split ordered list 947 template <typename ValueType> 948 std::pair<iterator, bool> internal_insert_value( ValueType&& value ) { 949 950 auto create_value_node = [&value, this]( sokey_type order_key )->value_node_ptr { 951 return create_node(order_key, std::forward<ValueType>(value)); 952 }; 953 954 auto insert_result = internal_insert(value, create_value_node); 955 956 if (insert_result.remaining_node != nullptr) { 957 // If the insertion fails - destroy the node which was failed to insert if it exist 958 __TBB_ASSERT(!insert_result.inserted, 959 "remaining_node should be nullptr if the node was successfully inserted"); 960 destroy_node(insert_result.remaining_node); 961 } 962 963 return { iterator(insert_result.node_with_equal_key), insert_result.inserted }; 964 } 965 966 // Inserts the node into the split ordered list 967 // Creates a node using the specified callback after the place for insertion was found 968 // Returns internal_insert_return_type object, where: 969 // - If the insertion succeeded: 970 // - remaining_node is nullptr 971 // - node_with_equal_key point to the inserted node 972 // - inserted is true 973 // - If the insertion failed: 974 // - remaining_node points to the node, that was failed to insert if it was created. 975 // nullptr if the node was not created, because the requested key was already 976 // presented in the list 977 // - node_with_equal_key point to the element in the list with the key, equivalent to 978 // to the requested key 979 // - inserted is false 980 template <typename ValueType, typename CreateInsertNode> 981 internal_insert_return_type internal_insert( ValueType&& value, CreateInsertNode create_insert_node ) { 982 static_assert(std::is_same<typename std::decay<ValueType>::type, value_type>::value, 983 "Incorrect type in internal_insert"); 984 const key_type& key = traits_type::get_key(value); 985 sokey_type hash_key = sokey_type(my_hash_compare(key)); 986 987 sokey_type order_key = split_order_key_regular(hash_key); 988 node_ptr prev = prepare_bucket(hash_key); 989 __TBB_ASSERT(prev != nullptr, "Invalid head node"); 990 991 auto search_result = search_after(prev, order_key, key); 992 993 if (search_result.second) { 994 return internal_insert_return_type{ nullptr, search_result.first, false }; 995 } 996 997 value_node_ptr new_node = create_insert_node(order_key); 998 node_ptr curr = search_result.first; 999 1000 while (!try_insert(prev, new_node, curr)) { 1001 search_result = search_after(prev, order_key, key); 1002 if (search_result.second) { 1003 return internal_insert_return_type{ new_node, search_result.first, false }; 1004 } 1005 curr = search_result.first; 1006 } 1007 1008 auto sz = my_size.fetch_add(1); 1009 adjust_table_size(sz + 1, my_bucket_count.load(std::memory_order_acquire)); 1010 return internal_insert_return_type{ nullptr, static_cast<value_node_ptr>(new_node), true }; 1011 } 1012 1013 // Searches the node with the key, equivalent to key with requested order key after the node prev 1014 // Returns the existing node and true if the node is already in the list 1015 // Returns the first node with the order key, greater than requested and false if the node is not presented in the list 1016 std::pair<value_node_ptr, bool> search_after( node_ptr& prev, sokey_type order_key, const key_type& key ) { 1017 // NOTE: static_cast<value_node_ptr>(curr) should be done only after we would ensure 1018 // that the node is not a dummy node 1019 1020 node_ptr curr = prev->next(); 1021 1022 while (curr != nullptr && (curr->order_key() < order_key || 1023 (curr->order_key() == order_key && !my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)))) 1024 { 1025 prev = curr; 1026 curr = curr->next(); 1027 } 1028 1029 if (curr != nullptr && curr->order_key() == order_key && !allow_multimapping) { 1030 return { static_cast<value_node_ptr>(curr), true }; 1031 } 1032 return { static_cast<value_node_ptr>(curr), false }; 1033 } 1034 1035 void adjust_table_size( size_type total_elements, size_type current_size ) { 1036 // Grow the table by a factor of 2 if possible and needed 1037 if ( (float(total_elements) / float(current_size)) > my_max_load_factor ) { 1038 // Double the size of the hash only if size hash not changed in between loads 1039 my_bucket_count.compare_exchange_strong(current_size, 2u * current_size); 1040 } 1041 } 1042 1043 node_ptr insert_dummy_node( node_ptr parent_dummy_node, sokey_type order_key ) { 1044 node_ptr prev_node = parent_dummy_node; 1045 1046 node_ptr dummy_node = create_dummy_node(order_key); 1047 node_ptr next_node; 1048 1049 do { 1050 next_node = prev_node->next(); 1051 // Move forward through the list while the order key is less than requested 1052 while (next_node != nullptr && next_node->order_key() < order_key) { 1053 prev_node = next_node; 1054 next_node = next_node->next(); 1055 } 1056 1057 if (next_node != nullptr && next_node->order_key() == order_key) { 1058 // Another dummy node with the same order key was inserted by another thread 1059 // Destroy the node and exit 1060 destroy_node(dummy_node); 1061 return next_node; 1062 } 1063 } while (!try_insert(prev_node, dummy_node, next_node)); 1064 1065 return dummy_node; 1066 } 1067 1068 // Try to insert a node between prev_node and expected next 1069 // If the next is not equal to expected next - return false 1070 static bool try_insert( node_ptr prev_node, node_ptr new_node, node_ptr current_next_node ) { 1071 new_node->set_next(current_next_node); 1072 return prev_node->try_set_next(current_next_node, new_node); 1073 } 1074 1075 // Returns the bucket, associated with the hash_key 1076 node_ptr prepare_bucket( sokey_type hash_key ) { 1077 size_type bucket = hash_key % my_bucket_count.load(std::memory_order_acquire); 1078 return get_bucket(bucket); 1079 } 1080 1081 // Initialize the corresponding bucket if it is not initialized 1082 node_ptr get_bucket( size_type bucket_index ) { 1083 if (my_segments[bucket_index].load(std::memory_order_acquire) == nullptr) { 1084 init_bucket(bucket_index); 1085 } 1086 return my_segments[bucket_index].load(std::memory_order_acquire); 1087 } 1088 1089 void init_bucket( size_type bucket ) { 1090 if (bucket == 0) { 1091 // Atomicaly store the first bucket into my_head 1092 node_ptr disabled = nullptr; 1093 my_segments[0].compare_exchange_strong(disabled, &my_head); 1094 return; 1095 } 1096 1097 size_type parent_bucket = get_parent(bucket); 1098 1099 while (my_segments[parent_bucket].load(std::memory_order_acquire) == nullptr) { 1100 // Initialize all of the parent buckets 1101 init_bucket(parent_bucket); 1102 } 1103 1104 __TBB_ASSERT(my_segments[parent_bucket].load(std::memory_order_acquire) != nullptr, "Parent bucket should be initialized"); 1105 node_ptr parent = my_segments[parent_bucket].load(std::memory_order_acquire); 1106 1107 // Insert dummy node into the list 1108 node_ptr dummy_node = insert_dummy_node(parent, split_order_key_dummy(bucket)); 1109 // TODO: consider returning pair<node_ptr, bool> to avoid store operation if the bucket was stored by an other thread 1110 // or move store to insert_dummy_node 1111 // Add dummy_node into the segment table 1112 my_segments[bucket].store(dummy_node, std::memory_order_release); 1113 } 1114 1115 node_ptr create_dummy_node( sokey_type order_key ) { 1116 node_allocator_type dummy_node_allocator(my_segments.get_allocator()); 1117 node_ptr dummy_node = node_allocator_traits::allocate(dummy_node_allocator, 1); 1118 node_allocator_traits::construct(dummy_node_allocator, dummy_node, order_key); 1119 return dummy_node; 1120 } 1121 1122 template <typename... Args> 1123 value_node_ptr create_node( sokey_type order_key, Args&&... args ) { 1124 value_node_allocator_type value_node_allocator(my_segments.get_allocator()); 1125 // Allocate memory for the value_node 1126 value_node_ptr new_node = value_node_allocator_traits::allocate(value_node_allocator, 1); 1127 // Construct the node 1128 value_node_allocator_traits::construct(value_node_allocator, new_node, order_key); 1129 1130 // try_call API is not convenient here due to broken 1131 // variadic capture on GCC 4.8.5 1132 auto value_guard = make_raii_guard([&] { 1133 value_node_allocator_traits::destroy(value_node_allocator, new_node); 1134 value_node_allocator_traits::deallocate(value_node_allocator, new_node, 1); 1135 }); 1136 1137 // Construct the value in the node 1138 value_node_allocator_traits::construct(value_node_allocator, new_node->storage(), std::forward<Args>(args)...); 1139 value_guard.dismiss(); 1140 return new_node; 1141 } 1142 1143 value_node_ptr first_value_node( node_ptr first_node ) const { 1144 while (first_node != nullptr && first_node->is_dummy()) { 1145 first_node = first_node->next(); 1146 } 1147 return static_cast<value_node_ptr>(first_node); 1148 } 1149 1150 // Unsafe method, which removes the node from the list and returns the next node 1151 node_ptr internal_erase( value_node_ptr node_to_erase ) { 1152 __TBB_ASSERT(node_to_erase != nullptr, "Invalid iterator for erase"); 1153 node_ptr next_node = node_to_erase->next(); 1154 internal_extract(node_to_erase); 1155 destroy_node(node_to_erase); 1156 return next_node; 1157 } 1158 1159 template <typename K> 1160 size_type internal_erase_by_key( const K& key ) { 1161 // TODO: consider reimplementation without equal_range - it is not effective to perform lookup over a bucket 1162 // for each unsafe_erase call 1163 auto eq_range = equal_range(key); 1164 size_type erased_count = 0; 1165 1166 for (auto it = eq_range.first; it != eq_range.second;) { 1167 it = unsafe_erase(it); 1168 ++erased_count; 1169 } 1170 return erased_count; 1171 } 1172 1173 // Unsafe method, which extracts the node from the list 1174 void internal_extract( value_node_ptr node_to_extract ) { 1175 const key_type& key = traits_type::get_key(node_to_extract->value()); 1176 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1177 1178 node_ptr prev_node = prepare_bucket(hash_key); 1179 1180 for (node_ptr node = prev_node->next(); node != nullptr; prev_node = node, node = node->next()) { 1181 if (node == node_to_extract) { 1182 unlink_node(prev_node, node, node_to_extract->next()); 1183 my_size.store(my_size.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed); 1184 return; 1185 } 1186 __TBB_ASSERT(node->order_key() <= node_to_extract->order_key(), 1187 "node, which is going to be extracted should be presented in the list"); 1188 } 1189 } 1190 1191 protected: 1192 template <typename SourceType> 1193 void internal_merge( SourceType&& source ) { 1194 static_assert(std::is_same<node_type, typename std::decay<SourceType>::type::node_type>::value, 1195 "Incompatible containers cannot be merged"); 1196 1197 for (node_ptr source_prev = &source.my_head; source_prev->next() != nullptr;) { 1198 if (!source_prev->next()->is_dummy()) { 1199 value_node_ptr curr = static_cast<value_node_ptr>(source_prev->next()); 1200 // If the multimapping is allowed, or the key is not presented 1201 // in the *this container - extract the node from the list 1202 if (allow_multimapping || !contains(traits_type::get_key(curr->value()))) { 1203 node_ptr next_node = curr->next(); 1204 source.unlink_node(source_prev, curr, next_node); 1205 1206 // Remember the old order key 1207 sokey_type old_order_key = curr->order_key(); 1208 1209 // Node handle with curr cannot be used directly in insert call, because 1210 // the destructor of node_type will destroy curr 1211 node_type curr_node = node_handle_accessor::construct<node_type>(curr); 1212 1213 // If the insertion fails - return ownership of the node to the source 1214 if (!insert(std::move(curr_node)).second) { 1215 __TBB_ASSERT(!allow_multimapping, "Insertion should succeed for multicontainer"); 1216 __TBB_ASSERT(source_prev->next() == next_node, 1217 "Concurrent operations with the source container in merge are prohibited"); 1218 1219 // Initialize the node with the old order key, because the order key 1220 // can change during the insertion 1221 curr->init(old_order_key); 1222 __TBB_ASSERT(old_order_key >= source_prev->order_key() && 1223 (next_node == nullptr || old_order_key <= next_node->order_key()), 1224 "Wrong nodes order in the source container"); 1225 // Merge is unsafe for source container, so the insertion back can be done without compare_exchange 1226 curr->set_next(next_node); 1227 source_prev->set_next(curr); 1228 source_prev = curr; 1229 node_handle_accessor::deactivate(curr_node); 1230 } else { 1231 source.my_size.fetch_sub(1, std::memory_order_relaxed); 1232 } 1233 } else { 1234 source_prev = curr; 1235 } 1236 } else { 1237 source_prev = source_prev->next(); 1238 } 1239 } 1240 } 1241 1242 private: 1243 // Unsafe method, which unlinks the node between prev and next 1244 void unlink_node( node_ptr prev_node, node_ptr node_to_unlink, node_ptr next_node ) { 1245 __TBB_ASSERT(prev_node->next() == node_to_unlink && 1246 node_to_unlink->next() == next_node, 1247 "erasing and extracting nodes from the containers are unsafe in concurrent mode"); 1248 prev_node->set_next(next_node); 1249 node_to_unlink->set_next(nullptr); 1250 } 1251 1252 template <typename K> 1253 value_node_ptr internal_find( const K& key ) { 1254 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1255 sokey_type order_key = split_order_key_regular(hash_key); 1256 1257 node_ptr curr = prepare_bucket(hash_key); 1258 1259 while (curr != nullptr) { 1260 if (curr->order_key() > order_key) { 1261 // If the order key is greater than the requested order key, 1262 // the element is not in the hash table 1263 return nullptr; 1264 } else if (curr->order_key() == order_key && 1265 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) { 1266 // The fact that order keys match does not mean that the element is found. 1267 // Key function comparison has to be performed to check whether this is the 1268 // right element. If not, keep searching while order key is the same. 1269 return static_cast<value_node_ptr>(curr); 1270 } 1271 curr = curr->next(); 1272 } 1273 1274 return nullptr; 1275 } 1276 1277 template <typename K> 1278 std::pair<value_node_ptr, value_node_ptr> internal_equal_range( const K& key ) { 1279 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1280 sokey_type order_key = split_order_key_regular(hash_key); 1281 1282 node_ptr curr = prepare_bucket(hash_key); 1283 1284 while (curr != nullptr) { 1285 if (curr->order_key() > order_key) { 1286 // If the order key is greater than the requested order key, 1287 // the element is not in the hash table 1288 return std::make_pair(nullptr, nullptr); 1289 } else if (curr->order_key() == order_key && 1290 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) { 1291 value_node_ptr first = static_cast<value_node_ptr>(curr); 1292 node_ptr last = first; 1293 do { 1294 last = last->next(); 1295 } while (allow_multimapping && last != nullptr && !last->is_dummy() && 1296 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(last)->value()), key)); 1297 return std::make_pair(first, first_value_node(last)); 1298 } 1299 curr = curr->next(); 1300 } 1301 return {nullptr, nullptr}; 1302 } 1303 1304 template <typename K> 1305 size_type internal_count( const K& key ) const { 1306 if (allow_multimapping) { 1307 // TODO: consider reimplementing the internal_equal_range with elements counting to avoid std::distance 1308 auto eq_range = equal_range(key); 1309 return std::distance(eq_range.first, eq_range.second); 1310 } else { 1311 return contains(key) ? 1 : 0; 1312 } 1313 } 1314 1315 void internal_copy( const concurrent_unordered_base& other ) { 1316 node_ptr last_node = &my_head; 1317 my_segments[0].store(&my_head, std::memory_order_relaxed); 1318 1319 for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) { 1320 node_ptr new_node; 1321 if (!node->is_dummy()) { 1322 // The node in the right table contains a value 1323 new_node = create_node(node->order_key(), static_cast<value_node_ptr>(node)->value()); 1324 } else { 1325 // The node in the right table is a dummy node 1326 new_node = create_dummy_node(node->order_key()); 1327 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed); 1328 } 1329 1330 last_node->set_next(new_node); 1331 last_node = new_node; 1332 } 1333 } 1334 1335 void internal_move( concurrent_unordered_base&& other ) { 1336 node_ptr last_node = &my_head; 1337 my_segments[0].store(&my_head, std::memory_order_relaxed); 1338 1339 for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) { 1340 node_ptr new_node; 1341 if (!node->is_dummy()) { 1342 // The node in the right table contains a value 1343 new_node = create_node(node->order_key(), std::move(static_cast<value_node_ptr>(node)->value())); 1344 } else { 1345 // TODO: do we need to destroy a dummy node in the right container? 1346 // The node in the right table is a dummy_node 1347 new_node = create_dummy_node(node->order_key()); 1348 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed); 1349 } 1350 1351 last_node->set_next(new_node); 1352 last_node = new_node; 1353 } 1354 } 1355 1356 void move_content( concurrent_unordered_base&& other ) { 1357 // NOTE: allocators should be equal 1358 my_head.set_next(other.my_head.next()); 1359 other.my_head.set_next(nullptr); 1360 my_segments[0].store(&my_head, std::memory_order_relaxed); 1361 1362 other.my_bucket_count.store(initial_bucket_count, std::memory_order_relaxed); 1363 other.my_max_load_factor = initial_max_load_factor; 1364 other.my_size.store(0, std::memory_order_relaxed); 1365 } 1366 1367 void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type&, 1368 /*is_always_equal = */std::true_type ) { 1369 // Allocators are always equal - no need to compare for equality 1370 move_content(std::move(other)); 1371 } 1372 1373 void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type& alloc, 1374 /*is_always_equal = */std::false_type ) { 1375 // Allocators are not always equal 1376 if (alloc == other.my_segments.get_allocator()) { 1377 move_content(std::move(other)); 1378 } else { 1379 try_call( [&] { 1380 internal_move(std::move(other)); 1381 } ).on_exception( [&] { 1382 clear(); 1383 }); 1384 } 1385 } 1386 1387 // Move assigns the hash table to other is any instances of allocator_type are always equal 1388 // or propagate_on_container_move_assignment is true 1389 void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::true_type ) { 1390 move_content(std::move(other)); 1391 } 1392 1393 // Move assigns the hash table to other is any instances of allocator_type are not always equal 1394 // and propagate_on_container_move_assignment is false 1395 void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::false_type ) { 1396 if (my_segments.get_allocator() == other.my_segments.get_allocator()) { 1397 move_content(std::move(other)); 1398 } else { 1399 // TODO: guards for exceptions 1400 internal_move(std::move(other)); 1401 } 1402 } 1403 1404 void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::true_type ) { 1405 internal_swap_fields(other); 1406 } 1407 1408 void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::false_type ) { 1409 __TBB_ASSERT(my_segments.get_allocator() == other.my_segments.get_allocator(), 1410 "Swapping with unequal allocators is not allowed"); 1411 internal_swap_fields(other); 1412 } 1413 1414 void internal_swap_fields( concurrent_unordered_base& other ) { 1415 node_ptr first_node = my_head.next(); 1416 my_head.set_next(other.my_head.next()); 1417 other.my_head.set_next(first_node); 1418 1419 size_type current_size = my_size.load(std::memory_order_relaxed); 1420 my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); 1421 other.my_size.store(current_size, std::memory_order_relaxed); 1422 1423 size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed); 1424 my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed); 1425 other.my_bucket_count.store(bucket_count, std::memory_order_relaxed); 1426 1427 using std::swap; 1428 swap(my_max_load_factor, other.my_max_load_factor); 1429 swap(my_hash_compare, other.my_hash_compare); 1430 my_segments.swap(other.my_segments); 1431 1432 // swap() method from segment table swaps all of the segments including the first segment 1433 // We should restore it to my_head. Without it the first segment of the container will point 1434 // to other.my_head. 1435 my_segments[0].store(&my_head, std::memory_order_relaxed); 1436 other.my_segments[0].store(&other.my_head, std::memory_order_relaxed); 1437 } 1438 1439 // A regular order key has its original hash value reversed and the last bit set 1440 static constexpr sokey_type split_order_key_regular( sokey_type hash ) { 1441 return reverse_bits(hash) | 0x1; 1442 } 1443 1444 // A dummy order key has its original hash value reversed and the last bit unset 1445 static constexpr sokey_type split_order_key_dummy( sokey_type hash ) { 1446 return reverse_bits(hash) & ~sokey_type(0x1); 1447 } 1448 1449 size_type get_parent( size_type bucket ) const { 1450 // Unset bucket's most significant turned-on bit 1451 __TBB_ASSERT(bucket != 0, "Unable to get_parent of the bucket 0"); 1452 size_type msb = tbb::detail::log2(bucket); 1453 return bucket & ~(size_type(1) << msb); 1454 } 1455 1456 size_type get_next_bucket_index( size_type bucket ) const { 1457 size_type bits = tbb::detail::log2(my_bucket_count.load(std::memory_order_relaxed)); 1458 size_type reversed_next = reverse_n_bits(bucket, bits) + 1; 1459 return reverse_n_bits(reversed_next, bits); 1460 } 1461 1462 std::atomic<size_type> my_size; 1463 std::atomic<size_type> my_bucket_count; 1464 float my_max_load_factor; 1465 hash_compare_type my_hash_compare; 1466 1467 list_node_type my_head; // Head node for split ordered list 1468 unordered_segment_table my_segments; // Segment table of pointers to nodes 1469 1470 template <typename Container, typename Value> 1471 friend class solist_iterator; 1472 1473 template <typename OtherTraits> 1474 friend class concurrent_unordered_base; 1475 }; // class concurrent_unordered_base 1476 1477 template <typename Traits> 1478 bool operator==( const concurrent_unordered_base<Traits>& lhs, 1479 const concurrent_unordered_base<Traits>& rhs ) { 1480 if (&lhs == &rhs) { return true; } 1481 if (lhs.size() != rhs.size()) { return false; } 1482 1483 #if _MSC_VER 1484 // Passing "unchecked" iterators to std::permutation with 3 parameters 1485 // causes compiler warnings. 1486 // The workaround is to use overload with 4 parameters, which is 1487 // available since C++14 - minimally supported version on MSVC 1488 return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 1489 #else 1490 return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin()); 1491 #endif 1492 } 1493 1494 #if !__TBB_CPP20_COMPARISONS_PRESENT 1495 template <typename Traits> 1496 bool operator!=( const concurrent_unordered_base<Traits>& lhs, 1497 const concurrent_unordered_base<Traits>& rhs ) { 1498 return !(lhs == rhs); 1499 } 1500 #endif 1501 1502 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) 1503 #pragma warning(pop) // warning 4127 is back 1504 #endif 1505 1506 } // namespace d1 1507 } // namespace detail 1508 } // namespace tbb 1509 1510 #endif // __TBB_detail__concurrent_unordered_base_H 1511