1 /*
2 Copyright (c) 2005-2023 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/parallel_for_each_common.h"
18 #include "common/concepts_common.h"
19 #include <vector>
20 #include <iterator>
21
22 //! \file test_parallel_for_each.cpp
23 //! \brief Test for [algorithms.parallel_for_each]
24
25 #if __TBB_CPP20_PRESENT
26 // Fancy iterator type that models the C++20 iterator type
27 // that defines the real iterator category using iterator_concept type
28 // and iterator_category is always std::input_iterator_type
29 // Similar iterators are used by C++20 ranges (e.g. std::ranges::iota_view::iterator)
30 // parallel_for_each algorithm should detect such iterators with respect to iterator_concept value
31
32 template <typename T, typename Category>
33 struct cpp20_iterator {
34 static_assert(std::derived_from<Category, std::forward_iterator_tag>,
35 "cpp20_iterator should be of at least forward iterator category");
36
37 using iterator_concept = Category;
38 using iterator_category = std::input_iterator_tag;
39 using value_type = T;
40 using difference_type = std::ptrdiff_t;
41
42 cpp20_iterator() = default;
cpp20_iteratorcpp20_iterator43 explicit cpp20_iterator(T* ptr) : my_ptr(ptr) {}
44
operator *cpp20_iterator45 T& operator*() const { return *my_ptr; }
46
operator ++cpp20_iterator47 cpp20_iterator& operator++() {
48 ++my_ptr;
49 return *this;
50 }
51
operator ++cpp20_iterator52 cpp20_iterator operator++(int) {
53 auto it = *this;
54 ++*this;
55 return it;
56 }
57
operator --cpp20_iterator58 cpp20_iterator& operator--()
59 requires std::derived_from<Category, std::bidirectional_iterator_tag>
60 {
61 --my_ptr;
62 return *this;
63 }
64
operator --cpp20_iterator65 cpp20_iterator operator--(int)
66 requires std::derived_from<Category, std::bidirectional_iterator_tag>
67 {
68 auto it = *this;
69 --*this;
70 return it;
71 }
72
operator +=cpp20_iterator73 cpp20_iterator& operator+=(difference_type n)
74 requires std::derived_from<Category, std::random_access_iterator_tag>
75 {
76 my_ptr += n;
77 return *this;
78 }
79
operator -=cpp20_iterator80 cpp20_iterator& operator-=(difference_type n)
81 requires std::derived_from<Category, std::random_access_iterator_tag>
82 {
83 my_ptr -= n;
84 return *this;
85 }
86
operator []cpp20_iterator87 T& operator[](difference_type n) const
88 requires std::derived_from<Category, std::random_access_iterator_tag>
89 {
90 return my_ptr[n];
91 }
92
93 friend bool operator==(const cpp20_iterator&, const cpp20_iterator&) = default;
94
95 friend auto operator<=>(const cpp20_iterator&, const cpp20_iterator&)
96 requires std::derived_from<Category, std::random_access_iterator_tag> = default;
97
operator +(cpp20_iterator i,difference_type n)98 friend cpp20_iterator operator+(cpp20_iterator i, difference_type n)
99 requires std::derived_from<Category, std::random_access_iterator_tag>
100 {
101 return cpp20_iterator(i.my_ptr + n);
102 }
103
operator +(difference_type n,cpp20_iterator i)104 friend cpp20_iterator operator+(difference_type n, cpp20_iterator i)
105 requires std::derived_from<Category, std::random_access_iterator_tag>
106 {
107 return i + n;
108 }
109
operator -(cpp20_iterator i,difference_type n)110 friend cpp20_iterator operator-(cpp20_iterator i, difference_type n)
111 requires std::derived_from<Category, std::random_access_iterator_tag>
112 {
113 return cpp20_iterator(i.my_ptr - n);
114 }
115
operator -(const cpp20_iterator & x,const cpp20_iterator & y)116 friend difference_type operator-(const cpp20_iterator& x, const cpp20_iterator& y) {
117 return x.my_ptr - y.my_ptr;
118 }
119 private:
120 T* my_ptr = nullptr;
121 }; // class cpp20_iterator
122 #endif // __TBB_CPP20_PRESENT
123
124 //! Test forward access iterator support
125 //! \brief \ref error_guessing \ref interface
126 TEST_CASE("Forward iterator support") {
127 for ( auto concurrency_level : utils::concurrency_range() ) {
128 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level);
129 for(size_t depth = 0; depth <= depths_nubmer; ++depth) {
130 g_tasks_expected = 0;
131 for (size_t i=0; i < depth; ++i)
132 g_tasks_expected += FindNumOfTasks(g_depths[i].value());
133 TestIterator_Modifiable<utils::ForwardIterator<value_t>>(depth);
134 }
135 }
136 }
137
138 //! Test random access iterator support
139 //! \brief \ref error_guessing \ref interface
140 TEST_CASE("Random access iterator support") {
141 for ( auto concurrency_level : utils::concurrency_range() ) {
142 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level);
143 for(size_t depth = 0; depth <= depths_nubmer; ++depth) {
144 g_tasks_expected = 0;
145 for (size_t i=0; i < depth; ++i)
146 g_tasks_expected += FindNumOfTasks(g_depths[i].value());
147 TestIterator_Modifiable<value_t*>(depth);
148 }
149 }
150 }
151
152 //! Test const random access iterator support
153 //! \brief \ref error_guessing \ref interface
154 TEST_CASE("Const random access iterator support") {
155 for ( auto concurrency_level : utils::concurrency_range() ) {
156 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level);
157 for(size_t depth = 0; depth <= depths_nubmer; ++depth) {
158 g_tasks_expected = 0;
159 for (size_t i=0; i < depth; ++i)
160 g_tasks_expected += FindNumOfTasks(g_depths[i].value());
161 TestIterator_Const<utils::ConstRandomIterator<value_t>>(depth);
162 }
163 }
164
165 }
166
167 //! Test container based overload
168 //! \brief \ref error_guessing \ref interface
169 TEST_CASE("Container based overload - forward iterator based container") {
170 container_based_overload_test_case<utils::ForwardIterator>(/*expected_value*/1);
171 }
172
173 //! Test container based overload
174 //! \brief \ref error_guessing \ref interface
175 TEST_CASE("Container based overload - random access iterator based container") {
176 container_based_overload_test_case<utils::RandomIterator>(/*expected_value*/1);
177 }
178
179 // Test for iterators over values convertible to work item type
180 //! \brief \ref error_guessing \ref interface
181 TEST_CASE("Using with values convertible to work item type") {
182 for ( auto concurrency_level : utils::concurrency_range() ) {
183 tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level);
184 using Iterator = size_t*;
185 for(size_t depth = 0; depth <= depths_nubmer; ++depth) {
186 g_tasks_expected = 0;
187 for (size_t i=0; i < depth; ++i)
188 g_tasks_expected += FindNumOfTasks(g_depths[i].value());
189 // Test for iterators over values convertible to work item type
190 TestIterator_Common<Iterator>(depth);
191 TestBody<FakeTaskGeneratorBody_RvalueRefVersion, Iterator>(depth);
192 TestBody<TaskGeneratorBody_RvalueRefVersion, Iterator>(depth);
193 }
194 }
195 }
196
197 //! Testing workers going to sleep
198 //! \brief \ref resource_usage \ref stress
199 TEST_CASE("That all workers sleep when no work") {
200 const std::size_t N = 100000;
201 std::vector<std::size_t> vec(N, 0);
202
__anon577ef66d0102(std::size_t& in) 203 tbb::parallel_for_each(vec.begin(), vec.end(), [&](std::size_t& in) {
204 for (int i = 0; i < 1000; ++i) {
205 ++in;
206 }
207 });
208 TestCPUUserTime(utils::get_platform_max_threads());
209 }
210
211 #if __TBB_CPP20_CONCEPTS_PRESENT
212
213 template <typename Iterator, typename Body>
214 concept can_call_parallel_for_each_with_iterator = requires( Iterator it, const Body& body, tbb::task_group_context ctx ) {
215 tbb::parallel_for_each(it, it, body);
216 tbb::parallel_for_each(it, it, body, ctx);
217 };
218
219 template <typename ContainerBasedSequence, typename Body>
220 concept can_call_parallel_for_each_with_cbs = requires( ContainerBasedSequence cbs,
221 const ContainerBasedSequence const_cbs,
222 const Body& body, tbb::task_group_context ctx ) {
223 tbb::parallel_for_each(cbs, body);
224 tbb::parallel_for_each(cbs, body, ctx);
225 tbb::parallel_for_each(const_cbs, body);
226 tbb::parallel_for_each(const_cbs, body, ctx);
227 };
228
229 using CorrectCBS = test_concepts::container_based_sequence::Correct;
230
231 template <typename Body>
232 concept can_call_parallel_for_each =
233 can_call_parallel_for_each_with_iterator<CorrectCBS::iterator, Body> &&
234 can_call_parallel_for_each_with_cbs<CorrectCBS, Body>;
235
236 template <typename Iterator>
237 using CorrectBody = test_concepts::parallel_for_each_body::Correct<decltype(*std::declval<Iterator>())>;
238
test_pfor_each_iterator_constraints()239 void test_pfor_each_iterator_constraints() {
240 using CorrectIterator = typename std::vector<int>::iterator; // random_access_iterator
241 using IncorrectIterator = std::ostream_iterator<int>; // output_iterator
242 static_assert(can_call_parallel_for_each_with_iterator<CorrectIterator, CorrectBody<CorrectIterator>>);
243 static_assert(!can_call_parallel_for_each_with_iterator<IncorrectIterator, CorrectBody<IncorrectIterator>>);
244 }
245
test_pfor_each_container_based_sequence_constraints()246 void test_pfor_each_container_based_sequence_constraints() {
247 using namespace test_concepts::container_based_sequence;
248 static_assert(can_call_parallel_for_each_with_cbs<Correct, CorrectBody<Correct::iterator>>);
249 static_assert(!can_call_parallel_for_each_with_cbs<NoBegin, CorrectBody<NoBegin::iterator>>);
250 static_assert(!can_call_parallel_for_each_with_cbs<NoEnd, CorrectBody<NoEnd::iterator>>);
251 }
252
test_pfor_each_body_constraints()253 void test_pfor_each_body_constraints() {
254 using namespace test_concepts::parallel_for_each_body;
255 static_assert(can_call_parallel_for_each<Correct<int>>);
256 static_assert(can_call_parallel_for_each<WithFeeder<int>>);
257 static_assert(!can_call_parallel_for_each<NoOperatorRoundBrackets<int>>);
258 static_assert(!can_call_parallel_for_each<WithFeederNoOperatorRoundBrackets<int>>);
259 static_assert(!can_call_parallel_for_each<OperatorRoundBracketsNonConst<int>>);
260 static_assert(!can_call_parallel_for_each<WithFeederOperatorRoundBracketsNonConst<int>>);
261 static_assert(!can_call_parallel_for_each<WrongInputOperatorRoundBrackets<int>>);
262 static_assert(!can_call_parallel_for_each<WithFeederWrongFirstInputOperatorRoundBrackets<int>>);
263 static_assert(!can_call_parallel_for_each<WithFeederWrongSecondInputOperatorRoundBrackets<int>>);
264 }
265
266 //! \brief \ref error_guessing
267 TEST_CASE("parallel_for_each constraints") {
268 test_pfor_each_iterator_constraints();
269 test_pfor_each_container_based_sequence_constraints();
270 test_pfor_each_body_constraints();
271 }
272
273 #endif // __TBB_CPP20_CONCEPTS_PRESENT
274
275 #if __TBB_CPP20_PRESENT
276
277 struct no_copy_move {
278 no_copy_move() = default;
279
280 no_copy_move(const no_copy_move&) = delete;
281 no_copy_move(no_copy_move&&) = delete;
282
283 no_copy_move& operator=(const no_copy_move&) = delete;
284 no_copy_move& operator=(no_copy_move&&) = delete;
285
286 int item = 0;
287 };
288
289 template <typename Category>
test_with_cpp20_iterator()290 void test_with_cpp20_iterator() {
291 constexpr std::size_t n = 1'000'000;
292
293 std::vector<no_copy_move> elements(n);
294
295 cpp20_iterator<no_copy_move, Category> begin(elements.data());
296 cpp20_iterator<no_copy_move, Category> end(elements.data() + n);
297
298 oneapi::tbb::parallel_for_each(begin, end, [](no_copy_move& element) {
299 element.item = 42;
300 });
301
302 for (std::size_t index = 0; index < n; ++index) {
303 CHECK(elements[index].item == 42);
304 }
305 }
306
307 //! \brief \ref error_guessing \ref regression
308 TEST_CASE("parallel_for_each with cpp20 iterator") {
309 // Test that parallel_for_each threats ignores iterator_category type
310 // if iterator_concept type is defined for iterator
311
312 // For input iterators parallel_for_each requires element to be
313 // copyable or movable so since cpp20_iterator is at least forward
314 // parallel_for_each should work with cpp20_iterator
315 // on non-copyable and non-movable type
316
317 // test cpp20_iterator implementation
318 using cpp20_forward_iterator = cpp20_iterator<int, std::forward_iterator_tag>;
319 using cpp20_bidirectional_iterator = cpp20_iterator<int, std::bidirectional_iterator_tag>;
320 using cpp20_random_access_iterator = cpp20_iterator<int, std::random_access_iterator_tag>;
321
322 static_assert(std::forward_iterator<cpp20_forward_iterator>);
323 static_assert(!std::bidirectional_iterator<cpp20_forward_iterator>);
324
325 static_assert(std::bidirectional_iterator<cpp20_bidirectional_iterator>);
326 static_assert(!std::random_access_iterator<cpp20_bidirectional_iterator>);
327
328 static_assert(std::random_access_iterator<cpp20_random_access_iterator>);
329
330 test_with_cpp20_iterator<std::forward_iterator_tag>();
331 test_with_cpp20_iterator<std::bidirectional_iterator_tag>();
332 test_with_cpp20_iterator<std::random_access_iterator_tag>();
333 }
334
335 #endif // __TBB_CPP20_PRESENT
336