xref: /oneTBB/test/tbb/test_parallel_scan.cpp (revision 18e06f7f)
1 /*
2     Copyright (c) 2005-2022 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #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, nullptr 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 = nullptr;
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