1 /* 2 Copyright (c) 2005-2021 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 #include "common/test.h" 18 #include "common/utils.h" 19 #include "common/utils_report.h" 20 #include "common/spin_barrier.h" 21 #include "common/checktype.h" 22 23 #include "common/container_move_support.h" 24 25 #include "oneapi/tbb/combinable.h" 26 #include "oneapi/tbb/parallel_for.h" 27 #include "oneapi/tbb/blocked_range.h" 28 #include "oneapi/tbb/global_control.h" 29 #include "oneapi/tbb/tbb_allocator.h" 30 // INFO: #include "oneapi/tbb/tick_count.h" 31 32 #include <cstring> 33 #include <vector> 34 #include <utility> 35 36 //! \file conformance_combinable.cpp 37 //! \brief Test for [tls.combinable] specification 38 39 //! Minimum number of threads 40 const int MinThread = 1; 41 42 //! Maximum number of threads 43 const int MaxThread = 4; 44 45 static std::atomic<int> construction_counter; 46 static std::atomic<int> destruction_counter; 47 48 const int REPETITIONS = 10; 49 const int N = 100000; 50 const double EXPECTED_SUM = (REPETITIONS + 1) * N; 51 52 // 53 // A minimal class 54 // Define: default and copy constructor, and allow implicit operator& 55 // also operator= 56 // 57 58 class minimal { 59 private: 60 int my_value; 61 public: 62 minimal(int val=0) : my_value(val) { ++construction_counter; } 63 minimal( const minimal &m ) : my_value(m.my_value) { ++construction_counter; } 64 minimal& operator=(const minimal& other) { my_value = other.my_value; return *this; } 65 minimal& operator+=(const minimal& other) { my_value += other.my_value; return *this; } 66 operator int() const { return my_value; } 67 ~minimal() { ++destruction_counter; } 68 void set_value( const int i ) { my_value = i; } 69 int value( ) const { return my_value; } 70 }; 71 72 //// functors for initialization and combine 73 74 template <typename T> 75 struct FunctorAddFinit { 76 T operator()() { return 0; } 77 }; 78 79 template <typename T> 80 struct FunctorAddFinit7 { 81 T operator()() { return 7; } 82 }; 83 84 template <typename T> 85 struct FunctorAddCombine { 86 T operator()(T left, T right ) const { 87 return left + right; 88 } 89 }; 90 91 template <typename T> 92 struct FunctorAddCombineRef { 93 T operator()(const T& left, const T& right ) const { 94 return left + right; 95 } 96 }; 97 98 template <typename T> 99 T my_combine( T left, T right) { return left + right; } 100 101 template <typename T> 102 T my_combine_ref( const T &left, const T &right) { return left + right; } 103 104 template <typename T> 105 class CombineEachHelper { 106 public: 107 CombineEachHelper(T& _result) : my_result(_result) {} 108 void operator()(const T& new_bit) { my_result += new_bit; } 109 private: 110 T& my_result; 111 }; 112 113 template <typename T> 114 class CombineEachHelperCnt { 115 public: 116 CombineEachHelperCnt(T& _result, int& _nbuckets) : my_result(_result), nBuckets(_nbuckets) {} 117 void operator()(const T& new_bit) { my_result += new_bit; ++nBuckets; } 118 private: 119 T& my_result; 120 int& nBuckets; 121 }; 122 123 template <typename T> 124 class CombineEachVectorHelper { 125 public: 126 typedef std::vector<T, oneapi::tbb::tbb_allocator<T> > ContainerType; 127 CombineEachVectorHelper(T& _result) : my_result(_result) { } 128 void operator()(const ContainerType& new_bit) { 129 for(typename ContainerType::const_iterator ci = new_bit.begin(); ci != new_bit.end(); ++ci) { 130 my_result += *ci; 131 } 132 } 133 134 private: 135 T& my_result; 136 }; 137 138 //// end functors 139 140 // parallel body with a test for first access 141 template <typename T> 142 class ParallelScalarBody: utils::NoAssign { 143 144 oneapi::tbb::combinable<T> &sums; 145 146 public: 147 148 ParallelScalarBody ( oneapi::tbb::combinable<T> &_sums ) : sums(_sums) { } 149 150 void operator()( const oneapi::tbb::blocked_range<int> &r ) const { 151 for (int i = r.begin(); i != r.end(); ++i) { 152 bool was_there; 153 T& my_local = sums.local(was_there); 154 if(!was_there) my_local = 0; 155 my_local += 1 ; 156 } 157 } 158 159 }; 160 161 // parallel body with no test for first access 162 template <typename T> 163 class ParallelScalarBodyNoInit: utils::NoAssign { 164 165 oneapi::tbb::combinable<T> &sums; 166 167 public: 168 169 ParallelScalarBodyNoInit ( oneapi::tbb::combinable<T> &_sums ) : sums(_sums) { } 170 171 void operator()( const oneapi::tbb::blocked_range<int> &r ) const { 172 for (int i = r.begin(); i != r.end(); ++i) { 173 sums.local() += 1 ; 174 } 175 } 176 177 }; 178 179 template< typename T > 180 void RunParallelScalarTests(const char* /* test_name */) { 181 for (int p = MinThread; p <= MaxThread; ++p) { 182 183 if (p == 0) continue; 184 // REMARK(" Testing parallel %s on %d thread(s)...\n", test_name, p); 185 oneapi::tbb::global_control gc(oneapi::tbb::global_control::max_allowed_parallelism, p); 186 187 // INFO: oneapi::tbb::tick_count t0; 188 T combine_sum(0); 189 T combine_ref_sum(0); 190 T combine_finit_sum(0); 191 T combine_each_sum(0); 192 T copy_construct_sum(0); 193 T copy_assign_sum(0); 194 T move_construct_sum(0); 195 T move_assign_sum(0); 196 197 for (int t = -1; t < REPETITIONS; ++t) { 198 // INFO: if (Verbose && t == 0) t0 = oneapi::tbb::tick_count::now(); 199 200 // test uninitialized parallel combinable 201 oneapi::tbb::combinable<T> sums; 202 oneapi::tbb::parallel_for( oneapi::tbb::blocked_range<int>( 0, N, 10000 ), ParallelScalarBody<T>( sums ) ); 203 combine_sum += sums.combine(my_combine<T>); 204 combine_ref_sum += sums.combine(my_combine_ref<T>); 205 206 // test combinable::clear() 207 oneapi::tbb::combinable<T> sums_to_clear; 208 oneapi::tbb::parallel_for( oneapi::tbb::blocked_range<int>(0, N, 10000), ParallelScalarBody<T>(sums_to_clear) ); 209 sums_to_clear.clear(); 210 CHECK_MESSAGE(sums_to_clear.combine(my_combine<T>) == 0, "Failed combinable::clear test"); 211 212 // test parallel combinable preinitialized with a functor that returns 0 213 FunctorAddFinit<T> my_finit_decl; 214 oneapi::tbb::combinable<T> finit_combinable(my_finit_decl); 215 oneapi::tbb::parallel_for( oneapi::tbb::blocked_range<int>( 0, N, 10000 ), ParallelScalarBodyNoInit<T>( finit_combinable ) ); 216 combine_finit_sum += finit_combinable.combine(my_combine<T>); 217 218 // test another way of combining the elements using CombineEachHelper<T> functor 219 CombineEachHelper<T> my_helper(combine_each_sum); 220 sums.combine_each(my_helper); 221 222 // test copy constructor for parallel combinable 223 oneapi::tbb::combinable<T> copy_constructed(sums); 224 copy_construct_sum += copy_constructed.combine(my_combine<T>); 225 226 // test copy assignment for uninitialized parallel combinable 227 oneapi::tbb::combinable<T> assigned; 228 assigned = sums; 229 copy_assign_sum += assigned.combine(my_combine<T>); 230 231 // test move constructor for parallel combinable 232 oneapi::tbb::combinable<T> moved1(std::move(sums)); 233 move_construct_sum += moved1.combine(my_combine<T>); 234 235 // test move assignment for uninitialized parallel combinable 236 oneapi::tbb::combinable<T> moved2; 237 moved2=std::move(finit_combinable); 238 move_assign_sum += moved2.combine(my_combine<T>); 239 } 240 // Here and below comparison for equality of float numbers succeeds 241 // as the rounding error doesn't accumulate and doesn't affect the comparison 242 REQUIRE( EXPECTED_SUM == combine_sum ); 243 REQUIRE( EXPECTED_SUM == combine_ref_sum ); 244 REQUIRE( EXPECTED_SUM == combine_finit_sum ); 245 REQUIRE( EXPECTED_SUM == combine_each_sum ); 246 REQUIRE( EXPECTED_SUM == copy_construct_sum ); 247 REQUIRE( EXPECTED_SUM == copy_assign_sum ); 248 REQUIRE( EXPECTED_SUM == move_construct_sum ); 249 REQUIRE( EXPECTED_SUM == move_assign_sum ); 250 // REMARK(" done parallel %s, %d, %g, %g\n", test_name, p, static_cast<double>(combine_sum), ( oneapi::tbb::tick_count::now() - t0).seconds()); 251 } 252 } 253 254 template <typename T> 255 class ParallelVectorForBody: utils::NoAssign { 256 257 oneapi::tbb::combinable< std::vector<T, oneapi::tbb::tbb_allocator<T> > > &locals; 258 259 public: 260 261 ParallelVectorForBody ( oneapi::tbb::combinable< std::vector<T, oneapi::tbb::tbb_allocator<T> > > &_locals ) : locals(_locals) { } 262 263 void operator()( const oneapi::tbb::blocked_range<int> &r ) const { 264 T one = 1; 265 266 for (int i = r.begin(); i < r.end(); ++i) { 267 locals.local().push_back( one ); 268 } 269 } 270 271 }; 272 273 template< typename T > 274 void RunParallelVectorTests(const char* /* test_name */) { 275 typedef std::vector<T, oneapi::tbb::tbb_allocator<T> > ContainerType; 276 277 for (int p = MinThread; p <= MaxThread; ++p) { 278 279 if (p == 0) continue; 280 // REMARK(" Testing parallel %s on %d thread(s)... \n", test_name, p); 281 oneapi::tbb::global_control gc(oneapi::tbb::global_control::max_allowed_parallelism, p); 282 283 // INFO: oneapi::tbb::tick_count t0; 284 T defaultConstructed_sum(0); 285 T copyConstructed_sum(0); 286 T copyAssigned_sum(0); 287 T moveConstructed_sum(0); 288 T moveAssigned_sum(0); 289 290 for (int t = -1; t < REPETITIONS; ++t) { 291 // if (Verbose && t == 0) t0 = oneapi::tbb::tick_count::now(); 292 293 typedef typename oneapi::tbb::combinable< ContainerType > CombinableType; 294 295 // test uninitialized parallel combinable 296 CombinableType vs; 297 oneapi::tbb::parallel_for( oneapi::tbb::blocked_range<int> (0, N, 10000), ParallelVectorForBody<T>( vs ) ); 298 CombineEachVectorHelper<T> MyCombineEach(defaultConstructed_sum); 299 vs.combine_each(MyCombineEach); // combine_each sums all elements of each vector into the result 300 301 // test copy constructor for parallel combinable with vectors 302 CombinableType vs2(vs); 303 CombineEachVectorHelper<T> MyCombineEach2(copyConstructed_sum); 304 vs2.combine_each(MyCombineEach2); 305 306 // test copy assignment for uninitialized parallel combinable with vectors 307 CombinableType vs3; 308 vs3 = vs; 309 CombineEachVectorHelper<T> MyCombineEach3(copyAssigned_sum); 310 vs3.combine_each(MyCombineEach3); 311 312 // test move constructor for parallel combinable with vectors 313 CombinableType vs4(std::move(vs2)); 314 CombineEachVectorHelper<T> MyCombineEach4(moveConstructed_sum); 315 vs4.combine_each(MyCombineEach4); 316 317 // test move assignment for uninitialized parallel combinable with vectors 318 vs4=std::move(vs3); 319 CombineEachVectorHelper<T> MyCombineEach5(moveAssigned_sum); 320 vs4.combine_each(MyCombineEach5); 321 } 322 323 double ResultValue = defaultConstructed_sum; 324 REQUIRE( EXPECTED_SUM == ResultValue ); 325 ResultValue = copyConstructed_sum; 326 REQUIRE( EXPECTED_SUM == ResultValue ); 327 ResultValue = copyAssigned_sum; 328 REQUIRE( EXPECTED_SUM == ResultValue ); 329 ResultValue = moveConstructed_sum; 330 REQUIRE( EXPECTED_SUM == ResultValue ); 331 ResultValue = moveAssigned_sum; 332 REQUIRE( EXPECTED_SUM == ResultValue ); 333 334 // REMARK(" done parallel %s, %d, %g, %g\n", test_name, p, ResultValue, ( oneapi::tbb::tick_count::now() - t0).seconds()); 335 } 336 } 337 338 void 339 RunParallelTests() { 340 // REMARK("Running RunParallelTests\n"); 341 RunParallelScalarTests<int>("int"); 342 RunParallelScalarTests<double>("double"); 343 RunParallelScalarTests<minimal>("minimal"); 344 RunParallelVectorTests<int>("std::vector<int, oneapi::tbb::tbb_allocator<int> >"); 345 RunParallelVectorTests<double>("std::vector<double, oneapi::tbb::tbb_allocator<double> >"); 346 } 347 348 template <typename T> 349 void 350 RunAssignmentAndCopyConstructorTest(const char* /* test_name */) { 351 // REMARK(" Testing assignment and copy construction for combinable<%s>...\n", test_name); 352 353 // test creation with finit function (combine returns finit return value if no threads have created locals) 354 FunctorAddFinit7<T> my_finit7_decl; 355 oneapi::tbb::combinable<T> create1(my_finit7_decl); 356 REQUIRE_MESSAGE(7 == create1.combine(my_combine<T>), "Unexpected combine result for combinable object preinitialized with functor"); 357 358 // test copy construction with function initializer 359 oneapi::tbb::combinable<T> copy1(create1); 360 REQUIRE_MESSAGE(7 == copy1.combine(my_combine<T>), "Unexpected combine result for copy-constructed combinable object"); 361 362 // test copy assignment with function initializer 363 FunctorAddFinit<T> my_finit_decl; 364 oneapi::tbb::combinable<T> assign1(my_finit_decl); 365 assign1 = create1; 366 REQUIRE_MESSAGE(7 == assign1.combine(my_combine<T>), "Unexpected combine result for copy-assigned combinable object"); 367 368 // test move construction with function initializer 369 oneapi::tbb::combinable<T> move1(std::move(create1)); 370 REQUIRE_MESSAGE(7 == move1.combine(my_combine<T>), "Unexpected combine result for move-constructed combinable object"); 371 372 // test move assignment with function initializer 373 oneapi::tbb::combinable<T> move2; 374 move2=std::move(copy1); 375 REQUIRE_MESSAGE(7 == move2.combine(my_combine<T>), "Unexpected combine result for move-assigned combinable object"); 376 377 // REMARK(" done\n"); 378 379 } 380 381 void RunAssignmentAndCopyConstructorTests() { 382 // REMARK("Running assignment and copy constructor tests:\n"); 383 RunAssignmentAndCopyConstructorTest<int>("int"); 384 RunAssignmentAndCopyConstructorTest<double>("double"); 385 RunAssignmentAndCopyConstructorTest<minimal>("minimal"); 386 } 387 388 void RunMoveSemanticsForStateTrackableObjectTest() { 389 // REMARK("Testing move assignment and move construction for combinable<Harness::StateTrackable>...\n"); 390 391 oneapi::tbb::combinable< StateTrackable<true> > create1; 392 REQUIRE_MESSAGE(create1.local().state == StateTrackable<true>::DefaultInitialized, 393 "Unexpected value in default combinable object"); 394 395 // Copy constructing of the new combinable causes copying of stored values 396 oneapi::tbb::combinable< StateTrackable<true> > copy1(create1); 397 REQUIRE_MESSAGE(copy1.local().state == StateTrackable<true>::CopyInitialized, 398 "Unexpected value in copy-constructed combinable object"); 399 400 // Copy assignment also causes copying of stored values 401 oneapi::tbb::combinable< StateTrackable<true> > copy2; 402 REQUIRE_MESSAGE(copy2.local().state == StateTrackable<true>::DefaultInitialized, 403 "Unexpected value in default combinable object"); 404 copy2=create1; 405 REQUIRE_MESSAGE(copy2.local().state == StateTrackable<true>::CopyInitialized, 406 "Unexpected value in copy-assigned combinable object"); 407 408 // Store some marked values in the initial combinable object 409 create1.local().state = StateTrackableBase::Unspecified; 410 411 // Move constructing of the new combinable must not cause copying of stored values 412 oneapi::tbb::combinable< StateTrackable<true> > move1(std::move(create1)); 413 REQUIRE_MESSAGE(move1.local().state == StateTrackableBase::Unspecified, "Unexpected value in move-constructed combinable object"); 414 415 // Move assignment must not cause copying of stored values 416 copy1=std::move(move1); 417 REQUIRE_MESSAGE(copy1.local().state == StateTrackableBase::Unspecified, "Unexpected value in move-assigned combinable object"); 418 419 // Make the stored values valid again in order to delete StateTrackable object correctly 420 copy1.local().state = StateTrackable<true>::MoveAssigned; 421 422 // REMARK("done\n"); 423 } 424 425 utils::SpinBarrier sBarrier; 426 427 struct Body : utils::NoAssign { 428 oneapi::tbb::combinable<int>* locals; 429 const int nthread; 430 const int nIters; 431 Body( int nthread_, int niters_ ) : nthread(nthread_), nIters(niters_) { sBarrier.initialize(nthread_); } 432 433 void operator()(int thread_id ) const { 434 bool existed; 435 sBarrier.wait(); 436 for(int i = 0; i < nIters; ++i ) { 437 existed = thread_id & 1; 438 int oldval = locals->local(existed); 439 REQUIRE_MESSAGE(existed == (i > 0), "Error on first reference"); 440 REQUIRE_MESSAGE((!existed || (oldval == thread_id)), "Error on fetched value"); 441 existed = thread_id & 1; 442 locals->local(existed) = thread_id; 443 REQUIRE_MESSAGE(existed, "Error on assignment"); 444 } 445 } 446 }; 447 448 void TestLocalAllocations( int nthread ) { 449 REQUIRE_MESSAGE(nthread > 0, "nthread must be positive"); 450 #define NITERATIONS 1000 451 Body myBody(nthread, NITERATIONS); 452 oneapi::tbb::combinable<int> myCombinable; 453 myBody.locals = &myCombinable; 454 455 NativeParallelFor( nthread, myBody ); 456 457 int mySum = 0; 458 int mySlots = 0; 459 CombineEachHelperCnt<int> myCountCombine(mySum, mySlots); 460 myCombinable.combine_each(myCountCombine); 461 462 REQUIRE_MESSAGE(nthread == mySlots, "Incorrect number of slots"); 463 REQUIRE_MESSAGE(mySum == (nthread - 1) * nthread / 2, "Incorrect values in result"); 464 } 465 466 void RunLocalAllocationsTests() { 467 // REMARK("Testing local() allocations\n"); 468 for(int i = 1 <= MinThread ? MinThread : 1; i <= MaxThread; ++i) { 469 // REMARK(" Testing local() allocation with nthreads=%d...\n", i); 470 for(int j = 0; j < 100; ++j) { 471 TestLocalAllocations(i); 472 } 473 // REMARK(" done\n"); 474 } 475 } 476 477 //! Test combinable in parallel algorithms 478 //! \brief \ref interface \ref requirement 479 TEST_CASE("Parallel scenario") { 480 RunParallelTests(); 481 RunLocalAllocationsTests(); 482 } 483 484 //! Test assignment and copy construction 485 //! \brief \ref interface \ref requirement 486 TEST_CASE("Assignment and copy constructor test") { 487 RunAssignmentAndCopyConstructorTests(); 488 } 489 490 //! Test move support 491 //! \brief \ref interface \ref requirement 492 TEST_CASE("Move semantics") { 493 RunMoveSemanticsForStateTrackableObjectTest(); 494 } 495 496