1 /* 2 Copyright (c) 2005-2021 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #ifndef __TBB_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(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 (my_begin_node == my_end_node) { 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 // deallocate_segment is required by the segment_table base class, but 835 // in unordered, it is also necessary to call the destructor during deallocation 836 void deallocate_segment( segment_type address, size_type index ) { 837 destroy_segment(address, index); 838 } 839 840 void destroy_segment( segment_type address, size_type index ) { 841 segment_allocator_type alloc(this->get_allocator()); 842 for (size_type i = 0; i != this->segment_size(index); ++i) { 843 segment_allocator_traits::destroy(alloc, address + i); 844 } 845 segment_allocator_traits::deallocate(alloc, address, this->segment_size(index)); 846 } 847 848 849 void copy_segment( size_type index, segment_type, segment_type to ) { 850 if (index == 0) { 851 // The first element in the first segment is embedded into the table (my_head) 852 // so the first pointer should not be stored here 853 // It would be stored during move ctor/assignment operation 854 to[1].store(nullptr, std::memory_order_relaxed); 855 } else { 856 for (size_type i = 0; i != this->segment_size(index); ++i) { 857 to[i].store(nullptr, std::memory_order_relaxed); 858 } 859 } 860 } 861 862 void move_segment( size_type index, segment_type from, segment_type to ) { 863 if (index == 0) { 864 // The first element in the first segment is embedded into the table (my_head) 865 // so the first pointer should not be stored here 866 // It would be stored during move ctor/assignment operation 867 to[1].store(from[1].load(std::memory_order_relaxed), std::memory_order_relaxed); 868 } else { 869 for (size_type i = 0; i != this->segment_size(index); ++i) { 870 to[i].store(from[i].load(std::memory_order_relaxed), std::memory_order_relaxed); 871 from[i].store(nullptr, std::memory_order_relaxed); 872 } 873 } 874 } 875 876 // allocate_long_table is required by the segment_table base class, but unused for unordered containers 877 typename base_type::segment_table_type allocate_long_table( const typename base_type::atomic_segment*, size_type ) { 878 __TBB_ASSERT(false, "This method should never been called"); 879 // TableType is a pointer 880 return nullptr; 881 } 882 883 // destroy_elements is required by the segment_table base class, but unused for unordered containers 884 // this function call but do nothing 885 void destroy_elements() {} 886 }; // struct unordered_segment_table 887 888 void internal_clear() { 889 // TODO: consider usefulness of two versions of clear() - with dummy nodes deallocation and without it 890 node_ptr next = my_head.next(); 891 node_ptr curr = next; 892 893 my_head.set_next(nullptr); 894 895 while (curr != nullptr) { 896 next = curr->next(); 897 destroy_node(curr); 898 curr = next; 899 } 900 901 my_size.store(0, std::memory_order_relaxed); 902 my_segments.clear(); 903 } 904 905 void destroy_node( node_ptr node ) { 906 if (node->is_dummy()) { 907 node_allocator_type dummy_node_allocator(my_segments.get_allocator()); 908 // Destroy the node 909 node_allocator_traits::destroy(dummy_node_allocator, node); 910 // Deallocate the memory 911 node_allocator_traits::deallocate(dummy_node_allocator, node, 1); 912 } else { 913 // GCC 11.1 issues a warning here that incorrect destructor might be called for dummy_nodes 914 #if (__TBB_GCC_VERSION >= 110100 && __TBB_GCC_VERSION < 120000 ) && !__clang__ && !__INTEL_COMPILER 915 volatile 916 #endif 917 value_node_ptr val_node = static_cast<value_node_ptr>(node); 918 value_node_allocator_type value_node_allocator(my_segments.get_allocator()); 919 // Destroy the value 920 value_node_allocator_traits::destroy(value_node_allocator, val_node->storage()); 921 // Destroy the node 922 value_node_allocator_traits::destroy(value_node_allocator, val_node); 923 // Deallocate the memory 924 value_node_allocator_traits::deallocate(value_node_allocator, val_node, 1); 925 } 926 } 927 928 struct internal_insert_return_type { 929 // If the insertion failed - the remaining_node points to the node, which was failed to insert 930 // This node can be allocated in process of insertion 931 value_node_ptr remaining_node; 932 // If the insertion failed - node_with_equal_key points to the node in the list with the 933 // key, equivalent to the inserted, otherwise it points to the node, which was inserted. 934 value_node_ptr node_with_equal_key; 935 // Insertion status 936 // NOTE: if it is true - remaining_node should be nullptr 937 bool inserted; 938 }; // struct internal_insert_return_type 939 940 // Inserts the value into the split ordered list 941 template <typename ValueType> 942 std::pair<iterator, bool> internal_insert_value( ValueType&& value ) { 943 944 auto create_value_node = [&value, this]( sokey_type order_key )->value_node_ptr { 945 return create_node(order_key, std::forward<ValueType>(value)); 946 }; 947 948 auto insert_result = internal_insert(value, create_value_node); 949 950 if (insert_result.remaining_node != nullptr) { 951 // If the insertion fails - destroy the node which was failed to insert if it exist 952 __TBB_ASSERT(!insert_result.inserted, 953 "remaining_node should be nullptr if the node was successfully inserted"); 954 destroy_node(insert_result.remaining_node); 955 } 956 957 return { iterator(insert_result.node_with_equal_key), insert_result.inserted }; 958 } 959 960 // Inserts the node into the split ordered list 961 // Creates a node using the specified callback after the place for insertion was found 962 // Returns internal_insert_return_type object, where: 963 // - If the insertion succeeded: 964 // - remaining_node is nullptr 965 // - node_with_equal_key point to the inserted node 966 // - inserted is true 967 // - If the insertion failed: 968 // - remaining_node points to the node, that was failed to insert if it was created. 969 // nullptr if the node was not created, because the requested key was already 970 // presented in the list 971 // - node_with_equal_key point to the element in the list with the key, equivalent to 972 // to the requested key 973 // - inserted is false 974 template <typename ValueType, typename CreateInsertNode> 975 internal_insert_return_type internal_insert( ValueType&& value, CreateInsertNode create_insert_node ) { 976 static_assert(std::is_same<typename std::decay<ValueType>::type, value_type>::value, 977 "Incorrect type in internal_insert"); 978 const key_type& key = traits_type::get_key(value); 979 sokey_type hash_key = sokey_type(my_hash_compare(key)); 980 981 sokey_type order_key = split_order_key_regular(hash_key); 982 node_ptr prev = prepare_bucket(hash_key); 983 __TBB_ASSERT(prev != nullptr, "Invalid head node"); 984 985 auto search_result = search_after(prev, order_key, key); 986 987 if (search_result.second) { 988 return internal_insert_return_type{ nullptr, search_result.first, false }; 989 } 990 991 value_node_ptr new_node = create_insert_node(order_key); 992 node_ptr curr = search_result.first; 993 994 while (!try_insert(prev, new_node, curr)) { 995 search_result = search_after(prev, order_key, key); 996 if (search_result.second) { 997 return internal_insert_return_type{ new_node, search_result.first, false }; 998 } 999 curr = search_result.first; 1000 } 1001 1002 auto sz = my_size.fetch_add(1); 1003 adjust_table_size(sz + 1, my_bucket_count.load(std::memory_order_acquire)); 1004 return internal_insert_return_type{ nullptr, static_cast<value_node_ptr>(new_node), true }; 1005 } 1006 1007 // Searches the node with the key, equivalent to key with requested order key after the node prev 1008 // Returns the existing node and true if the node is already in the list 1009 // Returns the first node with the order key, greater than requested and false if the node is not presented in the list 1010 std::pair<value_node_ptr, bool> search_after( node_ptr& prev, sokey_type order_key, const key_type& key ) { 1011 // NOTE: static_cast<value_node_ptr>(curr) should be done only after we would ensure 1012 // that the node is not a dummy node 1013 1014 node_ptr curr = prev->next(); 1015 1016 while (curr != nullptr && (curr->order_key() < order_key || 1017 (curr->order_key() == order_key && !my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)))) 1018 { 1019 prev = curr; 1020 curr = curr->next(); 1021 } 1022 1023 if (curr != nullptr && curr->order_key() == order_key && !allow_multimapping) { 1024 return { static_cast<value_node_ptr>(curr), true }; 1025 } 1026 return { static_cast<value_node_ptr>(curr), false }; 1027 } 1028 1029 void adjust_table_size( size_type total_elements, size_type current_size ) { 1030 // Grow the table by a factor of 2 if possible and needed 1031 if ( (float(total_elements) / float(current_size)) > my_max_load_factor ) { 1032 // Double the size of the hash only if size hash not changed in between loads 1033 my_bucket_count.compare_exchange_strong(current_size, 2u * current_size); 1034 } 1035 } 1036 1037 node_ptr insert_dummy_node( node_ptr parent_dummy_node, sokey_type order_key ) { 1038 node_ptr prev_node = parent_dummy_node; 1039 1040 node_ptr dummy_node = create_dummy_node(order_key); 1041 node_ptr next_node; 1042 1043 do { 1044 next_node = prev_node->next(); 1045 // Move forward through the list while the order key is less than requested 1046 while (next_node != nullptr && next_node->order_key() < order_key) { 1047 prev_node = next_node; 1048 next_node = next_node->next(); 1049 } 1050 1051 if (next_node != nullptr && next_node->order_key() == order_key) { 1052 // Another dummy node with the same order key was inserted by another thread 1053 // Destroy the node and exit 1054 destroy_node(dummy_node); 1055 return next_node; 1056 } 1057 } while (!try_insert(prev_node, dummy_node, next_node)); 1058 1059 return dummy_node; 1060 } 1061 1062 // Try to insert a node between prev_node and expected next 1063 // If the next is not equal to expected next - return false 1064 static bool try_insert( node_ptr prev_node, node_ptr new_node, node_ptr current_next_node ) { 1065 new_node->set_next(current_next_node); 1066 return prev_node->try_set_next(current_next_node, new_node); 1067 } 1068 1069 // Returns the bucket, associated with the hash_key 1070 node_ptr prepare_bucket( sokey_type hash_key ) { 1071 size_type bucket = hash_key % my_bucket_count.load(std::memory_order_acquire); 1072 return get_bucket(bucket); 1073 } 1074 1075 // Initialize the corresponding bucket if it is not initialized 1076 node_ptr get_bucket( size_type bucket_index ) { 1077 if (my_segments[bucket_index].load(std::memory_order_acquire) == nullptr) { 1078 init_bucket(bucket_index); 1079 } 1080 return my_segments[bucket_index].load(std::memory_order_acquire); 1081 } 1082 1083 void init_bucket( size_type bucket ) { 1084 if (bucket == 0) { 1085 // Atomicaly store the first bucket into my_head 1086 node_ptr disabled = nullptr; 1087 my_segments[0].compare_exchange_strong(disabled, &my_head); 1088 return; 1089 } 1090 1091 size_type parent_bucket = get_parent(bucket); 1092 1093 while (my_segments[parent_bucket].load(std::memory_order_acquire) == nullptr) { 1094 // Initialize all of the parent buckets 1095 init_bucket(parent_bucket); 1096 } 1097 1098 __TBB_ASSERT(my_segments[parent_bucket].load(std::memory_order_acquire) != nullptr, "Parent bucket should be initialized"); 1099 node_ptr parent = my_segments[parent_bucket].load(std::memory_order_acquire); 1100 1101 // Insert dummy node into the list 1102 node_ptr dummy_node = insert_dummy_node(parent, split_order_key_dummy(bucket)); 1103 // TODO: consider returning pair<node_ptr, bool> to avoid store operation if the bucket was stored by an other thread 1104 // or move store to insert_dummy_node 1105 // Add dummy_node into the segment table 1106 my_segments[bucket].store(dummy_node, std::memory_order_release); 1107 } 1108 1109 node_ptr create_dummy_node( sokey_type order_key ) { 1110 node_allocator_type dummy_node_allocator(my_segments.get_allocator()); 1111 node_ptr dummy_node = node_allocator_traits::allocate(dummy_node_allocator, 1); 1112 node_allocator_traits::construct(dummy_node_allocator, dummy_node, order_key); 1113 return dummy_node; 1114 } 1115 1116 template <typename... Args> 1117 value_node_ptr create_node( sokey_type order_key, Args&&... args ) { 1118 value_node_allocator_type value_node_allocator(my_segments.get_allocator()); 1119 // Allocate memory for the value_node 1120 value_node_ptr new_node = value_node_allocator_traits::allocate(value_node_allocator, 1); 1121 // Construct the node 1122 value_node_allocator_traits::construct(value_node_allocator, new_node, order_key); 1123 1124 // try_call API is not convenient here due to broken 1125 // variadic capture on GCC 4.8.5 1126 auto value_guard = make_raii_guard([&] { 1127 value_node_allocator_traits::destroy(value_node_allocator, new_node); 1128 value_node_allocator_traits::deallocate(value_node_allocator, new_node, 1); 1129 }); 1130 1131 // Construct the value in the node 1132 value_node_allocator_traits::construct(value_node_allocator, new_node->storage(), std::forward<Args>(args)...); 1133 value_guard.dismiss(); 1134 return new_node; 1135 } 1136 1137 value_node_ptr first_value_node( node_ptr first_node ) const { 1138 while (first_node != nullptr && first_node->is_dummy()) { 1139 first_node = first_node->next(); 1140 } 1141 return static_cast<value_node_ptr>(first_node); 1142 } 1143 1144 // Unsafe method, which removes the node from the list and returns the next node 1145 node_ptr internal_erase( value_node_ptr node_to_erase ) { 1146 __TBB_ASSERT(node_to_erase != nullptr, "Invalid iterator for erase"); 1147 node_ptr next_node = node_to_erase->next(); 1148 internal_extract(node_to_erase); 1149 destroy_node(node_to_erase); 1150 return next_node; 1151 } 1152 1153 template <typename K> 1154 size_type internal_erase_by_key( const K& key ) { 1155 // TODO: consider reimplementation without equal_range - it is not effective to perform lookup over a bucket 1156 // for each unsafe_erase call 1157 auto eq_range = equal_range(key); 1158 size_type erased_count = 0; 1159 1160 for (auto it = eq_range.first; it != eq_range.second;) { 1161 it = unsafe_erase(it); 1162 ++erased_count; 1163 } 1164 return erased_count; 1165 } 1166 1167 // Unsafe method, which extracts the node from the list 1168 void internal_extract( value_node_ptr node_to_extract ) { 1169 const key_type& key = traits_type::get_key(node_to_extract->value()); 1170 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1171 1172 node_ptr prev_node = prepare_bucket(hash_key); 1173 1174 for (node_ptr node = prev_node->next(); node != nullptr; prev_node = node, node = node->next()) { 1175 if (node == node_to_extract) { 1176 unlink_node(prev_node, node, node_to_extract->next()); 1177 my_size.store(my_size.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed); 1178 return; 1179 } 1180 __TBB_ASSERT(node->order_key() <= node_to_extract->order_key(), 1181 "node, which is going to be extracted should be presented in the list"); 1182 } 1183 } 1184 1185 protected: 1186 template <typename SourceType> 1187 void internal_merge( SourceType&& source ) { 1188 static_assert(std::is_same<node_type, typename std::decay<SourceType>::type::node_type>::value, 1189 "Incompatible containers cannot be merged"); 1190 1191 for (node_ptr source_prev = &source.my_head; source_prev->next() != nullptr;) { 1192 if (!source_prev->next()->is_dummy()) { 1193 value_node_ptr curr = static_cast<value_node_ptr>(source_prev->next()); 1194 // If the multimapping is allowed, or the key is not presented 1195 // in the *this container - extract the node from the list 1196 if (allow_multimapping || !contains(traits_type::get_key(curr->value()))) { 1197 node_ptr next_node = curr->next(); 1198 source.unlink_node(source_prev, curr, next_node); 1199 1200 // Remember the old order key 1201 sokey_type old_order_key = curr->order_key(); 1202 1203 // Node handle with curr cannot be used directly in insert call, because 1204 // the destructor of node_type will destroy curr 1205 node_type curr_node = node_handle_accessor::construct<node_type>(curr); 1206 1207 // If the insertion fails - return ownership of the node to the source 1208 if (!insert(std::move(curr_node)).second) { 1209 __TBB_ASSERT(!allow_multimapping, "Insertion should succeed for multicontainer"); 1210 __TBB_ASSERT(source_prev->next() == next_node, 1211 "Concurrent operations with the source container in merge are prohibited"); 1212 1213 // Initialize the node with the old order key, because the order key 1214 // can change during the insertion 1215 curr->init(old_order_key); 1216 __TBB_ASSERT(old_order_key >= source_prev->order_key() && 1217 (next_node == nullptr || old_order_key <= next_node->order_key()), 1218 "Wrong nodes order in the source container"); 1219 // Merge is unsafe for source container, so the insertion back can be done without compare_exchange 1220 curr->set_next(next_node); 1221 source_prev->set_next(curr); 1222 source_prev = curr; 1223 node_handle_accessor::deactivate(curr_node); 1224 } else { 1225 source.my_size.fetch_sub(1, std::memory_order_relaxed); 1226 } 1227 } else { 1228 source_prev = curr; 1229 } 1230 } else { 1231 source_prev = source_prev->next(); 1232 } 1233 } 1234 } 1235 1236 private: 1237 // Unsafe method, which unlinks the node between prev and next 1238 void unlink_node( node_ptr prev_node, node_ptr node_to_unlink, node_ptr next_node ) { 1239 __TBB_ASSERT(prev_node->next() == node_to_unlink && 1240 node_to_unlink->next() == next_node, 1241 "erasing and extracting nodes from the containers are unsafe in concurrent mode"); 1242 prev_node->set_next(next_node); 1243 node_to_unlink->set_next(nullptr); 1244 } 1245 1246 template <typename K> 1247 value_node_ptr internal_find( const K& key ) { 1248 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1249 sokey_type order_key = split_order_key_regular(hash_key); 1250 1251 node_ptr curr = prepare_bucket(hash_key); 1252 1253 while (curr != nullptr) { 1254 if (curr->order_key() > order_key) { 1255 // If the order key is greater than the requested order key, 1256 // the element is not in the hash table 1257 return nullptr; 1258 } else if (curr->order_key() == order_key && 1259 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) { 1260 // The fact that order keys match does not mean that the element is found. 1261 // Key function comparison has to be performed to check whether this is the 1262 // right element. If not, keep searching while order key is the same. 1263 return static_cast<value_node_ptr>(curr); 1264 } 1265 curr = curr->next(); 1266 } 1267 1268 return nullptr; 1269 } 1270 1271 template <typename K> 1272 std::pair<value_node_ptr, value_node_ptr> internal_equal_range( const K& key ) { 1273 sokey_type hash_key = sokey_type(my_hash_compare(key)); 1274 sokey_type order_key = split_order_key_regular(hash_key); 1275 1276 node_ptr curr = prepare_bucket(hash_key); 1277 1278 while (curr != nullptr) { 1279 if (curr->order_key() > order_key) { 1280 // If the order key is greater than the requested order key, 1281 // the element is not in the hash table 1282 return std::make_pair(nullptr, nullptr); 1283 } else if (curr->order_key() == order_key && 1284 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(curr)->value()), key)) { 1285 value_node_ptr first = static_cast<value_node_ptr>(curr); 1286 node_ptr last = first; 1287 do { 1288 last = last->next(); 1289 } while (allow_multimapping && last != nullptr && !last->is_dummy() && 1290 my_hash_compare(traits_type::get_key(static_cast<value_node_ptr>(last)->value()), key)); 1291 return std::make_pair(first, first_value_node(last)); 1292 } 1293 curr = curr->next(); 1294 } 1295 return {nullptr, nullptr}; 1296 } 1297 1298 template <typename K> 1299 size_type internal_count( const K& key ) const { 1300 if (allow_multimapping) { 1301 // TODO: consider reimplementing the internal_equal_range with elements counting to avoid std::distance 1302 auto eq_range = equal_range(key); 1303 return std::distance(eq_range.first, eq_range.second); 1304 } else { 1305 return contains(key) ? 1 : 0; 1306 } 1307 } 1308 1309 void internal_copy( const concurrent_unordered_base& other ) { 1310 node_ptr last_node = &my_head; 1311 my_segments[0].store(&my_head, std::memory_order_relaxed); 1312 1313 for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) { 1314 node_ptr new_node; 1315 if (!node->is_dummy()) { 1316 // The node in the right table contains a value 1317 new_node = create_node(node->order_key(), static_cast<value_node_ptr>(node)->value()); 1318 } else { 1319 // The node in the right table is a dummy node 1320 new_node = create_dummy_node(node->order_key()); 1321 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed); 1322 } 1323 1324 last_node->set_next(new_node); 1325 last_node = new_node; 1326 } 1327 } 1328 1329 void internal_move( concurrent_unordered_base&& other ) { 1330 node_ptr last_node = &my_head; 1331 my_segments[0].store(&my_head, std::memory_order_relaxed); 1332 1333 for (node_ptr node = other.my_head.next(); node != nullptr; node = node->next()) { 1334 node_ptr new_node; 1335 if (!node->is_dummy()) { 1336 // The node in the right table contains a value 1337 new_node = create_node(node->order_key(), std::move(static_cast<value_node_ptr>(node)->value())); 1338 } else { 1339 // TODO: do we need to destroy a dummy node in the right container? 1340 // The node in the right table is a dummy_node 1341 new_node = create_dummy_node(node->order_key()); 1342 my_segments[reverse_bits(node->order_key())].store(new_node, std::memory_order_relaxed); 1343 } 1344 1345 last_node->set_next(new_node); 1346 last_node = new_node; 1347 } 1348 } 1349 1350 void move_content( concurrent_unordered_base&& other ) { 1351 // NOTE: allocators should be equal 1352 my_head.set_next(other.my_head.next()); 1353 other.my_head.set_next(nullptr); 1354 my_segments[0].store(&my_head, std::memory_order_relaxed); 1355 1356 other.my_bucket_count.store(initial_bucket_count, std::memory_order_relaxed); 1357 other.my_max_load_factor = initial_max_load_factor; 1358 other.my_size.store(0, std::memory_order_relaxed); 1359 } 1360 1361 void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type&, 1362 /*is_always_equal = */std::true_type ) { 1363 // Allocators are always equal - no need to compare for equality 1364 move_content(std::move(other)); 1365 } 1366 1367 void internal_move_construct_with_allocator( concurrent_unordered_base&& other, const allocator_type& alloc, 1368 /*is_always_equal = */std::false_type ) { 1369 // Allocators are not always equal 1370 if (alloc == other.my_segments.get_allocator()) { 1371 move_content(std::move(other)); 1372 } else { 1373 try_call( [&] { 1374 internal_move(std::move(other)); 1375 } ).on_exception( [&] { 1376 clear(); 1377 }); 1378 } 1379 } 1380 1381 // Move assigns the hash table to other is any instances of allocator_type are always equal 1382 // or propagate_on_container_move_assignment is true 1383 void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::true_type ) { 1384 move_content(std::move(other)); 1385 } 1386 1387 // Move assigns the hash table to other is any instances of allocator_type are not always equal 1388 // and propagate_on_container_move_assignment is false 1389 void internal_move_assign( concurrent_unordered_base&& other, /*is_always_equal || POCMA = */std::false_type ) { 1390 if (my_segments.get_allocator() == other.my_segments.get_allocator()) { 1391 move_content(std::move(other)); 1392 } else { 1393 // TODO: guards for exceptions 1394 internal_move(std::move(other)); 1395 } 1396 } 1397 1398 void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::true_type ) { 1399 internal_swap_fields(other); 1400 } 1401 1402 void internal_swap( concurrent_unordered_base& other, /*is_always_equal || POCS = */std::false_type ) { 1403 __TBB_ASSERT(my_segments.get_allocator() == other.my_segments.get_allocator(), 1404 "Swapping with unequal allocators is not allowed"); 1405 internal_swap_fields(other); 1406 } 1407 1408 void internal_swap_fields( concurrent_unordered_base& other ) { 1409 node_ptr first_node = my_head.next(); 1410 my_head.set_next(other.my_head.next()); 1411 other.my_head.set_next(first_node); 1412 1413 size_type current_size = my_size.load(std::memory_order_relaxed); 1414 my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); 1415 other.my_size.store(current_size, std::memory_order_relaxed); 1416 1417 size_type bucket_count = my_bucket_count.load(std::memory_order_relaxed); 1418 my_bucket_count.store(other.my_bucket_count.load(std::memory_order_relaxed), std::memory_order_relaxed); 1419 other.my_bucket_count.store(bucket_count, std::memory_order_relaxed); 1420 1421 using std::swap; 1422 swap(my_max_load_factor, other.my_max_load_factor); 1423 swap(my_hash_compare, other.my_hash_compare); 1424 my_segments.swap(other.my_segments); 1425 1426 // swap() method from segment table swaps all of the segments including the first segment 1427 // We should restore it to my_head. Without it the first segment of the container will point 1428 // to other.my_head. 1429 my_segments[0].store(&my_head, std::memory_order_relaxed); 1430 other.my_segments[0].store(&other.my_head, std::memory_order_relaxed); 1431 } 1432 1433 // A regular order key has its original hash value reversed and the last bit set 1434 static constexpr sokey_type split_order_key_regular( sokey_type hash ) { 1435 return reverse_bits(hash) | 0x1; 1436 } 1437 1438 // A dummy order key has its original hash value reversed and the last bit unset 1439 static constexpr sokey_type split_order_key_dummy( sokey_type hash ) { 1440 return reverse_bits(hash) & ~sokey_type(0x1); 1441 } 1442 1443 size_type get_parent( size_type bucket ) const { 1444 // Unset bucket's most significant turned-on bit 1445 __TBB_ASSERT(bucket != 0, "Unable to get_parent of the bucket 0"); 1446 size_type msb = tbb::detail::log2(bucket); 1447 return bucket & ~(size_type(1) << msb); 1448 } 1449 1450 size_type get_next_bucket_index( size_type bucket ) const { 1451 size_type bits = tbb::detail::log2(my_bucket_count.load(std::memory_order_relaxed)); 1452 size_type reversed_next = reverse_n_bits(bucket, bits) + 1; 1453 return reverse_n_bits(reversed_next, bits); 1454 } 1455 1456 std::atomic<size_type> my_size; 1457 std::atomic<size_type> my_bucket_count; 1458 float my_max_load_factor; 1459 hash_compare_type my_hash_compare; 1460 1461 list_node_type my_head; // Head node for split ordered list 1462 unordered_segment_table my_segments; // Segment table of pointers to nodes 1463 1464 template <typename Container, typename Value> 1465 friend class solist_iterator; 1466 1467 template <typename OtherTraits> 1468 friend class concurrent_unordered_base; 1469 }; // class concurrent_unordered_base 1470 1471 template <typename Traits> 1472 bool operator==( const concurrent_unordered_base<Traits>& lhs, 1473 const concurrent_unordered_base<Traits>& rhs ) { 1474 if (&lhs == &rhs) { return true; } 1475 if (lhs.size() != rhs.size()) { return false; } 1476 1477 #if _MSC_VER 1478 // Passing "unchecked" iterators to std::permutation with 3 parameters 1479 // causes compiler warnings. 1480 // The workaround is to use overload with 4 parameters, which is 1481 // available since C++14 - minimally supported version on MSVC 1482 return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 1483 #else 1484 return std::is_permutation(lhs.begin(), lhs.end(), rhs.begin()); 1485 #endif 1486 } 1487 1488 #if !__TBB_CPP20_COMPARISONS_PRESENT 1489 template <typename Traits> 1490 bool operator!=( const concurrent_unordered_base<Traits>& lhs, 1491 const concurrent_unordered_base<Traits>& rhs ) { 1492 return !(lhs == rhs); 1493 } 1494 #endif 1495 1496 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) 1497 #pragma warning(pop) // warning 4127 is back 1498 #endif 1499 1500 } // namespace d1 1501 } // namespace detail 1502 } // namespace tbb 1503 1504 #endif // __TBB_detail__concurrent_unordered_base_H 1505