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_concurrent_hash_map_H 18 #define __TBB_concurrent_hash_map_H 19 20 #include "detail/_namespace_injection.h" 21 #include "detail/_utils.h" 22 #include "detail/_assert.h" 23 #include "detail/_allocator_traits.h" 24 #include "detail/_containers_helpers.h" 25 #include "detail/_template_helpers.h" 26 #include "detail/_hash_compare.h" 27 #include "detail/_range_common.h" 28 #include "tbb_allocator.h" 29 #include "spin_rw_mutex.h" 30 31 #include <atomic> 32 #include <initializer_list> 33 #include <tuple> 34 #include <iterator> 35 #include <utility> // Need std::pair 36 #include <cstring> // Need std::memset 37 38 namespace tbb { 39 namespace detail { 40 namespace d2 { 41 42 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS && __TBB_CPP20_CONCEPTS_PRESENT 43 template <typename Mutex> 44 concept ch_map_rw_scoped_lockable = rw_scoped_lockable<Mutex> && 45 requires(const typename Mutex::scoped_lock& sl) { 46 { sl.is_writer() } -> std::convertible_to<bool>; 47 }; 48 #endif 49 50 template <typename MutexType> 51 struct hash_map_node_base : no_copy { 52 using mutex_type = MutexType; 53 // Scoped lock type for mutex 54 using scoped_type = typename MutexType::scoped_lock; 55 // Next node in chain 56 hash_map_node_base* next; 57 mutex_type mutex; 58 }; 59 60 // Incompleteness flag value 61 static void* const rehash_req_flag = reinterpret_cast<void*>(std::size_t(3)); 62 // Rehashed empty bucket flag 63 static void* const empty_rehashed_flag = reinterpret_cast<void*>(std::size_t(0)); 64 65 template <typename MutexType> 66 bool rehash_required( hash_map_node_base<MutexType>* node_ptr ) { 67 return reinterpret_cast<void*>(node_ptr) == rehash_req_flag; 68 } 69 70 #if TBB_USE_ASSERT 71 template <typename MutexType> 72 bool empty_rehashed( hash_map_node_base<MutexType>* node_ptr ) { 73 return reinterpret_cast<void*>(node_ptr) == empty_rehashed_flag; 74 } 75 #endif 76 77 // base class of concurrent_hash_map 78 79 template <typename Allocator, typename MutexType> 80 class hash_map_base { 81 public: 82 using size_type = std::size_t; 83 using hashcode_type = std::size_t; 84 using segment_index_type = std::size_t; 85 using node_base = hash_map_node_base<MutexType>; 86 87 struct bucket : no_copy { 88 using mutex_type = MutexType; 89 using scoped_type = typename mutex_type::scoped_lock; 90 91 bucket() : node_list(nullptr) {} 92 bucket( node_base* ptr ) : node_list(ptr) {} 93 94 mutex_type mutex; 95 std::atomic<node_base*> node_list; 96 }; 97 98 using allocator_type = Allocator; 99 using allocator_traits_type = tbb::detail::allocator_traits<allocator_type>; 100 using bucket_allocator_type = typename allocator_traits_type::template rebind_alloc<bucket>; 101 using bucket_allocator_traits = tbb::detail::allocator_traits<bucket_allocator_type>; 102 103 // Count of segments in the first block 104 static constexpr size_type embedded_block = 1; 105 // Count of segments in the first block 106 static constexpr size_type embedded_buckets = 1 << embedded_block; 107 // Count of segments in the first block 108 static constexpr size_type first_block = 8; //including embedded_block. perfect with bucket size 16, so the allocations are power of 4096 109 // Size of a pointer / table size 110 static constexpr size_type pointers_per_table = sizeof(segment_index_type) * 8; // one segment per bit 111 112 using segment_ptr_type = bucket*; 113 using atomic_segment_type = std::atomic<segment_ptr_type>; 114 using segments_table_type = atomic_segment_type[pointers_per_table]; 115 116 hash_map_base( const allocator_type& alloc ) : my_allocator(alloc), my_mask(embedded_buckets - 1), my_size(0) { 117 for (size_type i = 0; i != embedded_buckets; ++i) { 118 my_embedded_segment[i].node_list.store(nullptr, std::memory_order_relaxed); 119 } 120 121 for (size_type segment_index = 0; segment_index < pointers_per_table; ++segment_index) { 122 auto argument = segment_index < embedded_block ? my_embedded_segment + segment_base(segment_index) : nullptr; 123 my_table[segment_index].store(argument, std::memory_order_relaxed); 124 } 125 126 __TBB_ASSERT( embedded_block <= first_block, "The first block number must include embedded blocks"); 127 } 128 129 // segment index of given index in the array 130 static segment_index_type segment_index_of( size_type index ) { 131 return segment_index_type(tbb::detail::log2( index|1 )); 132 } 133 134 // the first array index of given segment 135 static segment_index_type segment_base( segment_index_type k ) { 136 return (segment_index_type(1) << k & ~segment_index_type(1)); 137 } 138 139 // segment size except for k == 0 140 static size_type segment_size( segment_index_type k ) { 141 return size_type(1) << k; // fake value for k==0 142 } 143 144 // true if ptr is valid pointer 145 static bool is_valid( void* ptr ) { 146 return reinterpret_cast<uintptr_t>(ptr) > uintptr_t(63); 147 } 148 149 template <typename... Args> 150 void init_buckets_impl( segment_ptr_type ptr, size_type sz, const Args&... args ) { 151 for (size_type i = 0; i < sz; ++i) { 152 bucket_allocator_traits::construct(my_allocator, ptr + i, args...); 153 } 154 } 155 156 // Initialize buckets 157 void init_buckets( segment_ptr_type ptr, size_type sz, bool is_initial ) { 158 if (is_initial) { 159 init_buckets_impl(ptr, sz); 160 } else { 161 init_buckets_impl(ptr, sz, reinterpret_cast<node_base*>(rehash_req_flag)); 162 } 163 } 164 165 // Add node n to bucket b 166 static void add_to_bucket( bucket* b, node_base* n ) { 167 __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), nullptr); 168 n->next = b->node_list.load(std::memory_order_relaxed); 169 b->node_list.store(n, std::memory_order_relaxed); // its under lock and flag is set 170 } 171 172 const bucket_allocator_type& get_allocator() const { 173 return my_allocator; 174 } 175 176 bucket_allocator_type& get_allocator() { 177 return my_allocator; 178 } 179 180 // Enable segment 181 void enable_segment( segment_index_type k, bool is_initial = false ) { 182 __TBB_ASSERT( k, "Zero segment must be embedded" ); 183 size_type sz; 184 __TBB_ASSERT( !is_valid(my_table[k].load(std::memory_order_relaxed)), "Wrong concurrent assignment"); 185 if (k >= first_block) { 186 sz = segment_size(k); 187 segment_ptr_type ptr = nullptr; 188 try_call( [&] { 189 ptr = bucket_allocator_traits::allocate(my_allocator, sz); 190 } ).on_exception( [&] { 191 my_table[k].store(nullptr, std::memory_order_relaxed); 192 }); 193 194 __TBB_ASSERT(ptr, nullptr); 195 init_buckets(ptr, sz, is_initial); 196 my_table[k].store(ptr, std::memory_order_release); 197 sz <<= 1;// double it to get entire capacity of the container 198 } else { // the first block 199 __TBB_ASSERT( k == embedded_block, "Wrong segment index" ); 200 sz = segment_size(first_block); 201 segment_ptr_type ptr = nullptr; 202 try_call( [&] { 203 ptr = bucket_allocator_traits::allocate(my_allocator, sz - embedded_buckets); 204 } ).on_exception( [&] { 205 my_table[k].store(nullptr, std::memory_order_relaxed); 206 }); 207 208 __TBB_ASSERT(ptr, nullptr); 209 init_buckets(ptr, sz - embedded_buckets, is_initial); 210 ptr -= segment_base(embedded_block); 211 for(segment_index_type i = embedded_block; i < first_block; i++) // calc the offsets 212 my_table[i].store(ptr + segment_base(i), std::memory_order_release); 213 } 214 my_mask.store(sz-1, std::memory_order_release); 215 } 216 217 void delete_segment( segment_index_type s ) { 218 segment_ptr_type buckets_ptr = my_table[s].load(std::memory_order_relaxed); 219 size_type sz = segment_size( s ? s : 1 ); 220 221 size_type deallocate_size = 0; 222 223 if (s >= first_block) { // the first segment or the next 224 deallocate_size = sz; 225 } else if (s == embedded_block && embedded_block != first_block) { 226 deallocate_size = segment_size(first_block) - embedded_buckets; 227 } 228 229 for (size_type i = 0; i < deallocate_size; ++i) { 230 bucket_allocator_traits::destroy(my_allocator, buckets_ptr + i); 231 } 232 if (deallocate_size != 0) { 233 bucket_allocator_traits::deallocate(my_allocator, buckets_ptr, deallocate_size); 234 } 235 236 if (s >= embedded_block) my_table[s].store(nullptr, std::memory_order_relaxed); 237 } 238 239 // Get bucket by (masked) hashcode 240 bucket *get_bucket( hashcode_type h ) const noexcept { 241 segment_index_type s = segment_index_of( h ); 242 h -= segment_base(s); 243 segment_ptr_type seg = my_table[s].load(std::memory_order_acquire); 244 __TBB_ASSERT( is_valid(seg), "hashcode must be cut by valid mask for allocated segments" ); 245 return &seg[h]; 246 } 247 248 // detail serial rehashing helper 249 void mark_rehashed_levels( hashcode_type h ) noexcept { 250 segment_index_type s = segment_index_of( h ); 251 while (segment_ptr_type seg = my_table[++s].load(std::memory_order_relaxed)) 252 if (rehash_required(seg[h].node_list.load(std::memory_order_relaxed))) { 253 seg[h].node_list.store(reinterpret_cast<node_base*>(empty_rehashed_flag), std::memory_order_relaxed); 254 mark_rehashed_levels( h + ((hashcode_type)1<<s) ); // optimized segment_base(s) 255 } 256 } 257 258 // Check for mask race 259 // Splitting into two functions should help inlining 260 inline bool check_mask_race( const hashcode_type h, hashcode_type &m ) const { 261 hashcode_type m_now, m_old = m; 262 m_now = my_mask.load(std::memory_order_acquire); 263 if (m_old != m_now) { 264 return check_rehashing_collision(h, m_old, m = m_now); 265 } 266 return false; 267 } 268 269 // Process mask race, check for rehashing collision 270 bool check_rehashing_collision( const hashcode_type h, hashcode_type m_old, hashcode_type m ) const { 271 __TBB_ASSERT(m_old != m, nullptr); // TODO?: m arg could be optimized out by passing h = h&m 272 if( (h & m_old) != (h & m) ) { // mask changed for this hashcode, rare event 273 // condition above proves that 'h' has some other bits set beside 'm_old' 274 // find next applicable mask after m_old //TODO: look at bsl instruction 275 for( ++m_old; !(h & m_old); m_old <<= 1 ) // at maximum few rounds depending on the first block size 276 ; 277 m_old = (m_old<<1) - 1; // get full mask from a bit 278 __TBB_ASSERT((m_old&(m_old+1))==0 && m_old <= m, nullptr); 279 // check whether it is rehashing/ed 280 if (!rehash_required(get_bucket(h & m_old)->node_list.load(std::memory_order_acquire))) { 281 return true; 282 } 283 } 284 return false; 285 } 286 287 // Insert a node and check for load factor. @return segment index to enable. 288 segment_index_type insert_new_node( bucket *b, node_base *n, hashcode_type mask ) { 289 size_type sz = ++my_size; // prefix form is to enforce allocation after the first item inserted 290 add_to_bucket( b, n ); 291 // check load factor 292 if( sz >= mask ) { // TODO: add custom load_factor 293 segment_index_type new_seg = tbb::detail::log2( mask+1 ); //optimized segment_index_of 294 __TBB_ASSERT( is_valid(my_table[new_seg-1].load(std::memory_order_relaxed)), "new allocations must not publish new mask until segment has allocated"); 295 static const segment_ptr_type is_allocating = segment_ptr_type(2); 296 segment_ptr_type disabled = nullptr; 297 if (!(my_table[new_seg].load(std::memory_order_acquire)) 298 && my_table[new_seg].compare_exchange_strong(disabled, is_allocating)) 299 return new_seg; // The value must be processed 300 } 301 return 0; 302 } 303 304 // Prepare enough segments for number of buckets 305 void reserve(size_type buckets) { 306 if( !buckets-- ) return; 307 bool is_initial = !my_size.load(std::memory_order_relaxed); 308 for (size_type m = my_mask.load(std::memory_order_relaxed); buckets > m; 309 m = my_mask.load(std::memory_order_relaxed)) 310 { 311 enable_segment( segment_index_of( m+1 ), is_initial ); 312 } 313 } 314 315 // Swap hash_map_bases 316 void internal_swap_content(hash_map_base &table) { 317 using std::swap; 318 swap_atomics_relaxed(my_mask, table.my_mask); 319 swap_atomics_relaxed(my_size, table.my_size); 320 321 for(size_type i = 0; i < embedded_buckets; i++) { 322 auto temp = my_embedded_segment[i].node_list.load(std::memory_order_relaxed); 323 my_embedded_segment[i].node_list.store(table.my_embedded_segment[i].node_list.load(std::memory_order_relaxed), 324 std::memory_order_relaxed); 325 table.my_embedded_segment[i].node_list.store(temp, std::memory_order_relaxed); 326 } 327 for(size_type i = embedded_block; i < pointers_per_table; i++) { 328 auto temp = my_table[i].load(std::memory_order_relaxed); 329 my_table[i].store(table.my_table[i].load(std::memory_order_relaxed), 330 std::memory_order_relaxed); 331 table.my_table[i].store(temp, std::memory_order_relaxed); 332 } 333 } 334 335 void internal_move(hash_map_base&& other) { 336 my_mask.store(other.my_mask.load(std::memory_order_relaxed), std::memory_order_relaxed); 337 other.my_mask.store(embedded_buckets - 1, std::memory_order_relaxed); 338 339 my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed); 340 other.my_size.store(0, std::memory_order_relaxed); 341 342 for (size_type i = 0; i < embedded_buckets; ++i) { 343 my_embedded_segment[i].node_list.store(other.my_embedded_segment[i].node_list, std::memory_order_relaxed); 344 other.my_embedded_segment[i].node_list.store(nullptr, std::memory_order_relaxed); 345 } 346 347 for (size_type i = embedded_block; i < pointers_per_table; ++i) { 348 my_table[i].store(other.my_table[i].load(std::memory_order_relaxed), 349 std::memory_order_relaxed); 350 other.my_table[i].store(nullptr, std::memory_order_relaxed); 351 } 352 } 353 354 protected: 355 bucket_allocator_type my_allocator; 356 // Hash mask = sum of allocated segment sizes - 1 357 std::atomic<hashcode_type> my_mask; 358 // Size of container in stored items 359 std::atomic<size_type> my_size; // It must be in separate cache line from my_mask due to performance effects 360 // Zero segment 361 bucket my_embedded_segment[embedded_buckets]; 362 // Segment pointers table. Also prevents false sharing between my_mask and my_size 363 segments_table_type my_table; 364 }; 365 366 template <typename Iterator> 367 class hash_map_range; 368 369 // Meets requirements of a forward iterator for STL 370 // Value is either the T or const T type of the container. 371 template <typename Container, typename Value> 372 class hash_map_iterator { 373 using map_type = Container; 374 using node = typename Container::node; 375 using map_base = typename Container::base_type; 376 using node_base = typename map_base::node_base; 377 using bucket = typename map_base::bucket; 378 public: 379 using value_type = Value; 380 using size_type = typename Container::size_type; 381 using difference_type = typename Container::difference_type; 382 using pointer = value_type*; 383 using reference = value_type&; 384 using iterator_category = std::forward_iterator_tag; 385 386 // Construct undefined iterator 387 hash_map_iterator(): my_map(), my_index(), my_bucket(), my_node() {} 388 hash_map_iterator( const hash_map_iterator<Container, typename Container::value_type>& other ) : 389 my_map(other.my_map), 390 my_index(other.my_index), 391 my_bucket(other.my_bucket), 392 my_node(other.my_node) 393 {} 394 395 hash_map_iterator& operator=( const hash_map_iterator<Container, typename Container::value_type>& other ) { 396 my_map = other.my_map; 397 my_index = other.my_index; 398 my_bucket = other.my_bucket; 399 my_node = other.my_node; 400 return *this; 401 } 402 403 Value& operator*() const { 404 __TBB_ASSERT( map_base::is_valid(my_node), "iterator uninitialized or at end of container?" ); 405 return my_node->value(); 406 } 407 408 Value* operator->() const {return &operator*();} 409 410 hash_map_iterator& operator++() { 411 my_node = static_cast<node*>( my_node->next ); 412 if( !my_node ) advance_to_next_bucket(); 413 return *this; 414 } 415 416 // Post increment 417 hash_map_iterator operator++(int) { 418 hash_map_iterator old(*this); 419 operator++(); 420 return old; 421 } 422 private: 423 template <typename C, typename T, typename U> 424 friend bool operator==( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j ); 425 426 template <typename C, typename T, typename U> 427 friend bool operator!=( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j ); 428 429 template <typename C, typename T, typename U> 430 friend ptrdiff_t operator-( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j ); 431 432 template <typename C, typename U> 433 friend class hash_map_iterator; 434 435 template <typename I> 436 friend class hash_map_range; 437 438 void advance_to_next_bucket() { // TODO?: refactor to iterator_base class 439 size_t k = my_index+1; 440 __TBB_ASSERT( my_bucket, "advancing an invalid iterator?"); 441 while (k <= my_map->my_mask.load(std::memory_order_relaxed)) { 442 // Following test uses 2's-complement wizardry 443 if( k&(k-2) ) // not the beginning of a segment 444 ++my_bucket; 445 else my_bucket = my_map->get_bucket( k ); 446 node_base *n = my_bucket->node_list.load(std::memory_order_relaxed); 447 if( map_base::is_valid(n) ) { 448 my_node = static_cast<node*>(n); 449 my_index = k; 450 return; 451 } 452 ++k; 453 } 454 my_bucket = nullptr; my_node = nullptr; my_index = k; // the end 455 } 456 457 template <typename Key, typename T, typename HashCompare, typename A 458 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 459 , typename M 460 > 461 __TBB_requires(tbb::detail::hash_compare<HashCompare, Key> && 462 ch_map_rw_scoped_lockable<M>) 463 #else 464 > 465 __TBB_requires(tbb::detail::hash_compare<HashCompare, Key>) 466 #endif 467 friend class concurrent_hash_map; 468 469 hash_map_iterator( const Container &map, std::size_t index, const bucket *b, node_base *n ) : 470 my_map(&map), my_index(index), my_bucket(b), my_node(static_cast<node*>(n)) 471 { 472 if( b && !map_base::is_valid(n) ) 473 advance_to_next_bucket(); 474 } 475 476 // concurrent_hash_map over which we are iterating. 477 const Container *my_map; 478 // Index in hash table for current item 479 size_t my_index; 480 // Pointer to bucket 481 const bucket* my_bucket; 482 // Pointer to node that has current item 483 node* my_node; 484 }; 485 486 template <typename Container, typename T, typename U> 487 bool operator==( const hash_map_iterator<Container,T>& i, const hash_map_iterator<Container,U>& j ) { 488 return i.my_node == j.my_node && i.my_map == j.my_map; 489 } 490 491 template <typename Container, typename T, typename U> 492 bool operator!=( const hash_map_iterator<Container,T>& i, const hash_map_iterator<Container,U>& j ) { 493 return i.my_node != j.my_node || i.my_map != j.my_map; 494 } 495 496 // Range class used with concurrent_hash_map 497 template <typename Iterator> 498 class hash_map_range { 499 using map_type = typename Iterator::map_type; 500 public: 501 // Type for size of a range 502 using size_type = std::size_t; 503 using value_type = typename Iterator::value_type; 504 using reference = typename Iterator::reference; 505 using difference_type = typename Iterator::difference_type; 506 using iterator = Iterator; 507 508 // True if range is empty. 509 bool empty() const { return my_begin == my_end; } 510 511 // True if range can be partitioned into two subranges. 512 bool is_divisible() const { 513 return my_midpoint != my_end; 514 } 515 516 // Split range. 517 hash_map_range( hash_map_range& r, split ) : 518 my_end(r.my_end), 519 my_grainsize(r.my_grainsize) 520 { 521 r.my_end = my_begin = r.my_midpoint; 522 __TBB_ASSERT( !empty(), "Splitting despite the range is not divisible" ); 523 __TBB_ASSERT( !r.empty(), "Splitting despite the range is not divisible" ); 524 set_midpoint(); 525 r.set_midpoint(); 526 } 527 528 // Init range with container and grainsize specified 529 hash_map_range( const map_type &map, size_type grainsize_ = 1 ) : 530 my_begin( Iterator( map, 0, map.my_embedded_segment, map.my_embedded_segment->node_list.load(std::memory_order_relaxed) ) ), 531 my_end( Iterator( map, map.my_mask.load(std::memory_order_relaxed) + 1, nullptr, nullptr ) ), 532 my_grainsize( grainsize_ ) 533 { 534 __TBB_ASSERT( grainsize_>0, "grainsize must be positive" ); 535 set_midpoint(); 536 } 537 538 Iterator begin() const { return my_begin; } 539 Iterator end() const { return my_end; } 540 // The grain size for this range. 541 size_type grainsize() const { return my_grainsize; } 542 543 private: 544 Iterator my_begin; 545 Iterator my_end; 546 mutable Iterator my_midpoint; 547 size_t my_grainsize; 548 // Set my_midpoint to point approximately half way between my_begin and my_end. 549 void set_midpoint() const; 550 template <typename U> friend class hash_map_range; 551 }; 552 553 template <typename Iterator> 554 void hash_map_range<Iterator>::set_midpoint() const { 555 // Split by groups of nodes 556 size_t m = my_end.my_index-my_begin.my_index; 557 if( m > my_grainsize ) { 558 m = my_begin.my_index + m/2u; 559 auto b = my_begin.my_map->get_bucket(m); 560 my_midpoint = Iterator(*my_begin.my_map,m,b,b->node_list.load(std::memory_order_relaxed)); 561 } else { 562 my_midpoint = my_end; 563 } 564 __TBB_ASSERT( my_begin.my_index <= my_midpoint.my_index, 565 "my_begin is after my_midpoint" ); 566 __TBB_ASSERT( my_midpoint.my_index <= my_end.my_index, 567 "my_midpoint is after my_end" ); 568 __TBB_ASSERT( my_begin != my_midpoint || my_begin == my_end, 569 "[my_begin, my_midpoint) range should not be empty" ); 570 } 571 572 template <typename Key, typename T, 573 typename HashCompare = d1::tbb_hash_compare<Key>, 574 typename Allocator = tbb_allocator<std::pair<const Key, T>> 575 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 576 , typename MutexType = spin_rw_mutex 577 > 578 __TBB_requires(tbb::detail::hash_compare<HashCompare, Key> && 579 ch_map_rw_scoped_lockable<MutexType>) 580 #else 581 > 582 __TBB_requires(tbb::detail::hash_compare<HashCompare, Key>) 583 #endif 584 class concurrent_hash_map 585 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 586 : protected hash_map_base<Allocator, MutexType> 587 #else 588 : protected hash_map_base<Allocator, spin_rw_mutex> 589 #endif 590 { 591 template <typename Container, typename Value> 592 friend class hash_map_iterator; 593 594 template <typename I> 595 friend class hash_map_range; 596 using allocator_traits_type = tbb::detail::allocator_traits<Allocator>; 597 598 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 599 using base_type = hash_map_base<Allocator, MutexType>; 600 #else 601 using base_type = hash_map_base<Allocator, spin_rw_mutex>; 602 #endif 603 public: 604 using key_type = Key; 605 using mapped_type = T; 606 // type_identity is needed to disable implicit deduction guides for std::initializer_list constructors 607 // and copy/move constructor with explicit allocator argument 608 using allocator_type = tbb::detail::type_identity_t<Allocator>; 609 using hash_compare_type = tbb::detail::type_identity_t<HashCompare>; 610 using value_type = std::pair<const Key, T>; 611 using size_type = typename base_type::size_type; 612 using difference_type = std::ptrdiff_t; 613 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 614 using mutex_type = MutexType; 615 #endif 616 using pointer = typename allocator_traits_type::pointer; 617 using const_pointer = typename allocator_traits_type::const_pointer; 618 619 using reference = value_type&; 620 using const_reference = const value_type&; 621 using iterator = hash_map_iterator<concurrent_hash_map, value_type>; 622 using const_iterator = hash_map_iterator<concurrent_hash_map, const value_type>; 623 using range_type = hash_map_range<iterator>; 624 using const_range_type = hash_map_range<const_iterator>; 625 626 protected: 627 static_assert(std::is_same<value_type, typename Allocator::value_type>::value, 628 "value_type of the container must be the same as its allocator's"); 629 630 friend class const_accessor; 631 class node; 632 using segment_index_type = typename base_type::segment_index_type; 633 using segment_ptr_type = typename base_type::segment_ptr_type; 634 using node_base = typename base_type::node_base; 635 using bucket = typename base_type::bucket; 636 using hashcode_type = typename base_type::hashcode_type; 637 using bucket_allocator_type = typename base_type::bucket_allocator_type; 638 using node_allocator_type = typename base_type::allocator_traits_type::template rebind_alloc<node>; 639 using node_allocator_traits = tbb::detail::allocator_traits<node_allocator_type>; 640 hash_compare_type my_hash_compare; 641 642 class node : public node_base { 643 public: 644 node() {} 645 ~node() {} 646 pointer storage() { return &my_value; } 647 value_type& value() { return *storage(); } 648 private: 649 union { 650 value_type my_value; 651 }; 652 }; 653 654 void delete_node( node_base *n ) { 655 node_allocator_type node_allocator(this->get_allocator()); 656 node_allocator_traits::destroy(node_allocator, static_cast<node*>(n)->storage()); 657 node_allocator_traits::destroy(node_allocator, static_cast<node*>(n)); 658 node_allocator_traits::deallocate(node_allocator, static_cast<node*>(n), 1); 659 } 660 661 template <typename... Args> 662 static node* create_node(bucket_allocator_type& allocator, Args&&... args) { 663 node_allocator_type node_allocator(allocator); 664 node* node_ptr = node_allocator_traits::allocate(node_allocator, 1); 665 auto guard = make_raii_guard([&] { 666 node_allocator_traits::destroy(node_allocator, node_ptr); 667 node_allocator_traits::deallocate(node_allocator, node_ptr, 1); 668 }); 669 670 node_allocator_traits::construct(node_allocator, node_ptr); 671 node_allocator_traits::construct(node_allocator, node_ptr->storage(), std::forward<Args>(args)...); 672 guard.dismiss(); 673 return node_ptr; 674 } 675 676 static node* allocate_node_copy_construct(bucket_allocator_type& allocator, const Key &key, const T * t){ 677 return create_node(allocator, key, *t); 678 } 679 680 static node* allocate_node_move_construct(bucket_allocator_type& allocator, const Key &key, const T * t){ 681 return create_node(allocator, key, std::move(*const_cast<T*>(t))); 682 } 683 684 template <typename K = Key> 685 static node* allocate_node_default_construct(bucket_allocator_type& allocator, const K &key, const T * ){ 686 // Emplace construct an empty T object inside the pair 687 return create_node(allocator, std::piecewise_construct, 688 std::forward_as_tuple(key), std::forward_as_tuple()); 689 } 690 691 static node* do_not_allocate_node(bucket_allocator_type& , const Key &, const T * ){ 692 __TBB_ASSERT(false,"this dummy function should not be called"); 693 return nullptr; 694 } 695 696 template <typename K> 697 node *search_bucket( const K &key, bucket *b ) const { 698 node *n = static_cast<node*>( b->node_list.load(std::memory_order_relaxed) ); 699 while (this->is_valid(n) && !my_hash_compare.equal(key, n->value().first)) 700 n = static_cast<node*>( n->next ); 701 __TBB_ASSERT(!rehash_required(n), "Search can be executed only for rehashed bucket"); 702 return n; 703 } 704 705 // bucket accessor is to find, rehash, acquire a lock, and access a bucket 706 class bucket_accessor : public bucket::scoped_type { 707 bucket *my_b; 708 public: 709 bucket_accessor( concurrent_hash_map *base, const hashcode_type h, bool writer = false ) { acquire( base, h, writer ); } 710 // find a bucket by masked hashcode, optionally rehash, and acquire the lock 711 inline void acquire( concurrent_hash_map *base, const hashcode_type h, bool writer = false ) { 712 my_b = base->get_bucket( h ); 713 // TODO: actually, notification is unnecessary here, just hiding double-check 714 if (rehash_required(my_b->node_list.load(std::memory_order_acquire)) 715 && bucket::scoped_type::try_acquire( my_b->mutex, /*write=*/true ) ) 716 { 717 if (rehash_required(my_b->node_list.load(std::memory_order_relaxed))) base->rehash_bucket(my_b, h); // recursive rehashing 718 } 719 else bucket::scoped_type::acquire( my_b->mutex, writer ); 720 __TBB_ASSERT(!rehash_required(my_b->node_list.load(std::memory_order_relaxed)), nullptr); 721 } 722 723 // get bucket pointer 724 bucket *operator() () { return my_b; } 725 }; 726 727 // TODO refactor to hash_base 728 void rehash_bucket( bucket *b_new, const hashcode_type hash ) { 729 __TBB_ASSERT( hash > 1, "The lowermost buckets can't be rehashed" ); 730 b_new->node_list.store(reinterpret_cast<node_base*>(empty_rehashed_flag), std::memory_order_release); // mark rehashed 731 hashcode_type mask = (hashcode_type(1) << tbb::detail::log2(hash)) - 1; // get parent mask from the topmost bit 732 bucket_accessor b_old( this, hash & mask ); 733 734 mask = (mask<<1) | 1; // get full mask for new bucket 735 __TBB_ASSERT( (mask&(mask+1))==0 && (hash & mask) == hash, nullptr ); 736 restart: 737 node_base* prev = nullptr; 738 node_base* curr = b_old()->node_list.load(std::memory_order_acquire); 739 while (this->is_valid(curr)) { 740 hashcode_type curr_node_hash = my_hash_compare.hash(static_cast<node*>(curr)->value().first); 741 742 if ((curr_node_hash & mask) == hash) { 743 if (!b_old.is_writer()) { 744 if (!b_old.upgrade_to_writer()) { 745 goto restart; // node ptr can be invalid due to concurrent erase 746 } 747 } 748 node_base* next = curr->next; 749 // exclude from b_old 750 if (prev == nullptr) { 751 b_old()->node_list.store(curr->next, std::memory_order_relaxed); 752 } else { 753 prev->next = curr->next; 754 } 755 this->add_to_bucket(b_new, curr); 756 curr = next; 757 } else { 758 prev = curr; 759 curr = curr->next; 760 } 761 } 762 } 763 764 template <typename U> 765 using hash_compare_is_transparent = dependent_bool<comp_is_transparent<hash_compare_type>, U>; 766 767 public: 768 769 class accessor; 770 // Combines data access, locking, and garbage collection. 771 class const_accessor : private node::scoped_type /*which derived from no_copy*/ { 772 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 773 friend class concurrent_hash_map<Key,T,HashCompare,Allocator,MutexType>; 774 #else 775 friend class concurrent_hash_map<Key,T,HashCompare,Allocator>; 776 #endif 777 friend class accessor; 778 public: 779 // Type of value 780 using value_type = const typename concurrent_hash_map::value_type; 781 782 // True if result is empty. 783 bool empty() const { return !my_node; } 784 785 // Set to null 786 void release() { 787 if( my_node ) { 788 node::scoped_type::release(); 789 my_node = nullptr; 790 } 791 } 792 793 // Return reference to associated value in hash table. 794 const_reference operator*() const { 795 __TBB_ASSERT( my_node, "attempt to dereference empty accessor" ); 796 return my_node->value(); 797 } 798 799 // Return pointer to associated value in hash table. 800 const_pointer operator->() const { 801 return &operator*(); 802 } 803 804 // Create empty result 805 const_accessor() : my_node(nullptr), my_hash() {} 806 807 // Destroy result after releasing the underlying reference. 808 ~const_accessor() { 809 my_node = nullptr; // scoped lock's release() is called in its destructor 810 } 811 protected: 812 bool is_writer() { return node::scoped_type::is_writer(); } 813 node *my_node; 814 hashcode_type my_hash; 815 }; 816 817 // Allows write access to elements and combines data access, locking, and garbage collection. 818 class accessor: public const_accessor { 819 public: 820 // Type of value 821 using value_type = typename concurrent_hash_map::value_type; 822 823 // Return reference to associated value in hash table. 824 reference operator*() const { 825 __TBB_ASSERT( this->my_node, "attempt to dereference empty accessor" ); 826 return this->my_node->value(); 827 } 828 829 // Return pointer to associated value in hash table. 830 pointer operator->() const { 831 return &operator*(); 832 } 833 }; 834 835 explicit concurrent_hash_map( const hash_compare_type& compare, const allocator_type& a = allocator_type() ) 836 : base_type(a) 837 , my_hash_compare(compare) 838 {} 839 840 concurrent_hash_map() : concurrent_hash_map(hash_compare_type()) {} 841 842 explicit concurrent_hash_map( const allocator_type& a ) 843 : concurrent_hash_map(hash_compare_type(), a) 844 {} 845 846 // Construct empty table with n preallocated buckets. This number serves also as initial concurrency level. 847 concurrent_hash_map( size_type n, const allocator_type &a = allocator_type() ) 848 : concurrent_hash_map(a) 849 { 850 this->reserve(n); 851 } 852 853 concurrent_hash_map( size_type n, const hash_compare_type& compare, const allocator_type& a = allocator_type() ) 854 : concurrent_hash_map(compare, a) 855 { 856 this->reserve(n); 857 } 858 859 // Copy constructor 860 concurrent_hash_map( const concurrent_hash_map &table ) 861 : concurrent_hash_map(node_allocator_traits::select_on_container_copy_construction(table.get_allocator())) 862 { 863 try_call( [&] { 864 internal_copy(table); 865 }).on_exception( [&] { 866 this->clear(); 867 }); 868 } 869 870 concurrent_hash_map( const concurrent_hash_map &table, const allocator_type &a) 871 : concurrent_hash_map(a) 872 { 873 try_call( [&] { 874 internal_copy(table); 875 }).on_exception( [&] { 876 this->clear(); 877 }); 878 } 879 880 // Move constructor 881 concurrent_hash_map( concurrent_hash_map &&table ) 882 : concurrent_hash_map(std::move(table.get_allocator())) 883 { 884 this->internal_move(std::move(table)); 885 } 886 887 // Move constructor 888 concurrent_hash_map( concurrent_hash_map &&table, const allocator_type &a ) 889 : concurrent_hash_map(a) 890 { 891 using is_equal_type = typename node_allocator_traits::is_always_equal; 892 internal_move_construct_with_allocator(std::move(table), a, is_equal_type()); 893 } 894 895 // Construction with copying iteration range and given allocator instance 896 template <typename I> 897 concurrent_hash_map( I first, I last, const allocator_type &a = allocator_type() ) 898 : concurrent_hash_map(a) 899 { 900 try_call( [&] { 901 internal_copy(first, last, std::distance(first, last)); 902 }).on_exception( [&] { 903 this->clear(); 904 }); 905 } 906 907 template <typename I> 908 concurrent_hash_map( I first, I last, const hash_compare_type& compare, const allocator_type& a = allocator_type() ) 909 : concurrent_hash_map(compare, a) 910 { 911 try_call( [&] { 912 internal_copy(first, last, std::distance(first, last)); 913 }).on_exception( [&] { 914 this->clear(); 915 }); 916 } 917 918 concurrent_hash_map( std::initializer_list<value_type> il, const hash_compare_type& compare = hash_compare_type(), const allocator_type& a = allocator_type() ) 919 : concurrent_hash_map(compare, a) 920 { 921 try_call( [&] { 922 internal_copy(il.begin(), il.end(), il.size()); 923 }).on_exception( [&] { 924 this->clear(); 925 }); 926 } 927 928 concurrent_hash_map( std::initializer_list<value_type> il, const allocator_type& a ) 929 : concurrent_hash_map(il, hash_compare_type(), a) {} 930 931 // Assignment 932 concurrent_hash_map& operator=( const concurrent_hash_map &table ) { 933 if( this != &table ) { 934 clear(); 935 copy_assign_allocators(this->my_allocator, table.my_allocator); 936 internal_copy(table); 937 } 938 return *this; 939 } 940 941 // Move Assignment 942 concurrent_hash_map& operator=( concurrent_hash_map &&table ) { 943 if( this != &table ) { 944 using pocma_type = typename node_allocator_traits::propagate_on_container_move_assignment; 945 using is_equal_type = typename node_allocator_traits::is_always_equal; 946 move_assign_allocators(this->my_allocator, table.my_allocator); 947 internal_move_assign(std::move(table), tbb::detail::disjunction<is_equal_type, pocma_type>()); 948 } 949 return *this; 950 } 951 952 // Assignment 953 concurrent_hash_map& operator=( std::initializer_list<value_type> il ) { 954 clear(); 955 internal_copy(il.begin(), il.end(), il.size()); 956 return *this; 957 } 958 959 // Rehashes and optionally resizes the whole table. 960 /** Useful to optimize performance before or after concurrent operations. 961 Also enables using of find() and count() concurrent methods in serial context. */ 962 void rehash(size_type sz = 0) { 963 this->reserve(sz); // TODO: add reduction of number of buckets as well 964 hashcode_type mask = this->my_mask.load(std::memory_order_relaxed); 965 hashcode_type b = (mask+1)>>1; // size or first index of the last segment 966 __TBB_ASSERT((b&(b-1))==0, nullptr); // zero or power of 2 967 bucket *bp = this->get_bucket( b ); // only the last segment should be scanned for rehashing 968 for(; b <= mask; b++, bp++ ) { 969 node_base *n = bp->node_list.load(std::memory_order_relaxed); 970 __TBB_ASSERT( this->is_valid(n) || empty_rehashed(n) || rehash_required(n), "Broken internal structure" ); 971 __TBB_ASSERT( *reinterpret_cast<intptr_t*>(&bp->mutex) == 0, "concurrent or unexpectedly terminated operation during rehash() execution" ); 972 if (rehash_required(n)) { // rehash bucket, conditional because rehashing of a previous bucket may affect this one 973 hashcode_type h = b; bucket *b_old = bp; 974 do { 975 __TBB_ASSERT( h > 1, "The lowermost buckets can't be rehashed" ); 976 hashcode_type m = ( hashcode_type(1) << tbb::detail::log2( h ) ) - 1; // get parent mask from the topmost bit 977 b_old = this->get_bucket( h &= m ); 978 } while( rehash_required(b_old->node_list.load(std::memory_order_relaxed)) ); 979 // now h - is index of the root rehashed bucket b_old 980 this->mark_rehashed_levels( h ); // mark all non-rehashed children recursively across all segments 981 node_base* prev = nullptr; 982 node_base* curr = b_old->node_list.load(std::memory_order_relaxed); 983 while (this->is_valid(curr)) { 984 hashcode_type curr_node_hash = my_hash_compare.hash(static_cast<node*>(curr)->value().first); 985 986 if ((curr_node_hash & mask) != h) { // should be rehashed 987 node_base* next = curr->next; 988 // exclude from b_old 989 if (prev == nullptr) { 990 b_old->node_list.store(curr->next, std::memory_order_relaxed); 991 } else { 992 prev->next = curr->next; 993 } 994 bucket *b_new = this->get_bucket(curr_node_hash & mask); 995 __TBB_ASSERT(!rehash_required(b_new->node_list.load(std::memory_order_relaxed)), "hash() function changed for key in table or internal error"); 996 this->add_to_bucket(b_new, curr); 997 curr = next; 998 } else { 999 prev = curr; 1000 curr = curr->next; 1001 } 1002 } 1003 } 1004 } 1005 } 1006 1007 // Clear table 1008 void clear() { 1009 hashcode_type m = this->my_mask.load(std::memory_order_relaxed); 1010 __TBB_ASSERT((m&(m+1))==0, "data structure is invalid"); 1011 this->my_size.store(0, std::memory_order_relaxed); 1012 segment_index_type s = this->segment_index_of( m ); 1013 __TBB_ASSERT( s+1 == this->pointers_per_table || !this->my_table[s+1].load(std::memory_order_relaxed), "wrong mask or concurrent grow" ); 1014 do { 1015 __TBB_ASSERT(this->is_valid(this->my_table[s].load(std::memory_order_relaxed)), "wrong mask or concurrent grow" ); 1016 segment_ptr_type buckets_ptr = this->my_table[s].load(std::memory_order_relaxed); 1017 size_type sz = this->segment_size( s ? s : 1 ); 1018 for( segment_index_type i = 0; i < sz; i++ ) 1019 for( node_base *n = buckets_ptr[i].node_list.load(std::memory_order_relaxed); 1020 this->is_valid(n); n = buckets_ptr[i].node_list.load(std::memory_order_relaxed) ) 1021 { 1022 buckets_ptr[i].node_list.store(n->next, std::memory_order_relaxed); 1023 delete_node( n ); 1024 } 1025 this->delete_segment(s); 1026 } while(s-- > 0); 1027 this->my_mask.store(this->embedded_buckets - 1, std::memory_order_relaxed); 1028 } 1029 1030 // Clear table and destroy it. 1031 ~concurrent_hash_map() { clear(); } 1032 1033 //------------------------------------------------------------------------ 1034 // Parallel algorithm support 1035 //------------------------------------------------------------------------ 1036 range_type range( size_type grainsize=1 ) { 1037 return range_type( *this, grainsize ); 1038 } 1039 const_range_type range( size_type grainsize=1 ) const { 1040 return const_range_type( *this, grainsize ); 1041 } 1042 1043 //------------------------------------------------------------------------ 1044 // STL support - not thread-safe methods 1045 //------------------------------------------------------------------------ 1046 iterator begin() { return iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); } 1047 const_iterator begin() const { return const_iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); } 1048 const_iterator cbegin() const { return const_iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); } 1049 iterator end() { return iterator( *this, 0, nullptr, nullptr ); } 1050 const_iterator end() const { return const_iterator( *this, 0, nullptr, nullptr ); } 1051 const_iterator cend() const { return const_iterator( *this, 0, nullptr, nullptr ); } 1052 std::pair<iterator, iterator> equal_range( const Key& key ) { return internal_equal_range( key, end() ); } 1053 std::pair<const_iterator, const_iterator> equal_range( const Key& key ) const { return internal_equal_range( key, end() ); } 1054 1055 template <typename K> 1056 typename std::enable_if<hash_compare_is_transparent<K>::value, 1057 std::pair<iterator, iterator>>::type equal_range( const K& key ) { 1058 return internal_equal_range(key, end()); 1059 } 1060 1061 template <typename K> 1062 typename std::enable_if<hash_compare_is_transparent<K>::value, 1063 std::pair<const_iterator, const_iterator>>::type equal_range( const K& key ) const { 1064 return internal_equal_range(key, end()); 1065 } 1066 1067 // Number of items in table. 1068 size_type size() const { return this->my_size.load(std::memory_order_acquire); } 1069 1070 // True if size()==0. 1071 __TBB_nodiscard bool empty() const { return size() == 0; } 1072 1073 // Upper bound on size. 1074 size_type max_size() const { 1075 return allocator_traits_type::max_size(base_type::get_allocator()); 1076 } 1077 1078 // Returns the current number of buckets 1079 size_type bucket_count() const { return this->my_mask.load(std::memory_order_relaxed) + 1; } 1080 1081 // return allocator object 1082 allocator_type get_allocator() const { return base_type::get_allocator(); } 1083 1084 // swap two instances. Iterators are invalidated 1085 void swap(concurrent_hash_map& table) { 1086 using pocs_type = typename node_allocator_traits::propagate_on_container_swap; 1087 using is_equal_type = typename node_allocator_traits::is_always_equal; 1088 swap_allocators(this->my_allocator, table.my_allocator); 1089 internal_swap(table, tbb::detail::disjunction<pocs_type, is_equal_type>()); 1090 } 1091 1092 //------------------------------------------------------------------------ 1093 // concurrent map operations 1094 //------------------------------------------------------------------------ 1095 1096 // Return count of items (0 or 1) 1097 size_type count( const Key &key ) const { 1098 return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, nullptr, /*write=*/false, &do_not_allocate_node); 1099 } 1100 1101 template <typename K> 1102 typename std::enable_if<hash_compare_is_transparent<K>::value, 1103 size_type>::type count( const K& key ) const { 1104 return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, nullptr, /*write=*/false, &do_not_allocate_node); 1105 } 1106 1107 // Find item and acquire a read lock on the item. 1108 /** Return true if item is found, false otherwise. */ 1109 bool find( const_accessor &result, const Key &key ) const { 1110 result.release(); 1111 return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, &result, /*write=*/false, &do_not_allocate_node ); 1112 } 1113 1114 // Find item and acquire a write lock on the item. 1115 /** Return true if item is found, false otherwise. */ 1116 bool find( accessor &result, const Key &key ) { 1117 result.release(); 1118 return lookup</*insert*/false>(key, nullptr, &result, /*write=*/true, &do_not_allocate_node); 1119 } 1120 1121 template <typename K> 1122 typename std::enable_if<hash_compare_is_transparent<K>::value, 1123 bool>::type find( const_accessor& result, const K& key ) { 1124 result.release(); 1125 return lookup</*insert*/false>(key, nullptr, &result, /*write=*/false, &do_not_allocate_node); 1126 } 1127 1128 template <typename K> 1129 typename std::enable_if<hash_compare_is_transparent<K>::value, 1130 bool>::type find( accessor& result, const K& key ) { 1131 result.release(); 1132 return lookup</*insert*/false>(key, nullptr, &result, /*write=*/true, &do_not_allocate_node); 1133 } 1134 1135 // Insert item (if not already present) and acquire a read lock on the item. 1136 /** Returns true if item is new. */ 1137 bool insert( const_accessor &result, const Key &key ) { 1138 result.release(); 1139 return lookup</*insert*/true>(key, nullptr, &result, /*write=*/false, &allocate_node_default_construct<>); 1140 } 1141 1142 // Insert item (if not already present) and acquire a write lock on the item. 1143 /** Returns true if item is new. */ 1144 bool insert( accessor &result, const Key &key ) { 1145 result.release(); 1146 return lookup</*insert*/true>(key, nullptr, &result, /*write=*/true, &allocate_node_default_construct<>); 1147 } 1148 1149 template <typename K> 1150 typename std::enable_if<hash_compare_is_transparent<K>::value && 1151 std::is_constructible<key_type, const K&>::value, 1152 bool>::type insert( const_accessor& result, const K& key ) { 1153 result.release(); 1154 return lookup</*insert*/true>(key, nullptr, &result, /*write=*/false, &allocate_node_default_construct<K>); 1155 } 1156 1157 template <typename K> 1158 typename std::enable_if<hash_compare_is_transparent<K>::value && 1159 std::is_constructible<key_type, const K&>::value, 1160 bool>::type insert( accessor& result, const K& key ) { 1161 result.release(); 1162 return lookup</*insert*/true>(key, nullptr, &result, /*write=*/true, &allocate_node_default_construct<K>); 1163 } 1164 1165 // Insert item by copying if there is no such key present already and acquire a read lock on the item. 1166 /** Returns true if item is new. */ 1167 bool insert( const_accessor &result, const value_type &value ) { 1168 result.release(); 1169 return lookup</*insert*/true>(value.first, &value.second, &result, /*write=*/false, &allocate_node_copy_construct); 1170 } 1171 1172 // Insert item by copying if there is no such key present already and acquire a write lock on the item. 1173 /** Returns true if item is new. */ 1174 bool insert( accessor &result, const value_type &value ) { 1175 result.release(); 1176 return lookup</*insert*/true>(value.first, &value.second, &result, /*write=*/true, &allocate_node_copy_construct); 1177 } 1178 1179 // Insert item by copying if there is no such key present already 1180 /** Returns true if item is inserted. */ 1181 bool insert( const value_type &value ) { 1182 return lookup</*insert*/true>(value.first, &value.second, nullptr, /*write=*/false, &allocate_node_copy_construct); 1183 } 1184 1185 // Insert item by copying if there is no such key present already and acquire a read lock on the item. 1186 /** Returns true if item is new. */ 1187 bool insert( const_accessor &result, value_type && value ) { 1188 return generic_move_insert(result, std::move(value)); 1189 } 1190 1191 // Insert item by copying if there is no such key present already and acquire a write lock on the item. 1192 /** Returns true if item is new. */ 1193 bool insert( accessor &result, value_type && value ) { 1194 return generic_move_insert(result, std::move(value)); 1195 } 1196 1197 // Insert item by copying if there is no such key present already 1198 /** Returns true if item is inserted. */ 1199 bool insert( value_type && value ) { 1200 return generic_move_insert(accessor_not_used(), std::move(value)); 1201 } 1202 1203 // Insert item by copying if there is no such key present already and acquire a read lock on the item. 1204 /** Returns true if item is new. */ 1205 template <typename... Args> 1206 bool emplace( const_accessor &result, Args&&... args ) { 1207 return generic_emplace(result, std::forward<Args>(args)...); 1208 } 1209 1210 // Insert item by copying if there is no such key present already and acquire a write lock on the item. 1211 /** Returns true if item is new. */ 1212 template <typename... Args> 1213 bool emplace( accessor &result, Args&&... args ) { 1214 return generic_emplace(result, std::forward<Args>(args)...); 1215 } 1216 1217 // Insert item by copying if there is no such key present already 1218 /** Returns true if item is inserted. */ 1219 template <typename... Args> 1220 bool emplace( Args&&... args ) { 1221 return generic_emplace(accessor_not_used(), std::forward<Args>(args)...); 1222 } 1223 1224 // Insert range [first, last) 1225 template <typename I> 1226 void insert( I first, I last ) { 1227 for ( ; first != last; ++first ) 1228 insert( *first ); 1229 } 1230 1231 // Insert initializer list 1232 void insert( std::initializer_list<value_type> il ) { 1233 insert( il.begin(), il.end() ); 1234 } 1235 1236 // Erase item. 1237 /** Return true if item was erased by particularly this call. */ 1238 bool erase( const Key &key ) { 1239 return internal_erase(key); 1240 } 1241 1242 template <typename K> 1243 typename std::enable_if<hash_compare_is_transparent<K>::value, 1244 bool>::type erase( const K& key ) { 1245 return internal_erase(key); 1246 } 1247 1248 // Erase item by const_accessor. 1249 /** Return true if item was erased by particularly this call. */ 1250 bool erase( const_accessor& item_accessor ) { 1251 return exclude( item_accessor ); 1252 } 1253 1254 // Erase item by accessor. 1255 /** Return true if item was erased by particularly this call. */ 1256 bool erase( accessor& item_accessor ) { 1257 return exclude( item_accessor ); 1258 } 1259 1260 protected: 1261 template <typename K, typename AllocateNodeType> 1262 node* allocate_node_helper( const K& key, const T* t, AllocateNodeType allocate_node, std::true_type ) { 1263 return allocate_node(base_type::get_allocator(), key, t); 1264 } 1265 1266 template <typename K, typename AllocateNodeType> 1267 node* allocate_node_helper( const K&, const T*, AllocateNodeType, std::false_type ) { 1268 __TBB_ASSERT(false, "allocate_node_helper with std::false_type should never been called"); 1269 return nullptr; 1270 } 1271 1272 // Insert or find item and optionally acquire a lock on the item. 1273 template <bool OpInsert, typename K, typename AllocateNodeType> 1274 bool lookup( const K &key, const T *t, const_accessor *result, bool write, AllocateNodeType allocate_node, node *tmp_n = nullptr) 1275 { 1276 __TBB_ASSERT( !result || !result->my_node, nullptr ); 1277 bool return_value; 1278 hashcode_type const h = my_hash_compare.hash( key ); 1279 hashcode_type m = this->my_mask.load(std::memory_order_acquire); 1280 segment_index_type grow_segment = 0; 1281 node *n; 1282 restart: 1283 {//lock scope 1284 __TBB_ASSERT((m&(m+1))==0, "data structure is invalid"); 1285 return_value = false; 1286 // get bucket 1287 bucket_accessor b( this, h & m ); 1288 // find a node 1289 n = search_bucket( key, b() ); 1290 if( OpInsert ) { 1291 // [opt] insert a key 1292 if( !n ) { 1293 if( !tmp_n ) { 1294 tmp_n = allocate_node_helper(key, t, allocate_node, std::integral_constant<bool, OpInsert>{}); 1295 } 1296 while ( !b.is_writer() && !b.upgrade_to_writer() ) { // TODO: improved insertion 1297 // Rerun search list, in case another thread inserted the intem during the upgrade 1298 n = search_bucket(key, b()); 1299 if (this->is_valid(n)) { // unfortunately, it did 1300 if (!b.downgrade_to_reader()) { 1301 // If the lock was downgraded with reacquiring the mutex 1302 // Rerun search list in case another thread removed the item during the downgrade 1303 n = search_bucket(key, b()); 1304 if (!this->is_valid(n)) { 1305 // Unfortunately, it did 1306 // We need to try upgrading to writer again 1307 continue; 1308 } 1309 } 1310 goto exists; 1311 } 1312 } 1313 1314 if( this->check_mask_race(h, m) ) 1315 goto restart; // b.release() is done in ~b(). 1316 // insert and set flag to grow the container 1317 grow_segment = this->insert_new_node( b(), n = tmp_n, m ); 1318 tmp_n = nullptr; 1319 return_value = true; 1320 } 1321 } else { // find or count 1322 if( !n ) { 1323 if( this->check_mask_race( h, m ) ) 1324 goto restart; // b.release() is done in ~b(). TODO: replace by continue 1325 return false; 1326 } 1327 return_value = true; 1328 } 1329 exists: 1330 if( !result ) goto check_growth; 1331 // TODO: the following seems as generic/regular operation 1332 // acquire the item 1333 if( !result->try_acquire( n->mutex, write ) ) { 1334 for( tbb::detail::atomic_backoff backoff(true);; ) { 1335 if( result->try_acquire( n->mutex, write ) ) break; 1336 if( !backoff.bounded_pause() ) { 1337 // the wait takes really long, restart the operation 1338 b.release(); 1339 __TBB_ASSERT( !OpInsert || !return_value, "Can't acquire new item in locked bucket?" ); 1340 yield(); 1341 m = this->my_mask.load(std::memory_order_acquire); 1342 goto restart; 1343 } 1344 } 1345 } 1346 }//lock scope 1347 result->my_node = n; 1348 result->my_hash = h; 1349 check_growth: 1350 // [opt] grow the container 1351 if( grow_segment ) { 1352 this->enable_segment( grow_segment ); 1353 } 1354 if( tmp_n ) // if OpInsert only 1355 delete_node( tmp_n ); 1356 return return_value; 1357 } 1358 1359 struct accessor_not_used { void release(){}}; 1360 friend const_accessor* accessor_location( accessor_not_used const& ){ return nullptr;} 1361 friend const_accessor* accessor_location( const_accessor & a ) { return &a;} 1362 1363 friend bool is_write_access_needed( accessor const& ) { return true;} 1364 friend bool is_write_access_needed( const_accessor const& ) { return false;} 1365 friend bool is_write_access_needed( accessor_not_used const& ) { return false;} 1366 1367 template <typename Accessor> 1368 bool generic_move_insert( Accessor && result, value_type && value ) { 1369 result.release(); 1370 return lookup</*insert*/true>(value.first, &value.second, accessor_location(result), is_write_access_needed(result), &allocate_node_move_construct); 1371 } 1372 1373 template <typename Accessor, typename... Args> 1374 bool generic_emplace( Accessor && result, Args &&... args ) { 1375 result.release(); 1376 node * node_ptr = create_node(base_type::get_allocator(), std::forward<Args>(args)...); 1377 return lookup</*insert*/true>(node_ptr->value().first, nullptr, accessor_location(result), is_write_access_needed(result), &do_not_allocate_node, node_ptr); 1378 } 1379 1380 // delete item by accessor 1381 bool exclude( const_accessor &item_accessor ) { 1382 __TBB_ASSERT( item_accessor.my_node, nullptr ); 1383 node_base *const exclude_node = item_accessor.my_node; 1384 hashcode_type const hash = item_accessor.my_hash; 1385 hashcode_type mask = this->my_mask.load(std::memory_order_acquire); 1386 do { 1387 // get bucket 1388 bucket_accessor b( this, hash & mask, /*writer=*/true ); 1389 node_base* prev = nullptr; 1390 node_base* curr = b()->node_list.load(std::memory_order_relaxed); 1391 1392 while (curr && curr != exclude_node) { 1393 prev = curr; 1394 curr = curr->next; 1395 } 1396 1397 if (curr == nullptr) { // someone else was first 1398 if (this->check_mask_race(hash, mask)) 1399 continue; 1400 item_accessor.release(); 1401 return false; 1402 } 1403 __TBB_ASSERT( curr == exclude_node, nullptr ); 1404 // remove from container 1405 if (prev == nullptr) { 1406 b()->node_list.store(curr->next, std::memory_order_relaxed); 1407 } else { 1408 prev->next = curr->next; 1409 } 1410 1411 this->my_size--; 1412 break; 1413 } while(true); 1414 if (!item_accessor.is_writer()) { // need to get exclusive lock 1415 item_accessor.upgrade_to_writer(); // return value means nothing here 1416 } 1417 1418 item_accessor.release(); 1419 delete_node(exclude_node); // Only one thread can delete it 1420 return true; 1421 } 1422 1423 template <typename K> 1424 bool internal_erase( const K& key ) { 1425 node_base *erase_node; 1426 hashcode_type const hash = my_hash_compare.hash(key); 1427 hashcode_type mask = this->my_mask.load(std::memory_order_acquire); 1428 restart: 1429 {//lock scope 1430 // get bucket 1431 bucket_accessor b( this, hash & mask ); 1432 search: 1433 node_base* prev = nullptr; 1434 erase_node = b()->node_list.load(std::memory_order_relaxed); 1435 while (this->is_valid(erase_node) && !my_hash_compare.equal(key, static_cast<node*>(erase_node)->value().first ) ) { 1436 prev = erase_node; 1437 erase_node = erase_node->next; 1438 } 1439 1440 if (erase_node == nullptr) { // not found, but mask could be changed 1441 if (this->check_mask_race(hash, mask)) 1442 goto restart; 1443 return false; 1444 } else if (!b.is_writer() && !b.upgrade_to_writer()) { 1445 if (this->check_mask_race(hash, mask)) // contended upgrade, check mask 1446 goto restart; 1447 goto search; 1448 } 1449 1450 // remove from container 1451 if (prev == nullptr) { 1452 b()->node_list.store(erase_node->next, std::memory_order_relaxed); 1453 } else { 1454 prev->next = erase_node->next; 1455 } 1456 this->my_size--; 1457 } 1458 { 1459 typename node::scoped_type item_locker( erase_node->mutex, /*write=*/true ); 1460 } 1461 // note: there should be no threads pretending to acquire this mutex again, do not try to upgrade const_accessor! 1462 delete_node(erase_node); // Only one thread can delete it due to write lock on the bucket 1463 return true; 1464 } 1465 1466 // Returns an iterator for an item defined by the key, or for the next item after it (if upper==true) 1467 template <typename K, typename I> 1468 std::pair<I, I> internal_equal_range( const K& key, I end_ ) const { 1469 hashcode_type h = my_hash_compare.hash( key ); 1470 hashcode_type m = this->my_mask.load(std::memory_order_relaxed); 1471 __TBB_ASSERT((m&(m+1))==0, "data structure is invalid"); 1472 h &= m; 1473 bucket *b = this->get_bucket( h ); 1474 while (rehash_required(b->node_list.load(std::memory_order_relaxed))) { 1475 m = ( hashcode_type(1) << tbb::detail::log2( h ) ) - 1; // get parent mask from the topmost bit 1476 b = this->get_bucket( h &= m ); 1477 } 1478 node *n = search_bucket( key, b ); 1479 if( !n ) 1480 return std::make_pair(end_, end_); 1481 iterator lower(*this, h, b, n), upper(lower); 1482 return std::make_pair(lower, ++upper); 1483 } 1484 1485 // Copy "source" to *this, where *this must start out empty. 1486 void internal_copy( const concurrent_hash_map& source ) { 1487 hashcode_type mask = source.my_mask.load(std::memory_order_relaxed); 1488 if( this->my_mask.load(std::memory_order_relaxed) == mask ) { // optimized version 1489 this->reserve(source.my_size.load(std::memory_order_relaxed)); // TODO: load_factor? 1490 bucket *dst = nullptr, *src = nullptr; 1491 bool rehashing_required = false; 1492 for( hashcode_type k = 0; k <= mask; k++ ) { 1493 if( k & (k-2) ) ++dst,src++; // not the beginning of a segment 1494 else { dst = this->get_bucket( k ); src = source.get_bucket( k ); } 1495 __TBB_ASSERT(!rehash_required(dst->node_list.load(std::memory_order_relaxed)), "Invalid bucket in destination table"); 1496 node *n = static_cast<node*>( src->node_list.load(std::memory_order_relaxed) ); 1497 if (rehash_required(n)) { // source is not rehashed, items are in previous buckets 1498 rehashing_required = true; 1499 dst->node_list.store(reinterpret_cast<node_base*>(rehash_req_flag), std::memory_order_relaxed); 1500 } else for(; n; n = static_cast<node*>( n->next ) ) { 1501 node* node_ptr = create_node(base_type::get_allocator(), n->value().first, n->value().second); 1502 this->add_to_bucket( dst, node_ptr); 1503 this->my_size.fetch_add(1, std::memory_order_relaxed); 1504 } 1505 } 1506 if( rehashing_required ) rehash(); 1507 } else internal_copy(source.begin(), source.end(), source.my_size.load(std::memory_order_relaxed)); 1508 } 1509 1510 template <typename I> 1511 void internal_copy( I first, I last, size_type reserve_size ) { 1512 this->reserve(reserve_size); // TODO: load_factor? 1513 hashcode_type m = this->my_mask.load(std::memory_order_relaxed); 1514 for(; first != last; ++first) { 1515 hashcode_type h = my_hash_compare.hash( (*first).first ); 1516 bucket *b = this->get_bucket( h & m ); 1517 __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), "Invalid bucket in destination table"); 1518 node* node_ptr = create_node(base_type::get_allocator(), (*first).first, (*first).second); 1519 this->add_to_bucket( b, node_ptr ); 1520 ++this->my_size; // TODO: replace by non-atomic op 1521 } 1522 } 1523 1524 void internal_move_construct_with_allocator( concurrent_hash_map&& other, const allocator_type&, 1525 /*is_always_equal=*/std::true_type ) 1526 { 1527 this->internal_move(std::move(other)); 1528 } 1529 1530 void internal_move_construct_with_allocator( concurrent_hash_map&& other, const allocator_type& a, 1531 /*is_always_equal=*/std::false_type ) 1532 { 1533 if (a == other.get_allocator()){ 1534 this->internal_move(std::move(other)); 1535 } else { 1536 try_call( [&] { 1537 internal_copy(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()), 1538 other.size()); 1539 }).on_exception( [&] { 1540 this->clear(); 1541 }); 1542 } 1543 } 1544 1545 void internal_move_assign( concurrent_hash_map&& other, 1546 /*is_always_equal || POCMA = */std::true_type) 1547 { 1548 this->internal_move(std::move(other)); 1549 } 1550 1551 void internal_move_assign(concurrent_hash_map&& other, /*is_always_equal=*/ std::false_type) { 1552 if (this->my_allocator == other.my_allocator) { 1553 this->internal_move(std::move(other)); 1554 } else { 1555 //do per element move 1556 internal_copy(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()), 1557 other.size()); 1558 } 1559 } 1560 1561 void internal_swap(concurrent_hash_map& other, /*is_always_equal || POCS = */ std::true_type) { 1562 this->internal_swap_content(other); 1563 } 1564 1565 void internal_swap(concurrent_hash_map& other, /*is_always_equal || POCS = */ std::false_type) { 1566 __TBB_ASSERT(this->my_allocator == other.my_allocator, nullptr); 1567 this->internal_swap_content(other); 1568 } 1569 1570 // Fast find when no concurrent erasure is used. For internal use inside TBB only! 1571 /** Return pointer to item with given key, or nullptr if no such item exists. 1572 Must not be called concurrently with erasure operations. */ 1573 const_pointer internal_fast_find( const Key& key ) const { 1574 hashcode_type h = my_hash_compare.hash( key ); 1575 hashcode_type m = this->my_mask.load(std::memory_order_acquire); 1576 node *n; 1577 restart: 1578 __TBB_ASSERT((m&(m+1))==0, "data structure is invalid"); 1579 bucket *b = this->get_bucket( h & m ); 1580 // TODO: actually, notification is unnecessary here, just hiding double-check 1581 if (rehash_required(b->node_list.load(std::memory_order_acquire))) 1582 { 1583 typename bucket::scoped_type lock; 1584 if( lock.try_acquire( b->mutex, /*write=*/true ) ) { 1585 if (rehash_required(b->node_list.load(std::memory_order_relaxed))) 1586 const_cast<concurrent_hash_map*>(this)->rehash_bucket( b, h & m ); //recursive rehashing 1587 } 1588 else lock.acquire( b->mutex, /*write=*/false ); 1589 __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), nullptr); 1590 } 1591 n = search_bucket( key, b ); 1592 if( n ) 1593 return n->storage(); 1594 else if( this->check_mask_race( h, m ) ) 1595 goto restart; 1596 return nullptr; 1597 } 1598 }; 1599 1600 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1601 template <typename It, 1602 typename HashCompare = d1::tbb_hash_compare<iterator_key_t<It>>, 1603 typename Alloc = tbb_allocator<iterator_alloc_pair_t<It>>, 1604 typename = std::enable_if_t<is_input_iterator_v<It>>, 1605 typename = std::enable_if_t<is_allocator_v<Alloc>>, 1606 typename = std::enable_if_t<!is_allocator_v<HashCompare>>> 1607 concurrent_hash_map( It, It, HashCompare = HashCompare(), Alloc = Alloc() ) 1608 -> concurrent_hash_map<iterator_key_t<It>, iterator_mapped_t<It>, HashCompare, Alloc>; 1609 1610 template <typename It, typename Alloc, 1611 typename = std::enable_if_t<is_input_iterator_v<It>>, 1612 typename = std::enable_if_t<is_allocator_v<Alloc>>> 1613 concurrent_hash_map( It, It, Alloc ) 1614 -> concurrent_hash_map<iterator_key_t<It>, iterator_mapped_t<It>, d1::tbb_hash_compare<iterator_key_t<It>>, Alloc>; 1615 1616 template <typename Key, typename T, 1617 typename HashCompare = d1::tbb_hash_compare<std::remove_const_t<Key>>, 1618 typename Alloc = tbb_allocator<std::pair<const Key, T>>, 1619 typename = std::enable_if_t<is_allocator_v<Alloc>>, 1620 typename = std::enable_if_t<!is_allocator_v<HashCompare>>> 1621 concurrent_hash_map( std::initializer_list<std::pair<Key, T>>, HashCompare = HashCompare(), Alloc = Alloc() ) 1622 -> concurrent_hash_map<std::remove_const_t<Key>, T, HashCompare, Alloc>; 1623 1624 template <typename Key, typename T, typename Alloc, 1625 typename = std::enable_if_t<is_allocator_v<Alloc>>> 1626 concurrent_hash_map( std::initializer_list<std::pair<Key, T>>, Alloc ) 1627 -> concurrent_hash_map<std::remove_const_t<Key>, T, d1::tbb_hash_compare<std::remove_const_t<Key>>, Alloc>; 1628 1629 #endif /* __TBB_CPP17_DEDUCTION_GUIDES_PRESENT */ 1630 1631 template <typename Key, typename T, typename HashCompare, typename A1, typename A2> 1632 inline bool operator==(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b) { 1633 if(a.size() != b.size()) return false; 1634 typename concurrent_hash_map<Key, T, HashCompare, A1>::const_iterator i(a.begin()), i_end(a.end()); 1635 typename concurrent_hash_map<Key, T, HashCompare, A2>::const_iterator j, j_end(b.end()); 1636 for(; i != i_end; ++i) { 1637 j = b.equal_range(i->first).first; 1638 if( j == j_end || !(i->second == j->second) ) return false; 1639 } 1640 return true; 1641 } 1642 1643 #if !__TBB_CPP20_COMPARISONS_PRESENT 1644 template <typename Key, typename T, typename HashCompare, typename A1, typename A2> 1645 inline bool operator!=(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b) 1646 { return !(a == b); } 1647 #endif // !__TBB_CPP20_COMPARISONS_PRESENT 1648 1649 template <typename Key, typename T, typename HashCompare, typename A> 1650 inline void swap(concurrent_hash_map<Key, T, HashCompare, A> &a, concurrent_hash_map<Key, T, HashCompare, A> &b) 1651 { a.swap( b ); } 1652 1653 } // namespace d2 1654 } // namespace detail 1655 1656 inline namespace v1 { 1657 using detail::split; 1658 using detail::d2::concurrent_hash_map; 1659 using detail::d1::tbb_hash_compare; 1660 } // namespace v1 1661 1662 } // namespace tbb 1663 1664 #endif /* __TBB_concurrent_hash_map_H */ 1665