xref: /oneTBB/test/tbb/test_parallel_scan.cpp (revision 478de5b1)
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