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 #if __INTEL_COMPILER && _MSC_VER 18 #pragma warning(disable : 2586) // decorated name length exceeded, name was truncated 19 #endif 20 21 #include "common/test.h" 22 #include "common/config.h" 23 #include "common/utils_concurrency_limit.h" 24 #include "common/cpu_usertime.h" 25 #include "common/concepts_common.h" 26 27 #include "tbb/global_control.h" 28 #include "tbb/parallel_scan.h" 29 #include "tbb/blocked_range.h" 30 #include "tbb/tick_count.h" 31 #include <vector> 32 #include <atomic> 33 34 //! \file test_parallel_scan.cpp 35 //! \brief Test for [algorithms.parallel_scan] specification 36 37 using Range = tbb::blocked_range<long>; 38 39 static volatile bool ScanIsRunning = false; 40 41 //! Sum of 0..i with wrap around on overflow. 42 inline int TriangularSum( int i ) { 43 return i&1 ? ((i>>1)+1)*i : (i>>1)*(i+1); 44 } 45 46 //! Verify that sum is init plus sum of integers in closed interval [0..finish_index]. 47 /** line should be the source line of the caller */ 48 void VerifySum( int init, long finish_index, int sum, int line ) { 49 int expected = init + TriangularSum(finish_index); 50 CHECK_MESSAGE(expected == sum, "line " << line << ": sum[0.." << finish_index << "] should be = " << expected << ", but was computed as " << sum << "\n"); 51 } 52 53 const int MAXN = 20000; 54 55 enum AddendFlag { 56 UNUSED=0, 57 USED_NONFINAL=1, 58 USED_FINAL=2 59 }; 60 61 //! Array recording how each addend was used. 62 /** 'unsigned char' instead of AddendFlag for sake of compactness. */ 63 static unsigned char AddendHistory[MAXN]; 64 65 std::atomic<long> NumberOfLiveStorage; 66 67 template<typename T> 68 struct Storage { 69 T my_total; 70 Range my_range; 71 Storage(T init) : 72 my_total(init), my_range(-1, -1, 1) { 73 ++NumberOfLiveStorage; 74 } 75 ~Storage() { 76 --NumberOfLiveStorage; 77 } 78 Storage(const Storage& strg) : 79 my_total(strg.my_total), my_range(strg.my_range) { 80 ++NumberOfLiveStorage; 81 } 82 Storage & operator=(const Storage& strg) { 83 my_total = strg.my_total; 84 my_range = strg.my_range; 85 return *this; 86 } 87 }; 88 89 template<typename T> 90 Storage<T> JoinStorages(const Storage<T>& left, const Storage<T>& right) { 91 Storage<T> result = right; 92 CHECK(ScanIsRunning); 93 CHECK(left.my_range.end() == right.my_range.begin()); 94 result.my_total += left.my_total; 95 result.my_range = Range(left.my_range.begin(), right.my_range.end(), 1); 96 CHECK(ScanIsRunning); 97 return result; 98 } 99 100 template<typename T> 101 void Scan(const Range & r, bool is_final, Storage<T> & storage, std::vector<T> & sum, const std::vector<T> & addend) { 102 CHECK((!is_final || (storage.my_range.begin() == 0 && storage.my_range.end() == r.begin()) || (storage.my_range.empty() && r.begin() == 0))); 103 for (long i = r.begin(); i < r.end(); ++i) { 104 storage.my_total += addend[i]; 105 if (is_final) { 106 CHECK_MESSAGE(AddendHistory[i] < USED_FINAL, "addend used 'finally' twice?"); 107 AddendHistory[i] |= USED_FINAL; 108 sum[i] = storage.my_total; 109 VerifySum(42, i, int(sum[i]), __LINE__); 110 } 111 else { 112 CHECK_MESSAGE(AddendHistory[i] == UNUSED, "addend used too many times"); 113 AddendHistory[i] |= USED_NONFINAL; 114 } 115 } 116 if (storage.my_range.empty()) 117 storage.my_range = r; 118 else 119 storage.my_range = Range(storage.my_range.begin(), r.end(), 1); 120 } 121 122 template<typename T> 123 Storage<T> ScanWithInit(const Range & r, T init, bool is_final, Storage<T> & storage, std::vector<T> & sum, const std::vector<T> & addend) { 124 if (r.begin() == 0) 125 storage.my_total = init; 126 Scan(r, is_final, storage, sum, addend); 127 return storage; 128 } 129 130 template<typename T> 131 class Accumulator { 132 const std::vector<T> &my_array; 133 std::vector<T> & my_sum; 134 Storage<T> storage; 135 enum state_type { 136 full, // Accumulator has sufficient information for final scan, 137 // i.e. has seen all iterations to its left. 138 // It's either the original Accumulator provided by the user 139 // or a Accumulator constructed by a splitting constructor *and* subsequently 140 // subjected to a reverse_join with a full accumulator. 141 142 partial, // Accumulator has only enough information for pre_scan. 143 // i.e. has not seen all iterations to its left. 144 // It's an Accumulator created by a splitting constructor that 145 // has not yet been subjected to a reverse_join with a full accumulator. 146 147 summary, // Accumulator has summary of iterations processed, but not necessarily 148 // the information required for a final_scan or pre_scan. 149 // It's the result of "assign". 150 151 trash // Accumulator with possibly no useful information. 152 // It was the source for "assign". 153 154 }; 155 mutable state_type my_state; 156 //! Equals this while object is fully constructed, NULL otherwise. 157 /** Used to detect premature destruction and accidental bitwise copy. */ 158 Accumulator* self; 159 Accumulator& operator= (const Accumulator& other); 160 public: 161 Accumulator( T init, const std::vector<T> & array, std::vector<T> & sum ) : 162 my_array(array), my_sum(sum), storage(init), my_state(full) 163 { 164 // Set self as last action of constructor, to indicate that object is fully constructed. 165 self = this; 166 } 167 ~Accumulator() { 168 // Clear self as first action of destructor, to indicate that object is not fully constructed. 169 self = 0; 170 } 171 Accumulator( Accumulator& a, tbb::split ) : 172 my_array(a.my_array), my_sum(a.my_sum), storage(0), my_state(partial) 173 { 174 if (!(a.my_state == partial)) 175 CHECK(a.my_state == full); 176 if (!(a.my_state == full)) 177 CHECK(a.my_state == partial); 178 CHECK(ScanIsRunning); 179 // Set self as last action of constructor, to indicate that object is fully constructed. 180 self = this; 181 } 182 template<typename Tag> 183 void operator()( const Range& r, Tag /*tag*/ ) { 184 if(Tag::is_final_scan()) 185 CHECK(my_state == full); 186 else 187 CHECK(my_state == partial); 188 Scan(r, Tag::is_final_scan(), storage, my_sum, my_array); 189 CHECK_MESSAGE(self==this, "this Accumulator corrupted or prematurely destroyed"); 190 } 191 void reverse_join( const Accumulator& left_body) { 192 const Storage<T> & left = left_body.storage; 193 Storage<T> & right = storage; 194 CHECK(my_state == partial); 195 CHECK( ((left_body.my_state == full) || (left_body.my_state==partial)) ); 196 197 right = JoinStorages(left, right); 198 199 CHECK(left_body.self == &left_body); 200 my_state = left_body.my_state; 201 } 202 void assign( const Accumulator& other ) { 203 CHECK(other.my_state == full); 204 CHECK(my_state == full); 205 storage.my_total = other.storage.my_total; 206 storage.my_range = other.storage.my_range; 207 CHECK(self == this); 208 CHECK_MESSAGE(other.self==&other, "other Accumulator corrupted or prematurely destroyed"); 209 my_state = summary; 210 other.my_state = trash; 211 } 212 T get_total() { 213 return storage.my_total; 214 } 215 }; 216 217 218 template<typename T, typename Scan, typename ReverseJoin> 219 T ParallelScanFunctionalInvoker(const Range& range, T idx, const Scan& scan, const ReverseJoin& reverse_join, int mode) { 220 switch (mode%3) { 221 case 0: 222 return tbb::parallel_scan(range, idx, scan, reverse_join); 223 break; 224 case 1: 225 return tbb::parallel_scan(range, idx, scan, reverse_join, tbb::simple_partitioner()); 226 break; 227 default: 228 return tbb::parallel_scan(range, idx, scan, reverse_join, tbb::auto_partitioner()); 229 } 230 } 231 232 template<typename T> 233 class ScanBody { 234 const std::vector<T> &my_addend; 235 std::vector<T> &my_sum; 236 const T my_init; 237 ScanBody& operator= (const ScanBody&); 238 public: 239 ScanBody(T init, const std::vector<T> &addend, std::vector<T> &sum) :my_addend(addend), my_sum(sum), my_init(init) {} 240 template<typename S, typename Tag> 241 Storage<S> operator()(const Range& r, Storage<S> storage, Tag) const { 242 return ScanWithInit(r, my_init, Tag::is_final_scan(), storage, my_sum, my_addend); 243 } 244 }; 245 246 class JoinBody { 247 public: 248 template<typename T> 249 Storage<T> operator()(const Storage<T>& left, const Storage<T>& right) const { 250 return JoinStorages(left, right); 251 } 252 }; 253 254 struct ParallelScanTemplateFunctor { 255 template<typename T> 256 T operator()(Range range, T init, const std::vector<T> &addend, std::vector<T> &sum, int mode) { 257 for (long i = 0; i<MAXN; ++i) { 258 AddendHistory[i] = UNUSED; 259 } 260 ScanIsRunning = true; 261 ScanBody<T> sb(init, addend, sum); 262 JoinBody jb; 263 Storage<T> res = ParallelScanFunctionalInvoker(range, Storage<T>(0), sb, jb, mode); 264 ScanIsRunning = false; 265 if (range.empty()) 266 res.my_total = init; 267 return res.my_total; 268 } 269 }; 270 271 struct ParallelScanLambda { 272 template<typename T> 273 T operator()(Range range, T init, const std::vector<T> &addend, std::vector<T> &sum, int mode) { 274 for (long i = 0; i<MAXN; ++i) { 275 AddendHistory[i] = UNUSED; 276 } 277 ScanIsRunning = true; 278 Storage<T> res = ParallelScanFunctionalInvoker(range, Storage<T>(0), 279 [&addend, &sum, init](const Range& r, Storage<T> storage, bool is_final_scan /*tag*/) -> Storage<T> { 280 return ScanWithInit(r, init, is_final_scan, storage, sum, addend); 281 }, 282 [](const Storage<T>& left, const Storage<T>& right) -> Storage<T> { 283 return JoinStorages(left, right); 284 }, 285 mode); 286 ScanIsRunning = false; 287 if (range.empty()) 288 res.my_total = init; 289 return res.my_total; 290 } 291 }; 292 293 void TestAccumulator( int mode ) { 294 typedef int T; 295 std::vector<T> addend(MAXN); 296 std::vector<T> sum(MAXN); 297 std::vector<T> control_sum(MAXN); 298 T control_total; 299 for( int n=0; n<=MAXN; n = n <=128? n+1: n*3) { 300 for( int gs : {1, 2, 100, 511, 12345, n/ 111, n/17, n-1, n}) { 301 if(gs<=0 || gs > n) 302 continue; 303 control_total = 42; 304 for( long i=0; i<MAXN; ++i ) { 305 addend[i] = -1; 306 sum[i] = -2; 307 control_sum[i] = -2; 308 AddendHistory[i] = UNUSED; 309 } 310 for (long i = 0; i<n; ++i) { 311 addend[i] = i; 312 control_total += addend[i]; 313 control_sum[i] = control_total; 314 } 315 316 Accumulator<T> acc( 42, addend, sum); 317 ScanIsRunning = true; 318 319 switch (mode) { 320 case 0: 321 tbb::parallel_scan( Range( 0, n, gs ), acc ); 322 break; 323 case 1: 324 tbb::parallel_scan( Range( 0, n, gs ), acc, tbb::simple_partitioner() ); 325 break; 326 case 2: 327 tbb::parallel_scan( Range( 0, n, gs ), acc, tbb::auto_partitioner() ); 328 break; 329 } 330 331 ScanIsRunning = false; 332 333 for( long i=0; i<n; ++i ) 334 CHECK_MESSAGE((AddendHistory[i]&USED_FINAL), "failed to use addend[" << i << "] " << (AddendHistory[i] & USED_NONFINAL ? "(but used nonfinal)\n" : "\n")); 335 for( long i=0; i<n; ++i ) { 336 VerifySum( 42, i, sum[i], __LINE__ ); 337 } 338 if( n ) 339 CHECK(acc.get_total()==sum[n-1]); 340 else 341 CHECK(acc.get_total()==42); 342 CHECK(control_total ==acc.get_total()); 343 CHECK(control_sum==sum); 344 } 345 } 346 } 347 348 template<typename ParallelScanWrapper> 349 void TestInterface( int mode, ParallelScanWrapper parallel_scan_wrapper ) { 350 using T = int; 351 std::vector<T> addend(MAXN); 352 std::vector<T> control_sum(MAXN); 353 T control_total(42); 354 for( long i=0; i<MAXN; ++i ) { 355 addend[i] = i; 356 control_total += addend[i]; 357 control_sum[i] = control_total; 358 AddendHistory[i] = UNUSED; 359 } 360 361 std::vector<T> sum(MAXN); 362 for (long i = 0; i<MAXN; ++i) 363 sum[i] = -2; 364 ScanIsRunning = true; 365 T total = parallel_scan_wrapper(Range(0, MAXN, 1), 42, addend, sum, mode); 366 ScanIsRunning = false; 367 368 CHECK_MESSAGE(control_total==total, "Parallel prefix sum is not equal to serial"); 369 CHECK_MESSAGE(control_sum==sum, "Parallel prefix vector is not equal to serial"); 370 } 371 372 373 #if __TBB_CPP14_GENERIC_LAMBDAS_PRESENT 374 struct ParallelScanGenericLambda { 375 template<typename T> 376 T operator()(Range range, T init, const std::vector<T> &addend, std::vector<T> &sum, int mode) { 377 for (long i = 0; i<MAXN; ++i) { 378 AddendHistory[i] = UNUSED; 379 } 380 ScanIsRunning = true; 381 Storage<T> res = ParallelScanFunctionalInvoker(range, Storage<T>(0), 382 [&addend, &sum, init](const auto& rng, auto storage, bool is_final_scan) { 383 return ScanWithInit(rng, init, is_final_scan, storage, sum, addend); 384 }, 385 [](const auto& left, const auto& right) { 386 return JoinStorages(left, right); 387 }, 388 mode); 389 ScanIsRunning = false; 390 if (range.empty()) 391 res.my_total = init; 392 return res.my_total; 393 } 394 }; 395 #endif /* __TBB_CPP14_GENERIC_LAMBDAS_PRESENT */ 396 397 #if __TBB_CPP20_CONCEPTS_PRESENT 398 template <typename... Args> 399 concept can_call_parallel_scan_basic = requires( Args&&... args ) { 400 tbb::parallel_scan(std::forward<Args>(args)...); 401 }; 402 403 template <typename Range, typename Body> 404 concept can_call_imperative_pscan = can_call_parallel_scan_basic<const Range&, Body&> && 405 can_call_parallel_scan_basic<const Range&, Body&, const tbb::simple_partitioner&> && 406 can_call_parallel_scan_basic<const Range&, Body&, const tbb::auto_partitioner&>; 407 408 template <typename Range, typename Value, typename Func, typename Combine> 409 concept can_call_functional_pscan = can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&> && 410 can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&, const tbb::simple_partitioner&> && 411 can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&, const tbb::auto_partitioner&>; 412 413 using CorrectRange = test_concepts::range::Correct; 414 415 template <typename Range> 416 using CorrectBody = test_concepts::parallel_scan_body::Correct<Range>; 417 418 template <typename Range, typename T> 419 using CorrectFunc = test_concepts::parallel_scan_function::Correct<Range, T>; 420 421 template <typename T> 422 using CorrectCombine = test_concepts::parallel_scan_combine::Correct<T>; 423 424 void test_pscan_range_constraints() { 425 using namespace test_concepts::range; 426 static_assert(can_call_imperative_pscan<Correct, CorrectBody<Correct>>); 427 static_assert(!can_call_imperative_pscan<NonCopyable, CorrectBody<NonCopyable>>); 428 static_assert(!can_call_imperative_pscan<NonDestructible, CorrectBody<NonDestructible>>); 429 static_assert(!can_call_imperative_pscan<NonSplittable, CorrectBody<NonSplittable>>); 430 static_assert(!can_call_imperative_pscan<NoEmpty, CorrectBody<NoEmpty>>); 431 static_assert(!can_call_imperative_pscan<EmptyNonConst, CorrectBody<EmptyNonConst>>); 432 static_assert(!can_call_imperative_pscan<WrongReturnEmpty, CorrectBody<WrongReturnEmpty>>); 433 static_assert(!can_call_imperative_pscan<NoIsDivisible, CorrectBody<NoIsDivisible>>); 434 static_assert(!can_call_imperative_pscan<IsDivisibleNonConst, CorrectBody<IsDivisibleNonConst>>); 435 static_assert(!can_call_imperative_pscan<WrongReturnIsDivisible, CorrectBody<WrongReturnIsDivisible>>); 436 437 static_assert(can_call_functional_pscan<Correct, int, CorrectFunc<Correct, int>, CorrectCombine<int>>); 438 static_assert(!can_call_functional_pscan<NonCopyable, int, CorrectFunc<NonCopyable, int>, CorrectCombine<int>>); 439 static_assert(!can_call_functional_pscan<NonDestructible, int, CorrectFunc<NonDestructible, int>, CorrectCombine<int>>); 440 static_assert(!can_call_functional_pscan<NonSplittable, int, CorrectFunc<NonSplittable, int>, CorrectCombine<int>>); 441 static_assert(!can_call_functional_pscan<NoEmpty, int, CorrectFunc<NoEmpty, int>, CorrectCombine<int>>); 442 static_assert(!can_call_functional_pscan<EmptyNonConst, int, CorrectFunc<EmptyNonConst, int>, CorrectCombine<int>>); 443 static_assert(!can_call_functional_pscan<WrongReturnEmpty, int, CorrectFunc<WrongReturnEmpty, int>, CorrectCombine<int>>); 444 static_assert(!can_call_functional_pscan<NoIsDivisible, int, CorrectFunc<NoIsDivisible, int>, CorrectCombine<int>>); 445 static_assert(!can_call_functional_pscan<IsDivisibleNonConst, int, CorrectFunc<IsDivisibleNonConst, int>, CorrectCombine<int>>); 446 static_assert(!can_call_functional_pscan<WrongReturnIsDivisible, int, CorrectFunc<WrongReturnIsDivisible, int>, CorrectCombine<int>>); 447 } 448 449 void test_pscan_body_constraints() { 450 using namespace test_concepts::parallel_scan_body; 451 static_assert(can_call_imperative_pscan<CorrectRange, Correct<CorrectRange>>); 452 static_assert(!can_call_imperative_pscan<CorrectRange, NonSplittable<CorrectRange>>); 453 static_assert(!can_call_imperative_pscan<CorrectRange, NoPreScanOperatorRoundBrackets<CorrectRange>>); 454 static_assert(!can_call_imperative_pscan<CorrectRange, WrongFirstInputPreScanOperatorRoundBrackets<CorrectRange>>); 455 static_assert(!can_call_imperative_pscan<CorrectRange, WrongSecondInputPreScanOperatorRoundBrackets<CorrectRange>>); 456 static_assert(!can_call_imperative_pscan<CorrectRange, NoFinalScanOperatorRoundBrackets<CorrectRange>>); 457 static_assert(!can_call_imperative_pscan<CorrectRange, WrongFirstInputFinalScanOperatorRoundBrackets<CorrectRange>>); 458 static_assert(!can_call_imperative_pscan<CorrectRange, WrongSecondInputFinalScanOperatorRoundBrackets<CorrectRange>>); 459 static_assert(!can_call_imperative_pscan<CorrectRange, NoReverseJoin<CorrectRange>>); 460 static_assert(!can_call_imperative_pscan<CorrectRange, WrongInputReverseJoin<CorrectRange>>); 461 static_assert(!can_call_imperative_pscan<CorrectRange, NoAssign<CorrectRange>>); 462 static_assert(!can_call_imperative_pscan<CorrectRange, WrongInputAssign<CorrectRange>>); 463 } 464 465 void test_pscan_func_constraints() { 466 using namespace test_concepts::parallel_scan_function; 467 static_assert(can_call_functional_pscan<CorrectRange, int, Correct<CorrectRange, int>, CorrectCombine<int>>); 468 static_assert(!can_call_functional_pscan<CorrectRange, int, NoOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 469 static_assert(!can_call_functional_pscan<CorrectRange, int, OperatorRoundBracketsNonConst<CorrectRange, int>, CorrectCombine<int>>); 470 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongFirstInputOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 471 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongSecondInputOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 472 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongReturnOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 473 } 474 475 void test_pscan_combine_constraints() { 476 using namespace test_concepts::parallel_scan_combine; 477 static_assert(can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, Correct<int>>); 478 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, NoOperatorRoundBrackets<int>>); 479 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, OperatorRoundBracketsNonConst<int>>); 480 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongFirstInputOperatorRoundBrackets<int>>); 481 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongSecondInputOperatorRoundBrackets<int>>); 482 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongReturnOperatorRoundBrackets<int>>); 483 } 484 485 #endif // __TBB_CPP20_CONCEPTS_PRESENT 486 487 // Test for parallel_scan with with different partitioners 488 //! \brief \ref error_guessing \ref resource_usage 489 TEST_CASE("parallel_scan testing with different partitioners") { 490 for (auto concurrency_level : utils::concurrency_range()) { 491 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 492 for (int mode = 0; mode < 3; mode++) { 493 NumberOfLiveStorage = 0; 494 TestAccumulator(mode); 495 // Test that all workers sleep when no work 496 TestCPUUserTime(concurrency_level); 497 498 // Checking has to be done late, because when parallel_scan makes copies of 499 // the user's "Body", the copies might be destroyed slightly after parallel_scan 500 // returns. 501 CHECK(NumberOfLiveStorage == 0); 502 } 503 } 504 } 505 506 // Test for parallel_scan with template functors 507 //! \brief \ref error_guessing \ref interface \ref resource_usage 508 TEST_CASE("parallel_scan testing with template functor") { 509 for (auto concurrency_level : utils::concurrency_range()) { 510 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 511 for (int mode = 0; mode < 3; mode++) { 512 NumberOfLiveStorage = 0; 513 TestInterface(mode, ParallelScanTemplateFunctor()); 514 // Test that all workers sleep when no work 515 TestCPUUserTime(concurrency_level); 516 517 // Checking has to be done late, because when parallel_scan makes copies of 518 // the user's "Body", the copies might be destroyed slightly after parallel_scan 519 // returns. 520 CHECK(NumberOfLiveStorage == 0); 521 } 522 } 523 } 524 525 // Test for parallel_scan with lambdas 526 //! \brief \ref error_guessing \ref interface \ref resource_usage 527 TEST_CASE("parallel_scan testing with lambdas") { 528 for (auto concurrency_level : utils::concurrency_range()) { 529 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 530 for (int mode = 0; mode < 3; mode++) { 531 NumberOfLiveStorage = 0; 532 TestInterface(mode, ParallelScanLambda()); 533 534 // Test that all workers sleep when no work 535 TestCPUUserTime(concurrency_level); 536 537 // Checking has to be done late, because when parallel_scan makes copies of 538 // the user's "Body", the copies might be destroyed slightly after parallel_scan 539 // returns. 540 CHECK(NumberOfLiveStorage == 0); 541 } 542 } 543 } 544 545 #if __TBB_CPP14_GENERIC_LAMBDAS_PRESENT 546 // Test for parallel_scan with genetic lambdas 547 //! \brief \ref error_guessing \ref interface \ref resource_usage 548 TEST_CASE("parallel_scan testing with generic lambdas") { 549 for (auto concurrency_level : utils::concurrency_range()) { 550 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 551 for (int mode = 0; mode < 3; mode++) { 552 NumberOfLiveStorage = 0; 553 TestInterface(mode, ParallelScanGenericLambda()); 554 // Test that all workers sleep when no work 555 TestCPUUserTime(concurrency_level); 556 557 // Checking has to be done late, because when parallel_scan makes copies of 558 // the user's "Body", the copies might be destroyed slightly after parallel_scan 559 // returns. 560 CHECK(NumberOfLiveStorage == 0); 561 } 562 } 563 } 564 #endif /* __TBB_CPP14_GENERIC_LAMBDAS_PRESENT */ 565 566 #if __TBB_CPP20_CONCEPTS_PRESENT 567 //! \brief \ref error_guessing 568 TEST_CASE("parallel_scan constraints") { 569 test_pscan_range_constraints(); 570 test_pscan_body_constraints(); 571 test_pscan_func_constraints(); 572 test_pscan_combine_constraints(); 573 } 574 #endif // __TBB_CPP20_CONCEPTS_PRESENT 575