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 minimalCombinable {
59 private:
60 int my_value;
61 public:
minimalCombinable(int val=0)62 minimalCombinable(int val=0) : my_value(val) { ++construction_counter; }
minimalCombinable(const minimalCombinable & m)63 minimalCombinable( const minimalCombinable&m ) : my_value(m.my_value) { ++construction_counter; }
operator =(const minimalCombinable & other)64 minimalCombinable& operator=(const minimalCombinable& other) { my_value = other.my_value; return *this; }
operator +=(const minimalCombinable & other)65 minimalCombinable& operator+=(const minimalCombinable& other) { my_value += other.my_value; return *this; }
operator int() const66 operator int() const { return my_value; }
~minimalCombinable()67 ~minimalCombinable() { ++destruction_counter; }
set_value(const int i)68 void set_value( const int i ) { my_value = i; }
value() const69 int value( ) const { return my_value; }
70 };
71
72 //// functors for initialization and combine
73
74 template <typename T>
75 struct FunctorAddFinit {
operator ()FunctorAddFinit76 T operator()() { return 0; }
77 };
78
79 template <typename T>
80 struct FunctorAddFinit7 {
operator ()FunctorAddFinit781 T operator()() { return 7; }
82 };
83
84 template <typename T>
85 struct FunctorAddCombine {
operator ()FunctorAddCombine86 T operator()(T left, T right ) const {
87 return left + right;
88 }
89 };
90
91 template <typename T>
92 struct FunctorAddCombineRef {
operator ()FunctorAddCombineRef93 T operator()(const T& left, const T& right ) const {
94 return left + right;
95 }
96 };
97
98 template <typename T>
my_combine(T left,T right)99 T my_combine( T left, T right) { return left + right; }
100
101 template <typename T>
my_combine_ref(const T & left,const T & right)102 T my_combine_ref( const T &left, const T &right) { return left + right; }
103
104 template <typename T>
105 class CombineEachHelper {
106 public:
CombineEachHelper(T & _result)107 CombineEachHelper(T& _result) : my_result(_result) {}
operator ()(const T & new_bit)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:
CombineEachHelperCnt(T & _result,int & _nbuckets)116 CombineEachHelperCnt(T& _result, int& _nbuckets) : my_result(_result), nBuckets(_nbuckets) {}
operator ()(const T & new_bit)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;
CombineEachVectorHelper(T & _result)127 CombineEachVectorHelper(T& _result) : my_result(_result) { }
operator ()(const ContainerType & new_bit)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
ParallelScalarBody(oneapi::tbb::combinable<T> & _sums)148 ParallelScalarBody ( oneapi::tbb::combinable<T> &_sums ) : sums(_sums) { }
149
operator ()(const oneapi::tbb::blocked_range<int> & r) const150 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
ParallelScalarBodyNoInit(oneapi::tbb::combinable<T> & _sums)169 ParallelScalarBodyNoInit ( oneapi::tbb::combinable<T> &_sums ) : sums(_sums) { }
170
operator ()(const oneapi::tbb::blocked_range<int> & r) const171 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 >
RunParallelScalarTests(const char *)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
ParallelVectorForBody(oneapi::tbb::combinable<std::vector<T,oneapi::tbb::tbb_allocator<T>>> & _locals)261 ParallelVectorForBody ( oneapi::tbb::combinable< std::vector<T, oneapi::tbb::tbb_allocator<T> > > &_locals ) : locals(_locals) { }
262
operator ()(const oneapi::tbb::blocked_range<int> & r) const263 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 >
RunParallelVectorTests(const char *)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
RunParallelTests()339 RunParallelTests() {
340 // REMARK("Running RunParallelTests\n");
341 RunParallelScalarTests<int>("int");
342 RunParallelScalarTests<double>("double");
343 RunParallelScalarTests<minimalCombinable>("minimalCombinable");
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
RunAssignmentAndCopyConstructorTest(const char *)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
RunAssignmentAndCopyConstructorTests()381 void RunAssignmentAndCopyConstructorTests() {
382 // REMARK("Running assignment and copy constructor tests:\n");
383 RunAssignmentAndCopyConstructorTest<int>("int");
384 RunAssignmentAndCopyConstructorTest<double>("double");
385 RunAssignmentAndCopyConstructorTest<minimalCombinable>("minimalCombinable");
386 }
387
RunMoveSemanticsForStateTrackableObjectTest()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;
BodyBody431 Body( int nthread_, int niters_ ) : nthread(nthread_), nIters(niters_) { sBarrier.initialize(nthread_); }
432
operator ()Body433 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
TestLocalAllocations(int nthread)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
RunLocalAllocationsTests()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