1 /* 2 Copyright (c) 2005-2020 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 <tbb/concurrent_hash_map.h> 27 #include <tbb/global_control.h> 28 #include <tbb/parallel_for.h> 29 30 //! \file conformance_concurrent_hash_map.cpp 31 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification 32 33 /** Has tightly controlled interface so that we can verify 34 that concurrent_hash_map uses only the required interface. */ 35 class MyException : public std::bad_alloc { 36 public: 37 virtual const char *what() const throw() override { return "out of items limit"; } 38 virtual ~MyException() throw() {} 39 }; 40 41 /** Has tightly controlled interface so that we can verify 42 that concurrent_hash_map uses only the required interface. */ 43 class MyKey { 44 private: 45 int key; 46 friend class MyHashCompare; 47 friend class YourHashCompare; 48 public: 49 MyKey() = default; 50 MyKey( const MyKey& ) = default; 51 void operator=( const MyKey& ) = delete; 52 static MyKey make( int i ) { 53 MyKey result; 54 result.key = i; 55 return result; 56 } 57 58 int value_of() const { return key; } 59 }; 60 61 std::atomic<long> MyDataCount; 62 long MyDataCountLimit = 0; 63 64 class MyData { 65 protected: 66 friend class MyData2; 67 int data; 68 enum state_t { 69 LIVE=0x1234, 70 DEAD=0x5678 71 } my_state; 72 void operator=( const MyData& ); // Deny access 73 public: 74 MyData(int i = 0) { 75 my_state = LIVE; 76 data = i; 77 if (MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) { 78 TBB_TEST_THROW(MyException{}); 79 } 80 ++MyDataCount; 81 } 82 83 MyData( const MyData& other ) { 84 CHECK(other.my_state==LIVE); 85 my_state = LIVE; 86 data = other.data; 87 if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) { 88 TBB_TEST_THROW(MyException{}); 89 } 90 ++MyDataCount; 91 } 92 93 ~MyData() { 94 --MyDataCount; 95 my_state = DEAD; 96 } 97 98 static MyData make( int i ) { 99 MyData result; 100 result.data = i; 101 return result; 102 } 103 104 int value_of() const { 105 CHECK(my_state==LIVE); 106 return data; 107 } 108 109 void set_value( int i ) { 110 CHECK(my_state==LIVE); 111 data = i; 112 } 113 114 bool operator==( const MyData& other ) const { 115 CHECK(other.my_state==LIVE); 116 CHECK(my_state==LIVE); 117 return data == other.data; 118 } 119 }; 120 121 class MyData2 : public MyData { 122 public: 123 MyData2( ) {} 124 125 MyData2( const MyData2& other ) : MyData() { 126 CHECK(other.my_state==LIVE); 127 CHECK(my_state==LIVE); 128 data = other.data; 129 } 130 131 MyData2( const MyData& other ) { 132 CHECK(other.my_state==LIVE); 133 CHECK(my_state==LIVE); 134 data = other.data; 135 } 136 137 void operator=( const MyData& other ) { 138 CHECK(other.my_state==LIVE); 139 CHECK(my_state==LIVE); 140 data = other.data; 141 } 142 143 void operator=( const MyData2& other ) { 144 CHECK(other.my_state==LIVE); 145 CHECK(my_state==LIVE); 146 data = other.data; 147 } 148 149 bool operator==( const MyData2& other ) const { 150 CHECK(other.my_state==LIVE); 151 CHECK(my_state==LIVE); 152 return data == other.data; 153 } 154 }; 155 156 class MyHashCompare { 157 public: 158 bool equal( const MyKey& j, const MyKey& k ) const { 159 return j.key==k.key; 160 } 161 162 unsigned long hash( const MyKey& k ) const { 163 return k.key; 164 } 165 }; 166 167 class YourHashCompare { 168 public: 169 bool equal( const MyKey& j, const MyKey& k ) const { 170 return j.key==k.key; 171 } 172 173 unsigned long hash( const MyKey& ) const { 174 return 1; 175 } 176 }; 177 178 using test_allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData>>>; 179 using test_table_type = tbb::concurrent_hash_map<MyKey, MyData, MyHashCompare, test_allocator_type>; 180 using other_test_table_type = tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare>; 181 182 template <template <typename...> class ContainerType> 183 void test_member_types() { 184 using container_type = ContainerType<int, int>; 185 static_assert(std::is_same<typename container_type::allocator_type, tbb::tbb_allocator<std::pair<const int, int>>>::value, 186 "Incorrect default template allocator"); 187 188 static_assert(std::is_same<typename container_type::key_type, int>::value, 189 "Incorrect container key_type member type"); 190 static_assert(std::is_same<typename container_type::value_type, std::pair<const int, int>>::value, 191 "Incorrect container value_type member type"); 192 193 static_assert(std::is_unsigned<typename container_type::size_type>::value, 194 "Incorrect container size_type member type"); 195 static_assert(std::is_signed<typename container_type::difference_type>::value, 196 "Incorrect container difference_type member type"); 197 198 using value_type = typename container_type::value_type; 199 static_assert(std::is_same<typename container_type::reference, value_type&>::value, 200 "Incorrect container reference member type"); 201 static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value, 202 "Incorrect container const_reference member type"); 203 using allocator_type = typename container_type::allocator_type; 204 static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value, 205 "Incorrect container pointer member type"); 206 static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value, 207 "Incorrect container const_pointer member type"); 208 209 static_assert(utils::is_forward_iterator<typename container_type::iterator>::value, 210 "Incorrect container iterator member type"); 211 static_assert(!std::is_const<typename container_type::iterator::value_type>::value, 212 "Incorrect container iterator member type"); 213 static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value, 214 "Incorrect container const_iterator member type"); 215 static_assert(std::is_const<typename container_type::const_iterator::value_type>::value, 216 "Incorrect container iterator member type"); 217 } 218 219 template<typename test_table_type> 220 void FillTable( test_table_type& x, int n ) { 221 for( int i=1; i<=n; ++i ) { 222 MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order 223 typename test_table_type::accessor a; 224 bool b = x.insert(a,key); 225 CHECK(b); 226 a->second.set_value( i*i ); 227 } 228 } 229 230 template<typename test_table_type> 231 static void CheckTable( const test_table_type& x, int n ) { 232 REQUIRE_MESSAGE( x.size()==size_t(n), "table is different size than expected" ); 233 CHECK(x.empty()==(n==0)); 234 CHECK(x.size()<=x.max_size()); 235 for( int i=1; i<=n; ++i ) { 236 MyKey key( MyKey::make(-i) ); 237 typename test_table_type::const_accessor a; 238 bool b = x.find(a,key); 239 CHECK(b); 240 CHECK(a->second.value_of()==i*i); 241 } 242 int count = 0; 243 int key_sum = 0; 244 for( typename test_table_type::const_iterator i(x.begin()); i!=x.end(); ++i ) { 245 ++count; 246 key_sum += -i->first.value_of(); 247 } 248 CHECK(count==n); 249 CHECK(key_sum==n*(n+1)/2); 250 } 251 252 void TestCopy() { 253 INFO("testing copy\n"); 254 test_table_type t1; 255 for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) { 256 MyDataCount = 0; 257 258 FillTable(t1,i); 259 // Do not call CheckTable(t1,i) before copying, it enforces rehashing 260 261 test_table_type t2(t1); 262 // Check that copy constructor did not mangle source table. 263 CheckTable(t1,i); 264 swap(t1, t2); 265 CheckTable(t1,i); 266 CHECK(!(t1 != t2)); 267 268 // Clear original table 269 t2.clear(); 270 swap(t2, t1); 271 CheckTable(t1,0); 272 273 // Verify that copy of t1 is correct, even after t1 is cleared. 274 CheckTable(t2,i); 275 t2.clear(); 276 t1.swap( t2 ); 277 CheckTable(t1,0); 278 CheckTable(t2,0); 279 REQUIRE_MESSAGE( MyDataCount==0, "data leak?" ); 280 } 281 } 282 283 void TestRehash() { 284 INFO("testing rehashing\n"); 285 test_table_type w; 286 w.insert( std::make_pair(MyKey::make(-5), MyData()) ); 287 w.rehash(); // without this, assertion will fail 288 test_table_type::iterator it = w.begin(); 289 int i = 0; // check for non-rehashed buckets 290 for( ; it != w.end(); i++ ) 291 w.count( (it++)->first ); 292 CHECK(i == 1); 293 for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) { 294 for( int j=std::max(256+i, i*2); j<10000; j*=3 ) { 295 test_table_type v; 296 FillTable( v, i ); 297 CHECK(int(v.size()) == i); 298 CHECK(int(v.bucket_count()) <= j); 299 v.rehash( j ); 300 CHECK(int(v.bucket_count()) >= j); 301 CheckTable( v, i ); 302 } 303 } 304 } 305 306 void TestAssignment() { 307 INFO("testing assignment\n"); 308 tbb::concurrent_hash_map<int, int> test_map({{1, 2}, {2, 4}}); 309 test_map.operator=(test_map); // suppress self assign warning 310 CHECK(!test_map.empty()); 311 312 for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) { 313 for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) { 314 test_table_type t1; 315 test_table_type t2; 316 FillTable(t1,i); 317 FillTable(t2,j); 318 CHECK((t1 == t2) == (i == j)); 319 CheckTable(t2,j); 320 321 test_table_type& tref = t2=t1; 322 CHECK(&tref==&t2); 323 CHECK(t1 == t2); 324 CheckTable(t1,i); 325 CheckTable(t2,i); 326 327 t1.clear(); 328 CheckTable(t1,0); 329 CheckTable(t2,i); 330 REQUIRE_MESSAGE( MyDataCount==i, "data leak?" ); 331 332 t2.clear(); 333 CheckTable(t1,0); 334 CheckTable(t2,0); 335 REQUIRE_MESSAGE( MyDataCount==0, "data leak?" ); 336 } 337 } 338 } 339 340 template<typename Iterator, typename T> 341 void TestIteratorTraits() { 342 T x; 343 typename Iterator::reference xr = x; 344 typename Iterator::pointer xp = &x; 345 CHECK(&xr==xp); 346 } 347 348 template<typename Iterator1, typename Iterator2> 349 void TestIteratorAssignment( Iterator2 j ) { 350 Iterator1 i(j), k; 351 CHECK(i==j); 352 CHECK(!(i!=j)); 353 k = j; 354 CHECK(k==j); 355 CHECK(!(k!=j)); 356 } 357 358 template<typename Range1, typename Range2> 359 void TestRangeAssignment( Range2 r2 ) { 360 Range1 r1(r2); r1 = r2; 361 } 362 363 void TestIteratorsAndRanges() { 364 INFO("testing iterators compliance\n"); 365 TestIteratorTraits<test_table_type::iterator,test_table_type::value_type>(); 366 TestIteratorTraits<test_table_type::const_iterator,const test_table_type::value_type>(); 367 368 test_table_type v; 369 CHECK(v.range().grainsize() == 1); 370 test_table_type const &u = v; 371 372 TestIteratorAssignment<test_table_type::const_iterator>( u.begin() ); 373 TestIteratorAssignment<test_table_type::const_iterator>( v.begin() ); 374 TestIteratorAssignment<test_table_type::iterator>( v.begin() ); 375 // doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() ); 376 377 // check for non-existing 378 CHECK(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end())); 379 CHECK(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end())); 380 381 INFO("testing ranges compliance\n"); 382 TestRangeAssignment<test_table_type::const_range_type>( u.range() ); 383 TestRangeAssignment<test_table_type::range_type>( v.range() ); 384 // doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() ); 385 386 INFO("testing construction and insertion from iterators range\n"); 387 FillTable( v, 1000 ); 388 other_test_table_type t(v.begin(), v.end()); 389 v.rehash(); 390 CheckTable(t, 1000); 391 t.insert(v.begin(), v.end()); // do nothing 392 CheckTable(t, 1000); 393 t.clear(); 394 t.insert(v.begin(), v.end()); // restore 395 CheckTable(t, 1000); 396 397 INFO("testing comparison\n"); 398 using test_allocator_type2 = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData2>>>; 399 using YourTable1 = tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare, test_allocator_type2>; 400 using YourTable2 = tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare>; 401 YourTable1 t1; 402 FillTable( t1, 10 ); 403 CheckTable(t1, 10 ); 404 YourTable2 t2(t1.begin(), t1.end()); 405 MyKey key( MyKey::make(-5) ); MyData2 data; 406 CHECK(t2.erase(key)); 407 YourTable2::accessor a; 408 CHECK(t2.insert(a, key)); 409 data.set_value(0); a->second = data; 410 CHECK(t1 != t2); 411 data.set_value(5*5); a->second = data; 412 CHECK(t1 == t2); 413 } 414 415 struct test_insert { 416 template<typename container_type, typename element_type> 417 static void test( std::initializer_list<element_type> il, container_type const& expected ) { 418 container_type vd; 419 vd.insert( il ); 420 REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" ); 421 } 422 }; 423 424 void TestInitList(){ 425 using namespace initializer_list_support_tests; 426 INFO("testing initializer_list methods \n"); 427 428 using ch_map_type = tbb::concurrent_hash_map<int,int>; 429 std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}}; 430 431 test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il ); 432 test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} ); 433 } 434 435 template <typename base_alloc_type> 436 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> { 437 using base_type = StaticSharedCountingAllocator<base_alloc_type>; 438 using base_traits = tbb::detail::allocator_traits<base_alloc_type>; 439 public: 440 template<typename U> 441 struct rebind { 442 using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>; 443 }; 444 445 only_node_counting_allocator() : base_type() {} 446 only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {} 447 448 template<typename U> 449 only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {} 450 451 typename base_type::value_type* allocate(const std::size_t n) { 452 if ( n > 1) { 453 return base_alloc_type::allocate(n); 454 } else { 455 return base_type::allocate(n); 456 } 457 } 458 }; 459 460 #if TBB_USE_EXCEPTIONS 461 void TestExceptions() { 462 using allocator_type = only_node_counting_allocator<tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>; 463 using throwing_table = tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>; 464 enum methods { 465 zero_method = 0, 466 ctor_copy, op_assign, op_insert, 467 all_methods 468 }; 469 470 INFO("testing exception-safety guarantees\n"); 471 throwing_table src; 472 FillTable( src, 1000 ); 473 CHECK(MyDataCount==1000); 474 475 try { 476 for(int t = 0; t < 2; t++) // exception type 477 for(int m = zero_method+1; m < all_methods; m++) 478 { 479 allocator_type a; 480 allocator_type::init_counters(); 481 if(t) MyDataCountLimit = 101; 482 else a.set_limits(101); 483 throwing_table victim(a); 484 MyDataCount = 0; 485 486 try { 487 switch(m) { 488 case ctor_copy: { 489 throwing_table acopy(src, a); 490 } break; 491 case op_assign: { 492 victim = src; 493 } break; 494 case op_insert: { 495 // Insertion in cpp11 don't make copy constructions 496 // during the insertion, so we need to decrement limit 497 // to throw an exception in the right place and to prevent 498 // successful insertion of one unexpected item 499 if (MyDataCountLimit) 500 --MyDataCountLimit; 501 FillTable( victim, 1000 ); 502 } break; 503 default:; 504 } 505 REQUIRE_MESSAGE(false, "should throw an exception"); 506 } catch(std::bad_alloc &e) { 507 MyDataCountLimit = 0; 508 size_t size = victim.size(); 509 switch(m) { 510 case op_assign: 511 REQUIRE_MESSAGE( MyDataCount==100, "data leak?" ); 512 CHECK(size>=100); 513 utils_fallthrough; 514 case ctor_copy: 515 CheckTable(src, 1000); 516 break; 517 case op_insert: 518 CHECK(size==size_t(100-t)); 519 REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" ); 520 CheckTable(victim, 100-t); 521 break; 522 523 default:; // nothing to check here 524 } 525 INFO("Exception "<< m << " : " << e.what() << "- ok ()"); 526 } 527 catch ( ... ) { 528 REQUIRE_MESSAGE( false, "Unrecognized exception" ); 529 } 530 } 531 } catch(...) { 532 REQUIRE_MESSAGE(false, "unexpected exception"); 533 } 534 src.clear(); MyDataCount = 0; 535 allocator_type::max_items = 0; 536 } 537 #endif 538 539 struct default_container_traits { 540 template <typename container_type, typename iterator_type> 541 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){ 542 container_type* ptr = reinterpret_cast<container_type*>(&storage); 543 new (ptr) container_type(begin, end); 544 return *ptr; 545 } 546 547 template <typename container_type, typename iterator_type, typename allocator_type> 548 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){ 549 container_type* ptr = reinterpret_cast<container_type*>(&storage); 550 new (ptr) container_type(begin, end, a); 551 return *ptr; 552 } 553 }; 554 555 struct hash_map_traits : default_container_traits { 556 enum{ expected_number_of_items_to_allocate_for_steal_move = 0 }; 557 558 template<typename T> 559 struct hash_compare { 560 bool equal( const T& lhs, const T& rhs ) const { 561 return lhs==rhs; 562 } 563 size_t hash( const T& k ) const { 564 return my_hash_func(k); 565 } 566 std::hash<T> my_hash_func; 567 }; 568 569 template <typename T, typename Allocator> 570 using container_type = tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>; 571 572 template <typename T> 573 using container_value_type = std::pair<const T, T>; 574 575 template<typename element_type, typename allocator_type> 576 struct apply { 577 using type = tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>; 578 }; 579 580 using init_iterator_type = move_support_tests::FooPairIterator; 581 template <typename hash_map_type, typename iterator> 582 static bool equal(hash_map_type const& c, iterator begin, iterator end){ 583 bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() ); 584 if (!equal_sizes) 585 return false; 586 587 for (iterator it = begin; it != end; ++it ){ 588 if (c.count( (*it).first) == 0){ 589 return false; 590 } 591 } 592 return true; 593 } 594 }; 595 596 using DataStateTrackedTable = tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>; 597 598 struct RvalueInsert { 599 static void apply( DataStateTrackedTable& table, int i ) { 600 DataStateTrackedTable::accessor a; 601 int next = i + 1; 602 REQUIRE_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))), 603 "already present while should not ?" ); 604 CHECK((*a).second == next); 605 CHECK((*a).second.state == StateTrackableBase::MoveInitialized); 606 } 607 }; 608 609 struct Emplace { 610 template <typename Accessor> 611 static void apply_impl( DataStateTrackedTable& table, int i) { 612 Accessor a; 613 REQUIRE_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))), 614 "already present while should not ?" ); 615 CHECK((*a).second == i + 1); 616 CHECK((*a).second.state == StateTrackableBase::DirectInitialized); 617 } 618 619 static void apply( DataStateTrackedTable& table, int i ) { 620 // TODO: investigate ability to rewrite apply methods with use apply_imp method. 621 if (i % 2) { 622 apply_impl<DataStateTrackedTable::accessor>(table, i); 623 } else { 624 apply_impl<DataStateTrackedTable::const_accessor>(table, i); 625 } 626 } 627 }; 628 629 template<typename Op, typename test_table_type> 630 class TableOperation { 631 test_table_type& my_table; 632 public: 633 void operator()( const tbb::blocked_range<int>& range ) const { 634 for( int i=range.begin(); i!=range.end(); ++i ) 635 Op::apply(my_table,i); 636 } 637 TableOperation( test_table_type& table ) : my_table(table) {} 638 }; 639 640 bool UseKey( size_t i ) { 641 return (i&3)!=3; 642 } 643 644 struct Insert { 645 static void apply( test_table_type& table, int i ) { 646 if( UseKey(i) ) { 647 if( i&4 ) { 648 test_table_type::accessor a; 649 table.insert( a, MyKey::make(i) ); 650 if( i&1 ) 651 (*a).second.set_value(i*i); 652 else 653 a->second.set_value(i*i); 654 } else 655 if( i&1 ) { 656 test_table_type::accessor a; 657 table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) ); 658 CHECK((*a).second.value_of()==i*i); 659 } else { 660 test_table_type::const_accessor ca; 661 table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) ); 662 CHECK(ca->second.value_of()==i*i); 663 } 664 } 665 } 666 }; 667 668 struct Find { 669 static void apply( test_table_type& table, int i ) { 670 test_table_type::accessor a; 671 const test_table_type::accessor& ca = a; 672 bool b = table.find( a, MyKey::make(i) ); 673 CHECK(b==!a.empty()); 674 if( b ) { 675 if( !UseKey(i) ) 676 REPORT("Line %d: unexpected key %d present\n",__LINE__,i); 677 CHECK(ca->second.value_of()==i*i); 678 CHECK((*ca).second.value_of()==i*i); 679 if( i&1 ) 680 ca->second.set_value( ~ca->second.value_of() ); 681 else 682 (*ca).second.set_value( ~ca->second.value_of() ); 683 } else { 684 if( UseKey(i) ) 685 REPORT("Line %d: key %d missing\n",__LINE__,i); 686 } 687 } 688 }; 689 690 struct FindConst { 691 static void apply( const test_table_type& table, int i ) { 692 test_table_type::const_accessor a; 693 const test_table_type::const_accessor& ca = a; 694 bool b = table.find( a, MyKey::make(i) ); 695 CHECK(b==(table.count(MyKey::make(i))>0)); 696 CHECK(b==!a.empty()); 697 CHECK(b==UseKey(i)); 698 if( b ) { 699 CHECK(ca->second.value_of()==~(i*i)); 700 CHECK((*ca).second.value_of()==~(i*i)); 701 } 702 } 703 }; 704 705 struct InsertInitList { 706 static void apply( test_table_type& table, int i ) { 707 if ( UseKey( i ) ) { 708 // TODO: investigate why the following sequence causes an additional allocation sometimes: 709 // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) ); 710 // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) ); 711 std::initializer_list<test_table_type::value_type> il = { 712 test_table_type::value_type( MyKey::make( i ), i*i ) 713 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */ 714 }; 715 table.insert( il ); 716 } 717 } 718 }; 719 720 template<typename Op, typename TableType> 721 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) { 722 INFO("testing " << what << " with " << nthread << " threads"); 723 tbb::parallel_for(tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table)); 724 } 725 726 //! Test traversing the table with an iterator. 727 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) { 728 INFO("testing traversal\n"); 729 size_t actual_size = table.size(); 730 CHECK(actual_size==expected_size); 731 size_t count = 0; 732 bool* array = new bool[n]; 733 memset( array, 0, n*sizeof(bool) ); 734 const test_table_type& const_table = table; 735 test_table_type::const_iterator ci = const_table.begin(); 736 for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) { 737 // Check iterator 738 int k = i->first.value_of(); 739 CHECK(UseKey(k)); 740 CHECK((*i).first.value_of()==k); 741 REQUIRE_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" ); 742 REQUIRE_MESSAGE( !array[k], "duplicate key" ); 743 array[k] = true; 744 ++count; 745 746 // Check lower/upper bounds 747 std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first); 748 std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first); 749 CHECK((cer.first == er.first && cer.second == er.second)); 750 CHECK(cer.first == i); 751 CHECK(std::distance(cer.first, cer.second) == 1); 752 753 // Check const_iterator 754 test_table_type::const_iterator cic = ci++; 755 CHECK(cic->first.value_of()==k); 756 CHECK((*cic).first.value_of()==k); 757 } 758 CHECK(ci==const_table.end()); 759 delete[] array; 760 if (count != expected_size) { 761 INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size); 762 } 763 } 764 765 std::atomic<int> EraseCount; 766 767 struct Erase { 768 static void apply( test_table_type& table, int i ) { 769 bool b; 770 if(i&4) { 771 if(i&8) { 772 test_table_type::const_accessor a; 773 b = table.find( a, MyKey::make(i) ) && table.erase( a ); 774 } else { 775 test_table_type::accessor a; 776 b = table.find( a, MyKey::make(i) ) && table.erase( a ); 777 } 778 } else 779 b = table.erase( MyKey::make(i) ); 780 if( b ) ++EraseCount; 781 CHECK(table.count(MyKey::make(i)) == 0); 782 } 783 }; 784 785 using YourTable = tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>; 786 static const int IE_SIZE = 2; 787 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE]; 788 789 struct InsertErase { 790 static void apply( YourTable& table, int i ) { 791 if ( i%3 ) { 792 int key = i%IE_SIZE; 793 if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) ) 794 ++InsertEraseCount[key]; 795 } else { 796 int key = i%IE_SIZE; 797 if( i&1 ) { 798 YourTable::accessor res; 799 if(table.find( res, MyKey::make(key) ) && table.erase( res ) ) 800 --InsertEraseCount[key]; 801 } else { 802 YourTable::const_accessor res; 803 if(table.find( res, MyKey::make(key) ) && table.erase( res ) ) 804 --InsertEraseCount[key]; 805 } 806 } 807 } 808 }; 809 810 struct InnerInsert { 811 static void apply( YourTable& table, int i ) { 812 YourTable::accessor a1, a2; 813 if(i&1) std::this_thread::yield(); 814 table.insert( a1, MyKey::make(1) ); 815 std::this_thread::yield(); 816 table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain 817 table.erase( a2 ); // if erase by key it would lead to deadlock for single thread 818 } 819 }; 820 821 struct FakeExclusive { 822 utils::SpinBarrier& barrier; 823 YourTable& table; 824 FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {} 825 void operator()( std::size_t i ) const { 826 if(i) { 827 YourTable::const_accessor real_ca; 828 // const accessor on non-const table acquired as reader (shared) 829 CHECK(table.find(real_ca,MyKey::make(1))); 830 barrier.wait(); // item can be erased 831 std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase 832 real_ca->second.value_of(); // check the state while holding accessor 833 } else { 834 YourTable::accessor fake_ca; 835 const YourTable &const_table = table; 836 // non-const accessor on const table acquired as reader (shared) 837 CHECK(const_table.find(fake_ca,MyKey::make(1))); 838 barrier.wait(); // readers acquired 839 // can mistakenly remove the item while other readers still refers to it 840 table.erase( fake_ca ); 841 } 842 } 843 }; 844 845 using AtomicByte = std::atomic<unsigned char>; 846 847 template<typename RangeType> 848 struct ParallelTraverseBody { 849 const size_t n; 850 AtomicByte* const array; 851 ParallelTraverseBody( AtomicByte array_[], size_t n_ ) : 852 n(n_), 853 array(array_) 854 {} 855 void operator()( const RangeType& range ) const { 856 for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) { 857 int k = i->first.value_of(); 858 CHECK((0<=k && size_t(k)<n)); 859 ++array[k]; 860 } 861 } 862 }; 863 864 void Check( AtomicByte array[], size_t n, size_t expected_size ) { 865 if( expected_size ) 866 for( size_t k=0; k<n; ++k ) { 867 if( array[k] != int(UseKey(k)) ) { 868 REPORT("array[%d]=%d != %d=UseKey(%d)\n", 869 int(k), int(array[k]), int(UseKey(k)), int(k)); 870 CHECK(false); 871 } 872 } 873 } 874 875 //! Test traversing the table with a parallel range 876 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) { 877 INFO("testing parallel traversal\n"); 878 CHECK(table.size()==expected_size); 879 AtomicByte* array = new AtomicByte[n]; 880 881 memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) ); 882 test_table_type::range_type r = table.range(10); 883 tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n )); 884 Check( array, n, expected_size ); 885 886 const test_table_type& const_table = table; 887 memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) ); 888 test_table_type::const_range_type cr = const_table.range(10); 889 tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n )); 890 Check( array, n, expected_size ); 891 892 delete[] array; 893 } 894 895 void TestInsertFindErase( std::size_t nthread ) { 896 int n=250000; 897 898 // compute m = number of unique keys 899 int m = 0; 900 for( int i=0; i<n; ++i ) 901 m += UseKey(i); 902 { 903 test_allocator_type alloc; 904 test_allocator_type::init_counters(); 905 CHECK(MyDataCount==0); 906 test_table_type table(alloc); 907 TraverseTable(table,n,0); 908 ParallelTraverseTable(table,n,0); 909 910 int expected_allocs = 0, expected_frees = 100; 911 for ( int i = 0; i < 2; ++i ) { 912 if ( i==0 ) 913 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread ); 914 else 915 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread ); 916 CHECK(MyDataCount == m); 917 TraverseTable( table, n, m ); 918 ParallelTraverseTable( table, n, m ); 919 expected_allocs += m; 920 921 DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread ); 922 CHECK(MyDataCount == m); 923 924 DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread ); 925 CHECK(MyDataCount == m); 926 927 EraseCount = 0; 928 DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread ); 929 CHECK(EraseCount == m); 930 CHECK(MyDataCount == 0); 931 TraverseTable( table, n, 0 ); 932 expected_frees += m; 933 934 table.clear(); 935 } 936 937 if( nthread > 1 ) { 938 YourTable ie_table; 939 for( int i=0; i<IE_SIZE; ++i ) 940 InsertEraseCount[i] = 0; 941 DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread); 942 for( int i=0; i<IE_SIZE; ++i ) 943 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i))); 944 945 DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread); 946 utils::SpinBarrier barrier(nthread); 947 INFO("testing erase on fake exclusive accessor\n"); 948 utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table)); 949 } 950 } 951 REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed ); 952 REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed ); 953 REQUIRE( test_allocator_type::allocations == test_allocator_type::frees ); 954 } 955 956 std::atomic<int> Counter; 957 958 class AddToTable { 959 test_table_type& my_table; 960 const std::size_t my_nthread; 961 const int my_m; 962 public: 963 AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {} 964 void operator()( std::size_t ) const { 965 for( int i=0; i<my_m; ++i ) { 966 // Busy wait to synchronize threads 967 int j = 0; 968 while( Counter<i ) { 969 if( ++j==1000000 ) { 970 // If Counter<i after a million iterations, then we almost surely have 971 // more logical threads than physical threads, and should yield in 972 // order to let suspended logical threads make progress. 973 j = 0; 974 std::this_thread::yield(); 975 } 976 } 977 // Now all threads attempt to simultaneously insert a key. 978 int k; 979 { 980 test_table_type::accessor a; 981 MyKey key = MyKey::make(i); 982 if( my_table.insert( a, key ) ) 983 a->second.set_value( 1 ); 984 else 985 a->second.set_value( a->second.value_of()+1 ); 986 k = a->second.value_of(); 987 } 988 if( std::size_t(k) == my_nthread ) 989 Counter=i+1; 990 } 991 } 992 }; 993 994 class RemoveFromTable { 995 test_table_type& my_table; 996 const int my_m; 997 public: 998 RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {} 999 void operator()(std::size_t) const { 1000 for( int i=0; i<my_m; ++i ) { 1001 bool b; 1002 if(i&4) { 1003 if(i&8) { 1004 test_table_type::const_accessor a; 1005 b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a ); 1006 } else { 1007 test_table_type::accessor a; 1008 b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a ); 1009 } 1010 } else 1011 b = my_table.erase( MyKey::make(i) ); 1012 if( b ) ++EraseCount; 1013 } 1014 } 1015 }; 1016 1017 void TestConcurrency( std::size_t nthread ) { 1018 INFO("testing multiple insertions/deletions of same key with " << nthread << " threads"); 1019 test_allocator_type::init_counters(); 1020 { 1021 CHECK( MyDataCount==0); 1022 test_table_type table; 1023 const int m = 1000; 1024 Counter = 0; 1025 tbb::tick_count t0 = tbb::tick_count::now(); 1026 utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) ); 1027 REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" ); 1028 1029 EraseCount = 0; 1030 t0 = tbb::tick_count::now(); 1031 utils::NativeParallelFor( nthread, RemoveFromTable(table,m) ); 1032 REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected"); 1033 REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken"); 1034 1035 } 1036 REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed ); 1037 REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed ); 1038 REQUIRE( test_allocator_type::allocations == test_allocator_type::frees ); 1039 REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected"); 1040 } 1041 1042 template<typename Key> 1043 struct non_default_constructible_hash_compare : tbb::detail::d1::tbb_hash_compare<Key> { 1044 non_default_constructible_hash_compare() { 1045 REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument"); 1046 } 1047 1048 non_default_constructible_hash_compare(int) {} 1049 }; 1050 1051 void TestHashCompareConstructors() { 1052 using key_type = int; 1053 using map_type = tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>; 1054 1055 non_default_constructible_hash_compare<key_type> compare(0); 1056 map_type::allocator_type allocator; 1057 1058 map_type map1(compare); 1059 map_type map2(compare, allocator); 1060 1061 map_type map3(1, compare); 1062 map_type map4(1, compare, allocator); 1063 1064 std::vector<map_type::value_type> reference_vector; 1065 map_type map5(reference_vector.begin(), reference_vector.end(), compare); 1066 map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator); 1067 1068 map_type map7({}, compare); 1069 map_type map8({}, compare, allocator); 1070 } 1071 1072 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1073 template <typename T> 1074 struct debug_hash_compare : public tbb::detail::d1::tbb_hash_compare<T> {}; 1075 1076 template <template <typename...> typename TMap> 1077 void TestDeductionGuides() { 1078 using Key = int; 1079 using Value = std::string; 1080 1081 using ComplexType = std::pair<Key, Value>; 1082 using ComplexTypeConst = std::pair<const Key, Value>; 1083 1084 using DefaultCompare = tbb::detail::d1::tbb_hash_compare<Key>; 1085 using Compare = debug_hash_compare<Key>; 1086 using DefaultAllocator = tbb::tbb_allocator<ComplexTypeConst>; 1087 using Allocator = std::allocator<ComplexTypeConst>; 1088 1089 std::vector<ComplexType> v; 1090 auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") }; 1091 Compare compare; 1092 Allocator allocator; 1093 1094 // check TMap(InputIterator, InputIterator) 1095 TMap m1(v.begin(), v.end()); 1096 static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value); 1097 1098 // check TMap(InputIterator, InputIterator, HashCompare) 1099 TMap m2(v.begin(), v.end(), compare); 1100 static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value); 1101 1102 // check TMap(InputIterator, InputIterator, HashCompare, Allocator) 1103 TMap m3(v.begin(), v.end(), compare, allocator); 1104 static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value); 1105 1106 // check TMap(InputIterator, InputIterator, Allocator) 1107 TMap m4(v.begin(), v.end(), allocator); 1108 static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value); 1109 1110 // check TMap(std::initializer_list) 1111 TMap m5(l); 1112 static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value); 1113 1114 // check TMap(std::initializer_list, HashCompare) 1115 TMap m6(l, compare); 1116 static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value); 1117 1118 // check TMap(std::initializer_list, HashCompare, Allocator) 1119 TMap m7(l, compare, allocator); 1120 static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value); 1121 1122 // check TMap(std::initializer_list, Allocator) 1123 TMap m8(l, allocator); 1124 static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value); 1125 1126 // check TMap(TMap &) 1127 TMap m9(m1); 1128 static_assert(std::is_same<decltype(m9), decltype(m1)>::value); 1129 1130 // check TMap(TMap &, Allocator) 1131 TMap m10(m4, allocator); 1132 static_assert(std::is_same<decltype(m10), decltype(m4)>::value); 1133 1134 // check TMap(TMap &&) 1135 TMap m11(std::move(m1)); 1136 static_assert(std::is_same<decltype(m11), decltype(m1)>::value); 1137 1138 // check TMap(TMap &&, Allocator) 1139 TMap m12(std::move(m4), allocator); 1140 static_assert(std::is_same<decltype(m12), decltype(m4)>::value); 1141 } 1142 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1143 1144 //! Test consruction with hash_compare 1145 //! \brief \ref interface \ref requirement 1146 TEST_CASE("testing consruction with hash_compare") { 1147 TestHashCompareConstructors(); 1148 } 1149 1150 //! Test concurrent_hash_map member types 1151 //! \brief \ref interface \ref requirement 1152 TEST_CASE("test types"){ 1153 test_member_types<tbb::concurrent_hash_map>(); 1154 } 1155 1156 //! Test swap and clear operations 1157 //! \brief \ref interface \ref requirement 1158 TEST_CASE("test copy operations") { 1159 TestCopy(); 1160 } 1161 1162 //! Test rehash operation 1163 //! \brief \ref interface \ref requirement 1164 TEST_CASE("test rehash operation") { 1165 TestRehash(); 1166 } 1167 1168 //! Test assignment operation 1169 //! \brief \ref interface \ref requirement 1170 TEST_CASE("test assignment operation") { 1171 TestAssignment(); 1172 } 1173 1174 //! Test iterators and ranges 1175 //! \brief \ref interface \ref requirement 1176 TEST_CASE("test iterators and ranges") { 1177 TestIteratorsAndRanges(); 1178 } 1179 1180 //! Test work with initializer_list 1181 //! \brief \ref interface \ref requirement 1182 TEST_CASE("test work with initializer_list") { 1183 TestInitList(); 1184 } 1185 1186 #if TBB_USE_EXCEPTIONS 1187 //! Test exception safety 1188 //! \brief \ref requirement 1189 TEST_CASE("test exception safety") { 1190 TestExceptions(); 1191 } 1192 1193 //! Test exceptions safety guarantees for move constructor 1194 //! \brief \ref requirement 1195 TEST_CASE("test move support with exceptions") { 1196 move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>(); 1197 move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>(); 1198 } 1199 #endif 1200 1201 //! Test move constructor 1202 //! \brief \ref interface \ref requirement 1203 TEST_CASE("testing move constructor"){ 1204 move_support_tests::test_move_constructor<hash_map_traits>(); 1205 } 1206 1207 //! Test move assign operator 1208 //! \brief \ref interface \ref requirement 1209 TEST_CASE("testing move assign operator"){ 1210 move_support_tests::test_move_assignment<hash_map_traits>(); 1211 } 1212 1213 //! Test insert and empace 1214 //! \brief \ref interface \ref requirement 1215 TEST_CASE("testing concurrency"){ 1216 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, 1); 1217 int n=250000; 1218 { 1219 DataStateTrackedTable table; 1220 DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 ); 1221 } 1222 { 1223 DataStateTrackedTable table; 1224 DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 ); 1225 } 1226 } 1227 1228 //! Test allocator traits 1229 //! \brief \ref requirement 1230 TEST_CASE("testing allocator traits") { 1231 test_allocator_traits_support<hash_map_traits>(); 1232 } 1233 1234 //! Test concurrent operations 1235 //! \brief \ref requirement 1236 TEST_CASE("testing concurrency"){ 1237 for (std::size_t p = 1; p <= 4; ++p) { 1238 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 1239 TestInsertFindErase(p); 1240 TestConcurrency(p); 1241 } 1242 } 1243 1244 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1245 //! Test deduction guides 1246 //! \brief \ref interface 1247 TEST_CASE("testing deduction guides") { 1248 TestDeductionGuides<tbb::concurrent_hash_map>(); 1249 } 1250 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 1251