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 #include <common/test.h> 18 #include <common/utils.h> 19 #include "common/utils_report.h" 20 #include <common/spin_barrier.h> 21 #include <common/state_trackable.h> 22 #include <common/container_move_support.h> 23 #include <common/containers_common.h> 24 #include <common/initializer_list_support.h> 25 #include <common/vector_types.h> 26 #include <common/test_comparisons.h> 27 #include "oneapi/tbb/concurrent_hash_map.h" 28 #include "oneapi/tbb/global_control.h" 29 #include "oneapi/tbb/parallel_for.h" 30 31 //! \file conformance_concurrent_hash_map.cpp 32 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification 33 34 /** Has tightly controlled interface so that we can verify 35 that concurrent_hash_map uses only the required interface. */ 36 class MyException : public std::bad_alloc { 37 public: 38 virtual const char *what() const throw() override { return "out of items limit"; } 39 virtual ~MyException() throw() {} 40 }; 41 42 /** Has tightly controlled interface so that we can verify 43 that concurrent_hash_map uses only the required interface. */ 44 class MyKey { 45 private: 46 int key; 47 friend class MyHashCompare; 48 friend class YourHashCompare; 49 public: 50 MyKey() = default; 51 MyKey( const MyKey& ) = default; 52 void operator=( const MyKey& ) = delete; 53 static MyKey make( int i ) { 54 MyKey result; 55 result.key = i; 56 return result; 57 } 58 59 int value_of() const { return key; } 60 }; 61 62 std::atomic<long> MyDataCount; 63 long MyDataCountLimit = 0; 64 65 class MyData { 66 protected: 67 friend class MyData2; 68 int data; 69 enum state_t { 70 LIVE=0x1234, 71 DEAD=0x5678 72 } my_state; 73 void operator=( const MyData& ); // Deny access 74 public: 75 MyData(int i = 0) { 76 my_state = LIVE; 77 data = i; 78 if (MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) { 79 TBB_TEST_THROW(MyException{}); 80 } 81 ++MyDataCount; 82 } 83 84 MyData( const MyData& other ) { 85 CHECK_FAST(other.my_state==LIVE); 86 my_state = LIVE; 87 data = other.data; 88 if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) { 89 TBB_TEST_THROW(MyException{}); 90 } 91 ++MyDataCount; 92 } 93 94 ~MyData() { 95 --MyDataCount; 96 my_state = DEAD; 97 } 98 99 static MyData make( int i ) { 100 MyData result; 101 result.data = i; 102 return result; 103 } 104 105 int value_of() const { 106 CHECK_FAST(my_state==LIVE); 107 return data; 108 } 109 110 void set_value( int i ) { 111 CHECK_FAST(my_state==LIVE); 112 data = i; 113 } 114 115 bool operator==( const MyData& other ) const { 116 CHECK_FAST(other.my_state==LIVE); 117 CHECK_FAST(my_state==LIVE); 118 return data == other.data; 119 } 120 }; 121 122 class MyData2 : public MyData { 123 public: 124 MyData2( ) {} 125 126 MyData2( const MyData2& other ) : MyData() { 127 CHECK_FAST(other.my_state==LIVE); 128 CHECK_FAST(my_state==LIVE); 129 data = other.data; 130 } 131 132 MyData2( const MyData& other ) { 133 CHECK_FAST(other.my_state==LIVE); 134 CHECK_FAST(my_state==LIVE); 135 data = other.data; 136 } 137 138 void operator=( const MyData& other ) { 139 CHECK_FAST(other.my_state==LIVE); 140 CHECK_FAST(my_state==LIVE); 141 data = other.data; 142 } 143 144 void operator=( const MyData2& other ) { 145 CHECK_FAST(other.my_state==LIVE); 146 CHECK_FAST(my_state==LIVE); 147 data = other.data; 148 } 149 150 bool operator==( const MyData2& other ) const { 151 CHECK_FAST(other.my_state==LIVE); 152 CHECK_FAST(my_state==LIVE); 153 return data == other.data; 154 } 155 }; 156 157 class MyHashCompare { 158 public: 159 bool equal( const MyKey& j, const MyKey& k ) const { 160 return j.key==k.key; 161 } 162 163 std::size_t hash( const MyKey& k ) const { 164 return k.key; 165 } 166 }; 167 168 class YourHashCompare { 169 public: 170 bool equal( const MyKey& j, const MyKey& k ) const { 171 return j.key==k.key; 172 } 173 174 std::size_t hash( const MyKey& ) const { 175 return 1; 176 } 177 }; 178 179 using test_allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData>>>; 180 using test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData, MyHashCompare, test_allocator_type>; 181 using other_test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare>; 182 183 template <template <typename...> class ContainerType> 184 void test_member_types() { 185 using container_type = ContainerType<int, int>; 186 static_assert(std::is_same<typename container_type::allocator_type, oneapi::tbb::tbb_allocator<std::pair<const int, int>>>::value, 187 "Incorrect default template allocator"); 188 189 static_assert(std::is_same<typename container_type::key_type, int>::value, 190 "Incorrect container key_type member type"); 191 static_assert(std::is_same<typename container_type::value_type, std::pair<const int, int>>::value, 192 "Incorrect container value_type member type"); 193 194 static_assert(std::is_unsigned<typename container_type::size_type>::value, 195 "Incorrect container size_type member type"); 196 static_assert(std::is_signed<typename container_type::difference_type>::value, 197 "Incorrect container difference_type member type"); 198 199 using value_type = typename container_type::value_type; 200 static_assert(std::is_same<typename container_type::reference, value_type&>::value, 201 "Incorrect container reference member type"); 202 static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value, 203 "Incorrect container const_reference member type"); 204 using allocator_type = typename container_type::allocator_type; 205 static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value, 206 "Incorrect container pointer member type"); 207 static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value, 208 "Incorrect container const_pointer member type"); 209 210 static_assert(utils::is_forward_iterator<typename container_type::iterator>::value, 211 "Incorrect container iterator member type"); 212 static_assert(!std::is_const<typename container_type::iterator::value_type>::value, 213 "Incorrect container iterator member type"); 214 static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value, 215 "Incorrect container const_iterator member type"); 216 static_assert(std::is_const<typename container_type::const_iterator::value_type>::value, 217 "Incorrect container iterator member type"); 218 } 219 220 template<typename test_table_type> 221 void FillTable( test_table_type& x, int n ) { 222 for( int i=1; i<=n; ++i ) { 223 MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order 224 typename test_table_type::accessor a; 225 bool b = x.insert(a,key); 226 CHECK_FAST(b); 227 a->second.set_value( i*i ); 228 } 229 } 230 231 template<typename test_table_type> 232 static void CheckTable( const test_table_type& x, int n ) { 233 REQUIRE_MESSAGE( x.size()==size_t(n), "table is different size than expected" ); 234 CHECK(x.empty()==(n==0)); 235 CHECK(x.size()<=x.max_size()); 236 for( int i=1; i<=n; ++i ) { 237 MyKey key( MyKey::make(-i) ); 238 typename test_table_type::const_accessor a; 239 bool b = x.find(a,key); 240 CHECK_FAST(b); 241 CHECK_FAST(a->second.value_of()==i*i); 242 } 243 int count = 0; 244 int key_sum = 0; 245 for( typename test_table_type::const_iterator i(x.begin()); i!=x.end(); ++i ) { 246 ++count; 247 key_sum += -i->first.value_of(); 248 } 249 CHECK(count==n); 250 CHECK(key_sum==n*(n+1)/2); 251 } 252 253 void TestCopy() { 254 INFO("testing copy\n"); 255 test_table_type t1; 256 for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) { 257 MyDataCount = 0; 258 259 FillTable(t1,i); 260 // Do not call CheckTable(t1,i) before copying, it enforces rehashing 261 262 test_table_type t2(t1); 263 // Check that copy constructor did not mangle source table. 264 CheckTable(t1,i); 265 swap(t1, t2); 266 CheckTable(t1,i); 267 CHECK(!(t1 != t2)); 268 269 // Clear original table 270 t2.clear(); 271 swap(t2, t1); 272 CheckTable(t1,0); 273 274 // Verify that copy of t1 is correct, even after t1 is cleared. 275 CheckTable(t2,i); 276 t2.clear(); 277 t1.swap( t2 ); 278 CheckTable(t1,0); 279 CheckTable(t2,0); 280 REQUIRE_MESSAGE( MyDataCount==0, "data leak?" ); 281 } 282 } 283 284 void TestRehash() { 285 INFO("testing rehashing\n"); 286 test_table_type w; 287 w.insert( std::make_pair(MyKey::make(-5), MyData()) ); 288 w.rehash(); // without this, assertion will fail 289 test_table_type::iterator it = w.begin(); 290 int i = 0; // check for non-rehashed buckets 291 for( ; it != w.end(); i++ ) 292 w.count( (it++)->first ); 293 CHECK(i == 1); 294 for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) { 295 for( int j=std::max(256+i, i*2); j<10000; j*=3 ) { 296 test_table_type v; 297 FillTable( v, i ); 298 CHECK(int(v.size()) == i); 299 CHECK(int(v.bucket_count()) <= j); 300 v.rehash( j ); 301 CHECK(int(v.bucket_count()) >= j); 302 CheckTable( v, i ); 303 } 304 } 305 } 306 307 void TestAssignment() { 308 INFO("testing assignment\n"); 309 oneapi::tbb::concurrent_hash_map<int, int> test_map({{1, 2}, {2, 4}}); 310 test_map.operator=(test_map); // suppress self assign warning 311 CHECK(!test_map.empty()); 312 313 for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) { 314 for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) { 315 test_table_type t1; 316 test_table_type t2; 317 FillTable(t1,i); 318 FillTable(t2,j); 319 CHECK((t1 == t2) == (i == j)); 320 CheckTable(t2,j); 321 322 test_table_type& tref = t2=t1; 323 CHECK(&tref==&t2); 324 CHECK(t1 == t2); 325 CheckTable(t1,i); 326 CheckTable(t2,i); 327 328 t1.clear(); 329 CheckTable(t1,0); 330 CheckTable(t2,i); 331 REQUIRE_MESSAGE( MyDataCount==i, "data leak?" ); 332 333 t2.clear(); 334 CheckTable(t1,0); 335 CheckTable(t2,0); 336 REQUIRE_MESSAGE( MyDataCount==0, "data leak?" ); 337 } 338 } 339 } 340 341 template<typename Iterator, typename T> 342 void TestIteratorTraits() { 343 T x; 344 typename Iterator::reference xr = x; 345 typename Iterator::pointer xp = &x; 346 CHECK(&xr==xp); 347 } 348 349 template<typename Iterator1, typename Iterator2> 350 void TestIteratorAssignment( Iterator2 j ) { 351 Iterator1 i(j), k; 352 CHECK(i==j); 353 CHECK(!(i!=j)); 354 k = j; 355 CHECK(k==j); 356 CHECK(!(k!=j)); 357 } 358 359 template<typename Range1, typename Range2> 360 void TestRangeAssignment( Range2 r2 ) { 361 Range1 r1(r2); r1 = r2; 362 } 363 364 void TestIteratorsAndRanges() { 365 INFO("testing iterators compliance\n"); 366 TestIteratorTraits<test_table_type::iterator,test_table_type::value_type>(); 367 TestIteratorTraits<test_table_type::const_iterator,const test_table_type::value_type>(); 368 369 test_table_type v; 370 CHECK(v.range().grainsize() == 1); 371 test_table_type const &u = v; 372 373 TestIteratorAssignment<test_table_type::const_iterator>( u.begin() ); 374 TestIteratorAssignment<test_table_type::const_iterator>( v.begin() ); 375 TestIteratorAssignment<test_table_type::iterator>( v.begin() ); 376 // doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() ); 377 378 // check for non-existing 379 CHECK(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end())); 380 CHECK(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end())); 381 382 INFO("testing ranges compliance\n"); 383 TestRangeAssignment<test_table_type::const_range_type>( u.range() ); 384 TestRangeAssignment<test_table_type::range_type>( v.range() ); 385 // doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() ); 386 387 INFO("testing construction and insertion from iterators range\n"); 388 FillTable( v, 1000 ); 389 other_test_table_type t(v.begin(), v.end()); 390 v.rehash(); 391 CheckTable(t, 1000); 392 t.insert(v.begin(), v.end()); // do nothing 393 CheckTable(t, 1000); 394 t.clear(); 395 t.insert(v.begin(), v.end()); // restore 396 CheckTable(t, 1000); 397 398 INFO("testing comparison\n"); 399 using test_allocator_type2 = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData2>>>; 400 using YourTable1 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare, test_allocator_type2>; 401 using YourTable2 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare>; 402 YourTable1 t1; 403 FillTable( t1, 10 ); 404 CheckTable(t1, 10 ); 405 YourTable2 t2(t1.begin(), t1.end()); 406 MyKey key( MyKey::make(-5) ); MyData2 data; 407 CHECK(t2.erase(key)); 408 YourTable2::accessor a; 409 CHECK(t2.insert(a, key)); 410 data.set_value(0); a->second = data; 411 CHECK(t1 != t2); 412 data.set_value(5*5); a->second = data; 413 CHECK(t1 == t2); 414 } 415 416 struct test_insert { 417 template<typename container_type, typename element_type> 418 static void test( std::initializer_list<element_type> il, container_type const& expected ) { 419 container_type vd; 420 vd.insert( il ); 421 REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" ); 422 } 423 }; 424 425 struct ctor_test { 426 template<typename container_type, typename element_type> 427 static void test( std::initializer_list<element_type> il, container_type const& expected ) { 428 container_type vd(il, tbb::tbb_allocator<std::pair<element_type, element_type>>{}); 429 REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" ); 430 } 431 }; 432 433 void TestInitList(){ 434 using namespace initializer_list_support_tests; 435 INFO("testing initializer_list methods \n"); 436 437 using ch_map_type = oneapi::tbb::concurrent_hash_map<int,int>; 438 std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}}; 439 440 test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il ); 441 test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} ); 442 test_initializer_list_support_without_assign<ch_map_type, ctor_test>(pairs_il); 443 } 444 445 template <typename base_alloc_type> 446 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> { 447 using base_type = StaticSharedCountingAllocator<base_alloc_type>; 448 using base_traits = oneapi::tbb::detail::allocator_traits<base_alloc_type>; 449 public: 450 template<typename U> 451 struct rebind { 452 using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>; 453 }; 454 455 only_node_counting_allocator() : base_type() {} 456 only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {} 457 458 template<typename U> 459 only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {} 460 461 typename base_type::value_type* allocate(const std::size_t n) { 462 if ( n > 1) { 463 return base_alloc_type::allocate(n); 464 } else { 465 return base_type::allocate(n); 466 } 467 } 468 }; 469 470 #if TBB_USE_EXCEPTIONS 471 void TestExceptions() { 472 using allocator_type = only_node_counting_allocator<oneapi::tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>; 473 using throwing_table = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>; 474 enum methods { 475 zero_method = 0, 476 ctor_copy, op_assign, op_insert, 477 all_methods 478 }; 479 480 INFO("testing exception-safety guarantees\n"); 481 throwing_table src; 482 FillTable( src, 1000 ); 483 CHECK(MyDataCount==1000); 484 485 try { 486 for(int t = 0; t < 2; t++) // exception type 487 for(int m = zero_method+1; m < all_methods; m++) 488 { 489 allocator_type a; 490 allocator_type::init_counters(); 491 if(t) MyDataCountLimit = 101; 492 else a.set_limits(101); 493 throwing_table victim(a); 494 MyDataCount = 0; 495 496 try { 497 switch(m) { 498 case ctor_copy: { 499 throwing_table acopy(src, a); 500 } break; 501 case op_assign: { 502 victim = src; 503 } break; 504 case op_insert: { 505 // Insertion in cpp11 don't make copy constructions 506 // during the insertion, so we need to decrement limit 507 // to throw an exception in the right place and to prevent 508 // successful insertion of one unexpected item 509 if (MyDataCountLimit) 510 --MyDataCountLimit; 511 FillTable( victim, 1000 ); 512 } break; 513 default:; 514 } 515 REQUIRE_MESSAGE(false, "should throw an exception"); 516 } catch(std::bad_alloc &e) { 517 MyDataCountLimit = 0; 518 size_t size = victim.size(); 519 switch(m) { 520 case op_assign: 521 REQUIRE_MESSAGE( MyDataCount==100, "data leak?" ); 522 CHECK(size>=100); 523 utils_fallthrough; 524 case ctor_copy: 525 CheckTable(src, 1000); 526 break; 527 case op_insert: 528 CHECK(size==size_t(100-t)); 529 REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" ); 530 CheckTable(victim, 100-t); 531 break; 532 533 default:; // nothing to check here 534 } 535 INFO("Exception "<< m << " : " << e.what() << "- ok ()"); 536 } 537 catch ( ... ) { 538 REQUIRE_MESSAGE( false, "Unrecognized exception" ); 539 } 540 } 541 } catch(...) { 542 REQUIRE_MESSAGE(false, "unexpected exception"); 543 } 544 src.clear(); MyDataCount = 0; 545 allocator_type::max_items = 0; 546 } 547 #endif 548 549 struct default_container_traits { 550 template <typename container_type, typename iterator_type> 551 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){ 552 container_type* ptr = reinterpret_cast<container_type*>(&storage); 553 new (ptr) container_type(begin, end); 554 return *ptr; 555 } 556 557 template <typename container_type, typename iterator_type, typename allocator_type> 558 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){ 559 container_type* ptr = reinterpret_cast<container_type*>(&storage); 560 new (ptr) container_type(begin, end, a); 561 return *ptr; 562 } 563 }; 564 565 struct hash_map_traits : default_container_traits { 566 enum{ expected_number_of_items_to_allocate_for_steal_move = 0 }; 567 568 template<typename T> 569 struct hash_compare { 570 bool equal( const T& lhs, const T& rhs ) const { 571 return lhs==rhs; 572 } 573 size_t hash( const T& k ) const { 574 return my_hash_func(k); 575 } 576 std::hash<T> my_hash_func; 577 }; 578 579 template <typename T, typename Allocator> 580 using container_type = oneapi::tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>; 581 582 template <typename T> 583 using container_value_type = std::pair<const T, T>; 584 585 template<typename element_type, typename allocator_type> 586 struct apply { 587 using type = oneapi::tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>; 588 }; 589 590 using init_iterator_type = move_support_tests::FooPairIterator; 591 template <typename hash_map_type, typename iterator> 592 static bool equal(hash_map_type const& c, iterator begin, iterator end){ 593 bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() ); 594 if (!equal_sizes) 595 return false; 596 597 for (iterator it = begin; it != end; ++it ){ 598 if (c.count( (*it).first) == 0){ 599 return false; 600 } 601 } 602 return true; 603 } 604 }; 605 606 using DataStateTrackedTable = oneapi::tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>; 607 608 struct RvalueInsert { 609 static void apply( DataStateTrackedTable& table, int i ) { 610 DataStateTrackedTable::accessor a; 611 int next = i + 1; 612 CHECK_FAST_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))), 613 "already present while should not ?" ); 614 CHECK_FAST((*a).second == next); 615 CHECK_FAST((*a).second.state == StateTrackableBase::MoveInitialized); 616 } 617 }; 618 619 struct Emplace { 620 template <typename Accessor> 621 static void apply_impl( DataStateTrackedTable& table, int i) { 622 Accessor a; 623 CHECK_FAST_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))), 624 "already present while should not ?" ); 625 CHECK_FAST((*a).second == i + 1); 626 CHECK_FAST((*a).second.state == StateTrackableBase::DirectInitialized); 627 } 628 629 static void apply( DataStateTrackedTable& table, int i ) { 630 // TODO: investigate ability to rewrite apply methods with use apply_imp method. 631 if (i % 2) { 632 apply_impl<DataStateTrackedTable::accessor>(table, i); 633 } else { 634 apply_impl<DataStateTrackedTable::const_accessor>(table, i); 635 } 636 } 637 }; 638 639 template<typename Op, typename test_table_type> 640 class TableOperation { 641 test_table_type& my_table; 642 public: 643 void operator()( const oneapi::tbb::blocked_range<int>& range ) const { 644 for( int i=range.begin(); i!=range.end(); ++i ) 645 Op::apply(my_table,i); 646 } 647 TableOperation( test_table_type& table ) : my_table(table) {} 648 }; 649 650 bool UseKey( size_t i ) { 651 return (i&3)!=3; 652 } 653 654 struct Insert { 655 static void apply( test_table_type& table, int i ) { 656 if( UseKey(i) ) { 657 if( i&4 ) { 658 test_table_type::accessor a; 659 table.insert( a, MyKey::make(i) ); 660 if( i&1 ) 661 (*a).second.set_value(i*i); 662 else 663 a->second.set_value(i*i); 664 } else 665 if( i&1 ) { 666 test_table_type::accessor a; 667 table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) ); 668 CHECK_FAST((*a).second.value_of()==i*i); 669 } else { 670 test_table_type::const_accessor ca; 671 table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) ); 672 CHECK_FAST(ca->second.value_of()==i*i); 673 } 674 } 675 } 676 }; 677 678 struct Find { 679 static void apply( test_table_type& table, int i ) { 680 test_table_type::accessor a; 681 const test_table_type::accessor& ca = a; 682 bool b = table.find( a, MyKey::make(i) ); 683 CHECK_FAST(b==!a.empty()); 684 if( b ) { 685 if( !UseKey(i) ) 686 REPORT("Line %d: unexpected key %d present\n",__LINE__,i); 687 CHECK_FAST(ca->second.value_of()==i*i); 688 CHECK_FAST((*ca).second.value_of()==i*i); 689 if( i&1 ) 690 ca->second.set_value( ~ca->second.value_of() ); 691 else 692 (*ca).second.set_value( ~ca->second.value_of() ); 693 } else { 694 if( UseKey(i) ) 695 REPORT("Line %d: key %d missing\n",__LINE__,i); 696 } 697 } 698 }; 699 700 struct FindConst { 701 static void apply( const test_table_type& table, int i ) { 702 test_table_type::const_accessor a; 703 const test_table_type::const_accessor& ca = a; 704 bool b = table.find( a, MyKey::make(i) ); 705 CHECK_FAST(b==(table.count(MyKey::make(i))>0)); 706 CHECK_FAST(b==!a.empty()); 707 CHECK_FAST(b==UseKey(i)); 708 if( b ) { 709 CHECK_FAST(ca->second.value_of()==~(i*i)); 710 CHECK_FAST((*ca).second.value_of()==~(i*i)); 711 } 712 } 713 }; 714 715 struct InsertInitList { 716 static void apply( test_table_type& table, int i ) { 717 if ( UseKey( i ) ) { 718 // TODO: investigate why the following sequence causes an additional allocation sometimes: 719 // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) ); 720 // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) ); 721 std::initializer_list<test_table_type::value_type> il = { 722 test_table_type::value_type( MyKey::make( i ), i*i ) 723 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */ 724 }; 725 table.insert( il ); 726 } 727 } 728 }; 729 730 template<typename Op, typename TableType> 731 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) { 732 INFO("testing " << what << " with " << nthread << " threads"); 733 oneapi::tbb::parallel_for(oneapi::tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table)); 734 } 735 736 //! Test traversing the table with an iterator. 737 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) { 738 INFO("testing traversal\n"); 739 size_t actual_size = table.size(); 740 CHECK(actual_size==expected_size); 741 size_t count = 0; 742 bool* array = new bool[n]; 743 memset( array, 0, n*sizeof(bool) ); 744 const test_table_type& const_table = table; 745 test_table_type::const_iterator ci = const_table.begin(); 746 for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) { 747 // Check iterator 748 int k = i->first.value_of(); 749 CHECK_FAST(UseKey(k)); 750 CHECK_FAST((*i).first.value_of()==k); 751 CHECK_FAST_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" ); 752 CHECK_FAST_MESSAGE( !array[k], "duplicate key" ); 753 array[k] = true; 754 ++count; 755 756 // Check lower/upper bounds 757 std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first); 758 std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first); 759 CHECK_FAST((cer.first == er.first && cer.second == er.second)); 760 CHECK_FAST(cer.first == i); 761 CHECK_FAST(std::distance(cer.first, cer.second) == 1); 762 763 // Check const_iterator 764 test_table_type::const_iterator cic = ci++; 765 CHECK_FAST(cic->first.value_of()==k); 766 CHECK_FAST((*cic).first.value_of()==k); 767 } 768 CHECK(ci==const_table.end()); 769 delete[] array; 770 if (count != expected_size) { 771 INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size); 772 } 773 } 774 775 std::atomic<int> EraseCount; 776 777 struct Erase { 778 static void apply( test_table_type& table, int i ) { 779 bool b; 780 if(i&4) { 781 if(i&8) { 782 test_table_type::const_accessor a; 783 b = table.find( a, MyKey::make(i) ) && table.erase( a ); 784 } else { 785 test_table_type::accessor a; 786 b = table.find( a, MyKey::make(i) ) && table.erase( a ); 787 } 788 } else 789 b = table.erase( MyKey::make(i) ); 790 if( b ) ++EraseCount; 791 CHECK_FAST(table.count(MyKey::make(i)) == 0); 792 } 793 }; 794 795 using YourTable = oneapi::tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>; 796 static const int IE_SIZE = 2; 797 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE]; 798 799 struct InsertErase { 800 static void apply( YourTable& table, int i ) { 801 if ( i%3 ) { 802 int key = i%IE_SIZE; 803 if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) ) 804 ++InsertEraseCount[key]; 805 } else { 806 int key = i%IE_SIZE; 807 if( i&1 ) { 808 YourTable::accessor res; 809 if(table.find( res, MyKey::make(key) ) && table.erase( res ) ) 810 --InsertEraseCount[key]; 811 } else { 812 YourTable::const_accessor res; 813 if(table.find( res, MyKey::make(key) ) && table.erase( res ) ) 814 --InsertEraseCount[key]; 815 } 816 } 817 } 818 }; 819 820 struct InnerInsert { 821 static void apply( YourTable& table, int i ) { 822 YourTable::accessor a1, a2; 823 if(i&1) utils::yield(); 824 table.insert( a1, MyKey::make(1) ); 825 utils::yield(); 826 table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain 827 table.erase( a2 ); // if erase by key it would lead to deadlock for single thread 828 } 829 }; 830 831 struct FakeExclusive { 832 utils::SpinBarrier& barrier; 833 YourTable& table; 834 FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {} 835 void operator()( std::size_t i ) const { 836 if(i) { 837 YourTable::const_accessor real_ca; 838 // const accessor on non-const table acquired as reader (shared) 839 CHECK(table.find(real_ca,MyKey::make(1))); 840 barrier.wait(); // item can be erased 841 std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase 842 real_ca->second.value_of(); // check the state while holding accessor 843 } else { 844 YourTable::accessor fake_ca; 845 const YourTable &const_table = table; 846 // non-const accessor on const table acquired as reader (shared) 847 CHECK(const_table.find(fake_ca,MyKey::make(1))); 848 barrier.wait(); // readers acquired 849 // can mistakenly remove the item while other readers still refers to it 850 table.erase( fake_ca ); 851 } 852 } 853 }; 854 855 using AtomicByte = std::atomic<unsigned char>; 856 857 template<typename RangeType> 858 struct ParallelTraverseBody { 859 const size_t n; 860 AtomicByte* const array; 861 ParallelTraverseBody( AtomicByte array_[], size_t n_ ) : 862 n(n_), 863 array(array_) 864 {} 865 void operator()( const RangeType& range ) const { 866 for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) { 867 int k = i->first.value_of(); 868 CHECK_FAST((0<=k && size_t(k)<n)); 869 ++array[k]; 870 } 871 } 872 }; 873 874 void Check( AtomicByte array[], size_t n, size_t expected_size ) { 875 if( expected_size ) 876 for( size_t k=0; k<n; ++k ) { 877 if( array[k] != int(UseKey(k)) ) { 878 REPORT("array[%d]=%d != %d=UseKey(%d)\n", 879 int(k), int(array[k]), int(UseKey(k)), int(k)); 880 CHECK(false); 881 } 882 } 883 } 884 885 //! Test traversing the table with a parallel range 886 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) { 887 INFO("testing parallel traversal\n"); 888 CHECK(table.size()==expected_size); 889 AtomicByte* array = new AtomicByte[n]; 890 891 memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) ); 892 test_table_type::range_type r = table.range(10); 893 oneapi::tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n )); 894 Check( array, n, expected_size ); 895 896 const test_table_type& const_table = table; 897 memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) ); 898 test_table_type::const_range_type cr = const_table.range(10); 899 oneapi::tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n )); 900 Check( array, n, expected_size ); 901 902 delete[] array; 903 } 904 905 void TestInsertFindErase( std::size_t nthread ) { 906 int n=250000; 907 908 // compute m = number of unique keys 909 int m = 0; 910 for( int i=0; i<n; ++i ) 911 m += UseKey(i); 912 { 913 test_allocator_type alloc; 914 test_allocator_type::init_counters(); 915 CHECK(MyDataCount==0); 916 test_table_type table(alloc); 917 TraverseTable(table,n,0); 918 ParallelTraverseTable(table,n,0); 919 920 for ( int i = 0; i < 2; ++i ) { 921 if ( i==0 ) 922 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread ); 923 else 924 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread ); 925 CHECK(MyDataCount == m); 926 TraverseTable( table, n, m ); 927 ParallelTraverseTable( table, n, m ); 928 929 DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread ); 930 CHECK(MyDataCount == m); 931 932 DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread ); 933 CHECK(MyDataCount == m); 934 935 EraseCount = 0; 936 DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread ); 937 CHECK(EraseCount == m); 938 CHECK(MyDataCount == 0); 939 TraverseTable( table, n, 0 ); 940 941 table.clear(); 942 } 943 944 if( nthread > 1 ) { 945 YourTable ie_table; 946 for( int i=0; i<IE_SIZE; ++i ) 947 InsertEraseCount[i] = 0; 948 DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread); 949 for( int i=0; i<IE_SIZE; ++i ) 950 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i))); 951 952 DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread); 953 utils::SpinBarrier barrier(nthread); 954 INFO("testing erase on fake exclusive accessor\n"); 955 utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table)); 956 } 957 } 958 REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed ); 959 REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed ); 960 REQUIRE( test_allocator_type::allocations == test_allocator_type::frees ); 961 } 962 963 std::atomic<int> Counter; 964 965 class AddToTable { 966 test_table_type& my_table; 967 const std::size_t my_nthread; 968 const int my_m; 969 public: 970 AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {} 971 void operator()( std::size_t ) const { 972 for( int i=0; i<my_m; ++i ) { 973 // Busy wait to synchronize threads 974 int j = 0; 975 while( Counter<i ) { 976 if( ++j==1000000 ) { 977 // If Counter<i after a million iterations, then we almost surely have 978 // more logical threads than physical threads, and should yield in 979 // order to let suspended logical threads make progress. 980 j = 0; 981 utils::yield(); 982 } 983 } 984 // Now all threads attempt to simultaneously insert a key. 985 int k; 986 { 987 test_table_type::accessor a; 988 MyKey key = MyKey::make(i); 989 if( my_table.insert( a, key ) ) 990 a->second.set_value( 1 ); 991 else 992 a->second.set_value( a->second.value_of()+1 ); 993 k = a->second.value_of(); 994 } 995 if( std::size_t(k) == my_nthread ) 996 Counter=i+1; 997 } 998 } 999 }; 1000 1001 class RemoveFromTable { 1002 test_table_type& my_table; 1003 const int my_m; 1004 public: 1005 RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {} 1006 void operator()(std::size_t) const { 1007 for( int i=0; i<my_m; ++i ) { 1008 bool b; 1009 if(i&4) { 1010 if(i&8) { 1011 test_table_type::const_accessor a; 1012 b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a ); 1013 } else { 1014 test_table_type::accessor a; 1015 b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a ); 1016 } 1017 } else 1018 b = my_table.erase( MyKey::make(i) ); 1019 if( b ) ++EraseCount; 1020 } 1021 } 1022 }; 1023 1024 void TestConcurrency( std::size_t nthread ) { 1025 INFO("testing multiple insertions/deletions of same key with " << nthread << " threads"); 1026 test_allocator_type::init_counters(); 1027 { 1028 CHECK( MyDataCount==0); 1029 test_table_type table; 1030 const int m = 1000; 1031 Counter = 0; 1032 oneapi::tbb::tick_count t0 = oneapi::tbb::tick_count::now(); 1033 utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) ); 1034 REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" ); 1035 1036 EraseCount = 0; 1037 t0 = oneapi::tbb::tick_count::now(); 1038 utils::NativeParallelFor( nthread, RemoveFromTable(table,m) ); 1039 REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected"); 1040 REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken"); 1041 1042 } 1043 REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed ); 1044 REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed ); 1045 REQUIRE( test_allocator_type::allocations == test_allocator_type::frees ); 1046 REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected"); 1047 } 1048 1049 template<typename Key> 1050 struct non_default_constructible_hash_compare : oneapi::tbb::detail::d1::tbb_hash_compare<Key> { 1051 non_default_constructible_hash_compare() { 1052 REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument"); 1053 } 1054 1055 non_default_constructible_hash_compare(int) {} 1056 }; 1057 1058 void TestHashCompareConstructors() { 1059 using key_type = int; 1060 using map_type = oneapi::tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>; 1061 1062 non_default_constructible_hash_compare<key_type> compare(0); 1063 map_type::allocator_type allocator; 1064 1065 map_type map1(compare); 1066 map_type map2(compare, allocator); 1067 1068 map_type map3(1, compare); 1069 map_type map4(1, compare, allocator); 1070 1071 std::vector<map_type::value_type> reference_vector; 1072 map_type map5(reference_vector.begin(), reference_vector.end(), compare); 1073 map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator); 1074 1075 map_type map7({}, compare); 1076 map_type map8({}, compare, allocator); 1077 } 1078 1079 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1080 template <typename T> 1081 struct debug_hash_compare : public oneapi::tbb::detail::d1::tbb_hash_compare<T> {}; 1082 1083 template <template <typename...> typename TMap> 1084 void TestDeductionGuides() { 1085 using Key = int; 1086 using Value = std::string; 1087 1088 using ComplexType = std::pair<Key, Value>; 1089 using ComplexTypeConst = std::pair<const Key, Value>; 1090 1091 using DefaultCompare = oneapi::tbb::detail::d1::tbb_hash_compare<Key>; 1092 using Compare = debug_hash_compare<Key>; 1093 using DefaultAllocator = oneapi::tbb::tbb_allocator<ComplexTypeConst>; 1094 using Allocator = std::allocator<ComplexTypeConst>; 1095 1096 std::vector<ComplexType> v; 1097 auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") }; 1098 Compare compare; 1099 Allocator allocator; 1100 1101 // check TMap(InputIterator, InputIterator) 1102 TMap m1(v.begin(), v.end()); 1103 static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value); 1104 1105 // check TMap(InputIterator, InputIterator, HashCompare) 1106 TMap m2(v.begin(), v.end(), compare); 1107 static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value); 1108 1109 // check TMap(InputIterator, InputIterator, HashCompare, Allocator) 1110 TMap m3(v.begin(), v.end(), compare, allocator); 1111 static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value); 1112 1113 // check TMap(InputIterator, InputIterator, Allocator) 1114 TMap m4(v.begin(), v.end(), allocator); 1115 static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value); 1116 1117 // check TMap(std::initializer_list) 1118 TMap m5(l); 1119 static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value); 1120 1121 // check TMap(std::initializer_list, HashCompare) 1122 TMap m6(l, compare); 1123 static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value); 1124 1125 // check TMap(std::initializer_list, HashCompare, Allocator) 1126 TMap m7(l, compare, allocator); 1127 static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value); 1128 1129 // check TMap(std::initializer_list, Allocator) 1130 TMap m8(l, allocator); 1131 static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value); 1132 1133 // check TMap(TMap &) 1134 TMap m9(m1); 1135 static_assert(std::is_same<decltype(m9), decltype(m1)>::value); 1136 1137 // check TMap(TMap &, Allocator) 1138 TMap m10(m4, allocator); 1139 static_assert(std::is_same<decltype(m10), decltype(m4)>::value); 1140 1141 // check TMap(TMap &&) 1142 TMap m11(std::move(m1)); 1143 static_assert(std::is_same<decltype(m11), decltype(m1)>::value); 1144 1145 // check TMap(TMap &&, Allocator) 1146 TMap m12(std::move(m4), allocator); 1147 static_assert(std::is_same<decltype(m12), decltype(m4)>::value); 1148 } 1149 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1150 1151 template <typename CHMapType> 1152 void test_comparisons_basic() { 1153 using comparisons_testing::testEqualityComparisons; 1154 CHMapType c1, c2; 1155 testEqualityComparisons</*ExpectEqual = */true>(c1, c2); 1156 1157 c1.emplace(1, 1); 1158 testEqualityComparisons</*ExpectEqual = */false>(c1, c2); 1159 1160 c2.emplace(1, 1); 1161 testEqualityComparisons</*ExpectEqual = */true>(c1, c2); 1162 } 1163 1164 template <typename TwoWayComparableContainerType> 1165 void test_two_way_comparable_chmap() { 1166 TwoWayComparableContainerType c1, c2; 1167 c1.emplace(1, 1); 1168 c2.emplace(1, 1); 1169 comparisons_testing::TwoWayComparable::reset(); 1170 REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result"); 1171 comparisons_testing::check_equality_comparison(); 1172 REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result"); 1173 comparisons_testing::check_equality_comparison(); 1174 } 1175 1176 void TestCHMapComparisons() { 1177 using integral_container = oneapi::tbb::concurrent_hash_map<int, int>; 1178 using two_way_comparable_container = oneapi::tbb::concurrent_hash_map<comparisons_testing::TwoWayComparable, 1179 comparisons_testing::TwoWayComparable>; 1180 1181 test_comparisons_basic<integral_container>(); 1182 test_comparisons_basic<two_way_comparable_container>(); 1183 test_two_way_comparable_chmap<two_way_comparable_container>(); 1184 } 1185 1186 template <typename Iterator, typename CHMapType> 1187 void TestCHMapIteratorComparisonsBasic( CHMapType& chmap ) { 1188 REQUIRE_MESSAGE(!chmap.empty(), "Incorrect test setup"); 1189 using namespace comparisons_testing; 1190 Iterator it1, it2; 1191 testEqualityComparisons</*ExpectEqual = */true>(it1, it2); 1192 it1 = chmap.begin(); 1193 testEqualityComparisons</*ExpectEqual = */false>(it1, it2); 1194 it2 = chmap.begin(); 1195 testEqualityComparisons</*ExpectEqual = */true>(it1, it2); 1196 it2 = chmap.end(); 1197 testEqualityComparisons</*ExpectEqual = */false>(it1, it2); 1198 } 1199 1200 void TestCHMapIteratorComparisons() { 1201 using chmap_type = oneapi::tbb::concurrent_hash_map<int, int>; 1202 using value_type = typename chmap_type::value_type; 1203 chmap_type chmap = {value_type{1, 1}, value_type{2, 2}, value_type{3, 3}}; 1204 TestCHMapIteratorComparisonsBasic<typename chmap_type::iterator>(chmap); 1205 const chmap_type& cchmap = chmap; 1206 TestCHMapIteratorComparisonsBasic<typename chmap_type::const_iterator>(cchmap); 1207 } 1208 1209 template <bool IsConstructible> 1210 class HeterogeneousKey { 1211 public: 1212 static std::size_t heterogeneous_keys_count; 1213 1214 int integer_key() const { return my_key; } 1215 1216 template <bool I = IsConstructible, typename = typename std::enable_if<I>::type> 1217 HeterogeneousKey(int key) : my_key(key) { ++heterogeneous_keys_count; } 1218 1219 HeterogeneousKey(const HeterogeneousKey&) = delete; 1220 HeterogeneousKey& operator=(const HeterogeneousKey&) = delete; 1221 1222 static void reset() { heterogeneous_keys_count = 0; } 1223 1224 struct construct_flag {}; 1225 1226 HeterogeneousKey( construct_flag, int key ) : my_key(key) {} 1227 1228 private: 1229 int my_key; 1230 }; // class HeterogeneousKey 1231 1232 template <bool IsConstructible> 1233 std::size_t HeterogeneousKey<IsConstructible>::heterogeneous_keys_count = 0; 1234 1235 struct HeterogeneousHashCompare { 1236 using is_transparent = void; 1237 1238 template <bool IsConstructible> 1239 std::size_t hash( const HeterogeneousKey<IsConstructible>& key ) const { 1240 return my_hash_object(key.integer_key()); 1241 } 1242 1243 std::size_t hash( const int& key ) const { 1244 return my_hash_object(key); 1245 } 1246 1247 bool equal( const int& key1, const int& key2 ) const { 1248 return key1 == key2; 1249 } 1250 1251 template <bool IsConstructible> 1252 bool equal( const int& key1, const HeterogeneousKey<IsConstructible>& key2 ) const { 1253 return key1 == key2.integer_key(); 1254 } 1255 1256 template <bool IsConstructible> 1257 bool equal( const HeterogeneousKey<IsConstructible>& key1, const int& key2 ) const { 1258 return key1.integer_key() == key2; 1259 } 1260 1261 template <bool IsConstructible> 1262 bool equal( const HeterogeneousKey<IsConstructible>& key1, const HeterogeneousKey<IsConstructible>& key2 ) const { 1263 return key1.integer_key() == key2.integer_key(); 1264 } 1265 1266 std::hash<int> my_hash_object; 1267 }; // struct HeterogeneousHashCompare 1268 1269 class DefaultConstructibleValue { 1270 public: 1271 DefaultConstructibleValue() : my_i(default_value) {}; 1272 1273 int value() const { return my_i; } 1274 static constexpr int default_value = -4242; 1275 private: 1276 int my_i; 1277 }; // class DefaultConstructibleValue 1278 1279 constexpr int DefaultConstructibleValue::default_value; 1280 1281 void test_heterogeneous_find() { 1282 using key_type = HeterogeneousKey</*IsConstructible = */false>; 1283 using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>; 1284 1285 chmap_type chmap; 1286 using const_accessor = typename chmap_type::const_accessor; 1287 using accessor = typename chmap_type::accessor; 1288 const_accessor cacc; 1289 accessor acc; 1290 1291 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup"); 1292 1293 key_type key(key_type::construct_flag{}, 1); 1294 bool regular_result = chmap.find(cacc, key); 1295 bool heterogeneous_result = chmap.find(cacc, int(1)); 1296 1297 REQUIRE(!regular_result); 1298 REQUIRE_MESSAGE(regular_result == heterogeneous_result, 1299 "Incorrect heterogeneous find result with const_accessor (no element)"); 1300 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (no element)"); 1301 1302 regular_result = chmap.find(acc, key); 1303 heterogeneous_result = chmap.find(acc, int(1)); 1304 1305 REQUIRE(!regular_result); 1306 REQUIRE_MESSAGE(regular_result == heterogeneous_result, 1307 "Incorrect heterogeneous find result with accessor (no element)"); 1308 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (no element)"); 1309 1310 bool tmp_result = chmap.emplace(cacc, std::piecewise_construct, 1311 std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100)); 1312 REQUIRE(tmp_result); 1313 1314 regular_result = chmap.find(cacc, key); 1315 heterogeneous_result = chmap.find(cacc, int(1)); 1316 1317 REQUIRE(regular_result); 1318 REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with const_accessor (element exists)"); 1319 REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor returned"); 1320 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with const_accessor (element exists)"); 1321 cacc.release(); 1322 1323 regular_result = chmap.find(acc, key); 1324 heterogeneous_result = chmap.find(acc, int(1)); 1325 1326 REQUIRE(regular_result); 1327 REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous find result with accessor (element exists)"); 1328 REQUIRE_MESSAGE(acc->first.integer_key() == 1, "Incorrect accessor returned"); 1329 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during find call with accessor (element exists)"); 1330 key_type::reset(); 1331 } 1332 1333 void test_heterogeneous_count() { 1334 using key_type = HeterogeneousKey</*IsConstructible = */false>; 1335 using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>; 1336 1337 chmap_type chmap; 1338 1339 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup"); 1340 key_type key(key_type::construct_flag{}, 1); 1341 1342 typename chmap_type::size_type regular_count = chmap.count(key); 1343 typename chmap_type::size_type heterogeneous_count = chmap.count(int(1)); 1344 1345 REQUIRE(regular_count == 0); 1346 REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (no element)"); 1347 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (no element)"); 1348 1349 chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100)); 1350 1351 regular_count = chmap.count(key); 1352 heterogeneous_count = chmap.count(int(1)); 1353 1354 REQUIRE(regular_count == 1); 1355 REQUIRE_MESSAGE(regular_count == heterogeneous_count, "Incorrect heterogeneous count result (element exists)"); 1356 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during count call (element exists)"); 1357 key_type::reset(); 1358 } 1359 1360 void test_heterogeneous_equal_range() { 1361 using key_type = HeterogeneousKey</*IsConstructible = */false>; 1362 using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>; 1363 1364 chmap_type chmap; 1365 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup"); 1366 1367 using iterator = typename chmap_type::iterator; 1368 using const_iterator = typename chmap_type::const_iterator; 1369 using result = std::pair<iterator, iterator>; 1370 using const_result = std::pair<const_iterator, const_iterator>; 1371 key_type key(key_type::construct_flag{}, 1); 1372 1373 result regular_result = chmap.equal_range(key); 1374 result heterogeneous_result = chmap.equal_range(int(1)); 1375 1376 REQUIRE(regular_result.first == chmap.end()); 1377 REQUIRE(regular_result.second == chmap.end()); 1378 REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, no element)"); 1379 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, no element)"); 1380 1381 const chmap_type& cchmap = chmap; 1382 1383 const_result regular_const_result = cchmap.equal_range(key); 1384 const_result heterogeneous_const_result = cchmap.equal_range(int(1)); 1385 1386 REQUIRE(regular_const_result.first == cchmap.end()); 1387 REQUIRE(regular_const_result.second == cchmap.end()); 1388 REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result, 1389 "Incorrect heterogeneous equal_range result (const, no element)"); 1390 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, no element)"); 1391 1392 chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100)); 1393 1394 regular_result = chmap.equal_range(key); 1395 heterogeneous_result = chmap.equal_range(int(1)); 1396 1397 REQUIRE(regular_result.first != chmap.end()); 1398 REQUIRE(regular_result.first->first.integer_key() == 1); 1399 REQUIRE(regular_result.second == chmap.end()); 1400 REQUIRE_MESSAGE(regular_result == heterogeneous_result, "Incorrect heterogeneous equal_range result (non const, element exists)"); 1401 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (non const, element exists)"); 1402 1403 regular_const_result = cchmap.equal_range(key); 1404 heterogeneous_const_result = cchmap.equal_range(int(1)); 1405 REQUIRE_MESSAGE(regular_const_result == heterogeneous_const_result, 1406 "Incorrect heterogeneous equal_range result (const, element exists)"); 1407 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Temporary key object was created during equal_range call (const, element exists)"); 1408 key_type::reset(); 1409 } 1410 1411 void test_heterogeneous_insert() { 1412 using key_type = HeterogeneousKey</*IsConstructible = */true>; 1413 using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, DefaultConstructibleValue, HeterogeneousHashCompare>; 1414 1415 chmap_type chmap; 1416 using const_accessor = typename chmap_type::const_accessor; 1417 using accessor = typename chmap_type::accessor; 1418 const_accessor cacc; 1419 accessor acc; 1420 1421 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup"); 1422 1423 bool result = chmap.insert(cacc, int(1)); 1424 1425 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "Only one heterogeneous key should be created"); 1426 REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (const_accessor)"); 1427 REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor"); 1428 REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed"); 1429 1430 result = chmap.insert(cacc, int(1)); 1431 1432 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 1, "No extra keys should be created"); 1433 REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (const_accessor)"); 1434 REQUIRE_MESSAGE(cacc->first.integer_key() == 1, "Incorrect accessor"); 1435 REQUIRE_MESSAGE(cacc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed"); 1436 1437 result = chmap.insert(acc, int(2)); 1438 1439 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "Only one extra heterogeneous key should be created"); 1440 REQUIRE_MESSAGE(result, "Incorrect heterogeneous insert result (accessor)"); 1441 REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor"); 1442 REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed"); 1443 1444 result = chmap.insert(acc, int(2)); 1445 1446 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 2, "No extra keys should be created"); 1447 REQUIRE_MESSAGE(!result, "Incorrect heterogeneous insert result (accessor)"); 1448 REQUIRE_MESSAGE(acc->first.integer_key() == 2, "Incorrect accessor"); 1449 REQUIRE_MESSAGE(acc->second.value() == DefaultConstructibleValue::default_value, "Value should be default constructed"); 1450 1451 key_type::reset(); 1452 } 1453 1454 void test_heterogeneous_erase() { 1455 using key_type = HeterogeneousKey</*IsConstructible = */false>; 1456 using chmap_type = oneapi::tbb::concurrent_hash_map<key_type, int, HeterogeneousHashCompare>; 1457 1458 chmap_type chmap; 1459 1460 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "Incorrect test setup"); 1461 1462 chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 1), std::forward_as_tuple(100)); 1463 chmap.emplace(std::piecewise_construct, std::forward_as_tuple(key_type::construct_flag{}, 2), std::forward_as_tuple(200)); 1464 1465 typename chmap_type::const_accessor cacc; 1466 1467 REQUIRE(chmap.find(cacc, int(1))); 1468 REQUIRE(chmap.find(cacc, int(2))); 1469 1470 cacc.release(); 1471 1472 bool result = chmap.erase(int(1)); 1473 REQUIRE_MESSAGE(result, "Erasure should be successful"); 1474 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created"); 1475 REQUIRE_MESSAGE(!chmap.find(cacc, int(1)), "Element was not erased"); 1476 1477 1478 result = chmap.erase(int(1)); 1479 REQUIRE_MESSAGE(!result, "Erasure should fail"); 1480 REQUIRE_MESSAGE(key_type::heterogeneous_keys_count == 0, "No extra keys should be created"); 1481 key_type::reset(); 1482 } 1483 1484 void test_heterogeneous_lookup() { 1485 test_heterogeneous_find(); 1486 test_heterogeneous_count(); 1487 test_heterogeneous_equal_range(); 1488 } 1489 1490 //! Test consruction with hash_compare 1491 //! \brief \ref interface \ref requirement 1492 TEST_CASE("testing consruction with hash_compare") { 1493 TestHashCompareConstructors(); 1494 } 1495 1496 //! Test concurrent_hash_map member types 1497 //! \brief \ref interface \ref requirement 1498 TEST_CASE("test types"){ 1499 test_member_types<oneapi::tbb::concurrent_hash_map>(); 1500 } 1501 1502 //! Test swap and clear operations 1503 //! \brief \ref interface \ref requirement 1504 TEST_CASE("test copy operations") { 1505 TestCopy(); 1506 } 1507 1508 //! Test rehash operation 1509 //! \brief \ref interface \ref requirement 1510 TEST_CASE("test rehash operation") { 1511 TestRehash(); 1512 } 1513 1514 //! Test assignment operation 1515 //! \brief \ref interface \ref requirement 1516 TEST_CASE("test assignment operation") { 1517 TestAssignment(); 1518 } 1519 1520 //! Test iterators and ranges 1521 //! \brief \ref interface \ref requirement 1522 TEST_CASE("test iterators and ranges") { 1523 TestIteratorsAndRanges(); 1524 } 1525 1526 //! Test work with initializer_list 1527 //! \brief \ref interface \ref requirement 1528 TEST_CASE("test work with initializer_list") { 1529 TestInitList(); 1530 } 1531 1532 #if TBB_USE_EXCEPTIONS 1533 //! Test exception safety 1534 //! \brief \ref requirement 1535 TEST_CASE("test exception safety") { 1536 TestExceptions(); 1537 } 1538 1539 //! Test exceptions safety guarantees for move constructor 1540 //! \brief \ref requirement 1541 TEST_CASE("test move support with exceptions") { 1542 move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>(); 1543 move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>(); 1544 } 1545 #endif 1546 1547 //! Test move constructor 1548 //! \brief \ref interface \ref requirement 1549 TEST_CASE("testing move constructor"){ 1550 move_support_tests::test_move_constructor<hash_map_traits>(); 1551 } 1552 1553 //! Test move assign operator 1554 //! \brief \ref interface \ref requirement 1555 TEST_CASE("testing move assign operator"){ 1556 move_support_tests::test_move_assignment<hash_map_traits>(); 1557 } 1558 1559 //! Test insert and empace 1560 //! \brief \ref interface \ref requirement 1561 TEST_CASE("testing concurrent insert and emplace"){ 1562 int n=250000; 1563 { 1564 DataStateTrackedTable table; 1565 DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 ); 1566 } 1567 { 1568 DataStateTrackedTable table; 1569 DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 ); 1570 } 1571 } 1572 1573 //! Test allocator traits 1574 //! \brief \ref requirement 1575 TEST_CASE("testing allocator traits") { 1576 test_allocator_traits_support<hash_map_traits>(); 1577 } 1578 1579 //! Test concurrent operations 1580 //! \brief \ref requirement 1581 TEST_CASE("testing concurrency"){ 1582 for (std::size_t p = 1; p <= 4; ++p) { 1583 oneapi::tbb::global_control limit(oneapi::tbb::global_control::max_allowed_parallelism, p); 1584 TestInsertFindErase(p); 1585 TestConcurrency(p); 1586 } 1587 } 1588 1589 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1590 //! Test deduction guides 1591 //! \brief \ref interface 1592 TEST_CASE("testing deduction guides") { 1593 TestDeductionGuides<oneapi::tbb::concurrent_hash_map>(); 1594 } 1595 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1596 1597 //! \brief \ref interface \ref requirement 1598 TEST_CASE("concurrent_hash_map comparisons") { 1599 TestCHMapComparisons(); 1600 } 1601 1602 //! \brief \ref interface \ref requirement 1603 TEST_CASE("concurrent_hash_map iterator comparisons") { 1604 TestCHMapIteratorComparisons(); 1605 } 1606 1607 //! \brief \ref interface \ref requirement 1608 TEST_CASE("test concurrent_hash_map heterogeneous lookup") { 1609 test_heterogeneous_lookup(); 1610 } 1611 1612 //! \brief \ref interface \ref requirement 1613 TEST_CASE("test concurrent_hash_map heterogeneous insert") { 1614 test_heterogeneous_insert(); 1615 } 1616 1617 //! \brief \ref interface \ref requirement 1618 TEST_CASE("test concurrent_hash_map heterogeneous erase") { 1619 test_heterogeneous_erase(); 1620 } 1621