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 long used_once_count = 0; 334 for( long i=0; i<n; ++i ) 335 CHECK_MESSAGE((AddendHistory[i]&USED_FINAL), "failed to use addend[" << i << "] " << (AddendHistory[i] & USED_NONFINAL ? "(but used nonfinal)\n" : "\n")); 336 for( long i=0; i<n; ++i ) { 337 VerifySum( 42, i, sum[i], __LINE__ ); 338 used_once_count += AddendHistory[i]==USED_FINAL; 339 } 340 if( n ) 341 CHECK(acc.get_total()==sum[n-1]); 342 else 343 CHECK(acc.get_total()==42); 344 CHECK(control_total ==acc.get_total()); 345 CHECK(control_sum==sum); 346 } 347 } 348 } 349 350 template<typename ParallelScanWrapper> 351 void TestInterface( int mode, ParallelScanWrapper parallel_scan_wrapper ) { 352 using T = int; 353 std::vector<T> addend(MAXN); 354 std::vector<T> control_sum(MAXN); 355 T control_total(42); 356 for( long i=0; i<MAXN; ++i ) { 357 addend[i] = i; 358 control_total += addend[i]; 359 control_sum[i] = control_total; 360 AddendHistory[i] = UNUSED; 361 } 362 363 std::vector<T> sum(MAXN); 364 for (long i = 0; i<MAXN; ++i) 365 sum[i] = -2; 366 ScanIsRunning = true; 367 T total = parallel_scan_wrapper(Range(0, MAXN, 1), 42, addend, sum, mode); 368 ScanIsRunning = false; 369 370 CHECK_MESSAGE(control_total==total, "Parallel prefix sum is not equal to serial"); 371 CHECK_MESSAGE(control_sum==sum, "Parallel prefix vector is not equal to serial"); 372 } 373 374 375 #if __TBB_CPP14_GENERIC_LAMBDAS_PRESENT 376 struct ParallelScanGenericLambda { 377 template<typename T> 378 T operator()(Range range, T init, const std::vector<T> &addend, std::vector<T> &sum, int mode) { 379 for (long i = 0; i<MAXN; ++i) { 380 AddendHistory[i] = UNUSED; 381 } 382 ScanIsRunning = true; 383 Storage<T> res = ParallelScanFunctionalInvoker(range, Storage<T>(0), 384 [&addend, &sum, init](const auto& rng, auto storage, bool is_final_scan) { 385 return ScanWithInit(rng, init, is_final_scan, storage, sum, addend); 386 }, 387 [](const auto& left, const auto& right) { 388 return JoinStorages(left, right); 389 }, 390 mode); 391 ScanIsRunning = false; 392 if (range.empty()) 393 res.my_total = init; 394 return res.my_total; 395 } 396 }; 397 #endif /* __TBB_CPP14_GENERIC_LAMBDAS_PRESENT */ 398 399 #if __TBB_CPP20_CONCEPTS_PRESENT 400 template <typename... Args> 401 concept can_call_parallel_scan_basic = requires( Args&&... args ) { 402 tbb::parallel_scan(std::forward<Args>(args)...); 403 }; 404 405 template <typename Range, typename Body> 406 concept can_call_imperative_pscan = can_call_parallel_scan_basic<const Range&, Body&> && 407 can_call_parallel_scan_basic<const Range&, Body&, const tbb::simple_partitioner&> && 408 can_call_parallel_scan_basic<const Range&, Body&, const tbb::auto_partitioner&>; 409 410 template <typename Range, typename Value, typename Func, typename Combine> 411 concept can_call_functional_pscan = can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&> && 412 can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&, const tbb::simple_partitioner&> && 413 can_call_parallel_scan_basic<const Range&, const Value&, const Func&, const Combine&, const tbb::auto_partitioner&>; 414 415 using CorrectRange = test_concepts::range::Correct; 416 417 template <typename Range> 418 using CorrectBody = test_concepts::parallel_scan_body::Correct<Range>; 419 420 template <typename Range, typename T> 421 using CorrectFunc = test_concepts::parallel_scan_function::Correct<Range, T>; 422 423 template <typename T> 424 using CorrectCombine = test_concepts::parallel_scan_combine::Correct<T>; 425 426 void test_pscan_range_constraints() { 427 using namespace test_concepts::range; 428 static_assert(can_call_imperative_pscan<Correct, CorrectBody<Correct>>); 429 static_assert(!can_call_imperative_pscan<NonCopyable, CorrectBody<NonCopyable>>); 430 static_assert(!can_call_imperative_pscan<NonDestructible, CorrectBody<NonDestructible>>); 431 static_assert(!can_call_imperative_pscan<NonSplittable, CorrectBody<NonSplittable>>); 432 static_assert(!can_call_imperative_pscan<NoEmpty, CorrectBody<NoEmpty>>); 433 static_assert(!can_call_imperative_pscan<EmptyNonConst, CorrectBody<EmptyNonConst>>); 434 static_assert(!can_call_imperative_pscan<WrongReturnEmpty, CorrectBody<WrongReturnEmpty>>); 435 static_assert(!can_call_imperative_pscan<NoIsDivisible, CorrectBody<NoIsDivisible>>); 436 static_assert(!can_call_imperative_pscan<IsDivisibleNonConst, CorrectBody<IsDivisibleNonConst>>); 437 static_assert(!can_call_imperative_pscan<WrongReturnIsDivisible, CorrectBody<WrongReturnIsDivisible>>); 438 439 static_assert(can_call_functional_pscan<Correct, int, CorrectFunc<Correct, int>, CorrectCombine<int>>); 440 static_assert(!can_call_functional_pscan<NonCopyable, int, CorrectFunc<NonCopyable, int>, CorrectCombine<int>>); 441 static_assert(!can_call_functional_pscan<NonDestructible, int, CorrectFunc<NonDestructible, int>, CorrectCombine<int>>); 442 static_assert(!can_call_functional_pscan<NonSplittable, int, CorrectFunc<NonSplittable, int>, CorrectCombine<int>>); 443 static_assert(!can_call_functional_pscan<NoEmpty, int, CorrectFunc<NoEmpty, int>, CorrectCombine<int>>); 444 static_assert(!can_call_functional_pscan<EmptyNonConst, int, CorrectFunc<EmptyNonConst, int>, CorrectCombine<int>>); 445 static_assert(!can_call_functional_pscan<WrongReturnEmpty, int, CorrectFunc<WrongReturnEmpty, int>, CorrectCombine<int>>); 446 static_assert(!can_call_functional_pscan<NoIsDivisible, int, CorrectFunc<NoIsDivisible, int>, CorrectCombine<int>>); 447 static_assert(!can_call_functional_pscan<IsDivisibleNonConst, int, CorrectFunc<IsDivisibleNonConst, int>, CorrectCombine<int>>); 448 static_assert(!can_call_functional_pscan<WrongReturnIsDivisible, int, CorrectFunc<WrongReturnIsDivisible, int>, CorrectCombine<int>>); 449 } 450 451 void test_pscan_body_constraints() { 452 using namespace test_concepts::parallel_scan_body; 453 static_assert(can_call_imperative_pscan<CorrectRange, Correct<CorrectRange>>); 454 static_assert(!can_call_imperative_pscan<CorrectRange, NonSplittable<CorrectRange>>); 455 static_assert(!can_call_imperative_pscan<CorrectRange, NoPreScanOperatorRoundBrackets<CorrectRange>>); 456 static_assert(!can_call_imperative_pscan<CorrectRange, WrongFirstInputPreScanOperatorRoundBrackets<CorrectRange>>); 457 static_assert(!can_call_imperative_pscan<CorrectRange, WrongSecondInputPreScanOperatorRoundBrackets<CorrectRange>>); 458 static_assert(!can_call_imperative_pscan<CorrectRange, NoFinalScanOperatorRoundBrackets<CorrectRange>>); 459 static_assert(!can_call_imperative_pscan<CorrectRange, WrongFirstInputFinalScanOperatorRoundBrackets<CorrectRange>>); 460 static_assert(!can_call_imperative_pscan<CorrectRange, WrongSecondInputFinalScanOperatorRoundBrackets<CorrectRange>>); 461 static_assert(!can_call_imperative_pscan<CorrectRange, NoReverseJoin<CorrectRange>>); 462 static_assert(!can_call_imperative_pscan<CorrectRange, WrongInputReverseJoin<CorrectRange>>); 463 static_assert(!can_call_imperative_pscan<CorrectRange, NoAssign<CorrectRange>>); 464 static_assert(!can_call_imperative_pscan<CorrectRange, WrongInputAssign<CorrectRange>>); 465 } 466 467 void test_pscan_func_constraints() { 468 using namespace test_concepts::parallel_scan_function; 469 static_assert(can_call_functional_pscan<CorrectRange, int, Correct<CorrectRange, int>, CorrectCombine<int>>); 470 static_assert(!can_call_functional_pscan<CorrectRange, int, NoOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 471 static_assert(!can_call_functional_pscan<CorrectRange, int, OperatorRoundBracketsNonConst<CorrectRange, int>, CorrectCombine<int>>); 472 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongFirstInputOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 473 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongSecondInputOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 474 static_assert(!can_call_functional_pscan<CorrectRange, int, WrongReturnOperatorRoundBrackets<CorrectRange, int>, CorrectCombine<int>>); 475 } 476 477 void test_pscan_combine_constraints() { 478 using namespace test_concepts::parallel_scan_combine; 479 static_assert(can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, Correct<int>>); 480 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, NoOperatorRoundBrackets<int>>); 481 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, OperatorRoundBracketsNonConst<int>>); 482 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongFirstInputOperatorRoundBrackets<int>>); 483 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongSecondInputOperatorRoundBrackets<int>>); 484 static_assert(!can_call_functional_pscan<CorrectRange, int, CorrectFunc<CorrectRange, int>, WrongReturnOperatorRoundBrackets<int>>); 485 } 486 487 #endif // __TBB_CPP20_CONCEPTS_PRESENT 488 489 // Test for parallel_scan with with different partitioners 490 //! \brief \ref error_guessing \ref resource_usage 491 TEST_CASE("parallel_scan testing with different partitioners") { 492 for (auto concurrency_level : utils::concurrency_range()) { 493 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 494 for (int mode = 0; mode < 3; mode++) { 495 NumberOfLiveStorage = 0; 496 TestAccumulator(mode); 497 // Test that all workers sleep when no work 498 TestCPUUserTime(concurrency_level); 499 500 // Checking has to be done late, because when parallel_scan makes copies of 501 // the user's "Body", the copies might be destroyed slightly after parallel_scan 502 // returns. 503 CHECK(NumberOfLiveStorage == 0); 504 } 505 } 506 } 507 508 // Test for parallel_scan with template functors 509 //! \brief \ref error_guessing \ref interface \ref resource_usage 510 TEST_CASE("parallel_scan testing with template functor") { 511 for (auto concurrency_level : utils::concurrency_range()) { 512 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 513 for (int mode = 0; mode < 3; mode++) { 514 NumberOfLiveStorage = 0; 515 TestInterface(mode, ParallelScanTemplateFunctor()); 516 // Test that all workers sleep when no work 517 TestCPUUserTime(concurrency_level); 518 519 // Checking has to be done late, because when parallel_scan makes copies of 520 // the user's "Body", the copies might be destroyed slightly after parallel_scan 521 // returns. 522 CHECK(NumberOfLiveStorage == 0); 523 } 524 } 525 } 526 527 // Test for parallel_scan with lambdas 528 //! \brief \ref error_guessing \ref interface \ref resource_usage 529 TEST_CASE("parallel_scan testing with lambdas") { 530 for (auto concurrency_level : utils::concurrency_range()) { 531 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 532 for (int mode = 0; mode < 3; mode++) { 533 NumberOfLiveStorage = 0; 534 TestInterface(mode, ParallelScanLambda()); 535 536 // Test that all workers sleep when no work 537 TestCPUUserTime(concurrency_level); 538 539 // Checking has to be done late, because when parallel_scan makes copies of 540 // the user's "Body", the copies might be destroyed slightly after parallel_scan 541 // returns. 542 CHECK(NumberOfLiveStorage == 0); 543 } 544 } 545 } 546 547 #if __TBB_CPP14_GENERIC_LAMBDAS_PRESENT 548 // Test for parallel_scan with genetic lambdas 549 //! \brief \ref error_guessing \ref interface \ref resource_usage 550 TEST_CASE("parallel_scan testing with generic lambdas") { 551 for (auto concurrency_level : utils::concurrency_range()) { 552 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level); 553 for (int mode = 0; mode < 3; mode++) { 554 NumberOfLiveStorage = 0; 555 TestInterface(mode, ParallelScanGenericLambda()); 556 // Test that all workers sleep when no work 557 TestCPUUserTime(concurrency_level); 558 559 // Checking has to be done late, because when parallel_scan makes copies of 560 // the user's "Body", the copies might be destroyed slightly after parallel_scan 561 // returns. 562 CHECK(NumberOfLiveStorage == 0); 563 } 564 } 565 } 566 #endif /* __TBB_CPP14_GENERIC_LAMBDAS_PRESENT */ 567 568 #if __TBB_CPP20_CONCEPTS_PRESENT 569 //! \brief \ref error_guessing 570 TEST_CASE("parallel_scan constraints") { 571 test_pscan_range_constraints(); 572 test_pscan_body_constraints(); 573 test_pscan_func_constraints(); 574 test_pscan_combine_constraints(); 575 } 576 #endif // __TBB_CPP20_CONCEPTS_PRESENT 577