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