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