1 /*
2 Copyright (c) 2017-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_assert.h"
20 #include "common/utils_concurrency_limit.h"
21
22 //! \file conformance_blocked_rangeNd.cpp
23 //! \brief Test for [preview] functionality
24
25 #define TBB_PREVIEW_BLOCKED_RANGE_ND 1
26 #include "oneapi/tbb/blocked_rangeNd.h"
27 #include "oneapi/tbb/parallel_for.h"
28 #include "oneapi/tbb/global_control.h"
29
30 #include <algorithm> // std::for_each
31 #include <array>
32
33 // AbstractValueType class represents Value concept's requirements in the most abstract way
34 class AbstractValueType {
35 int value;
AbstractValueType()36 AbstractValueType() {}
37 public:
38 friend AbstractValueType MakeAbstractValue(int i);
39 friend int GetValueOf(const AbstractValueType& v);
40 };
41
GetValueOf(const AbstractValueType & v)42 int GetValueOf(const AbstractValueType& v) { return v.value; }
43
MakeAbstractValue(int i)44 AbstractValueType MakeAbstractValue(int i) {
45 AbstractValueType x;
46 x.value = i;
47 return x;
48 }
49
50 // operator- returns amount of elements of AbstractValueType between u and v
operator -(const AbstractValueType & u,const AbstractValueType & v)51 std::size_t operator-(const AbstractValueType& u, const AbstractValueType& v) {
52 return GetValueOf(u) - GetValueOf(v);
53 }
54
operator <(const AbstractValueType & u,const AbstractValueType & v)55 bool operator<(const AbstractValueType& u, const AbstractValueType& v) {
56 return GetValueOf(u) < GetValueOf(v);
57 }
58
operator +(const AbstractValueType & u,std::size_t offset)59 AbstractValueType operator+(const AbstractValueType& u, std::size_t offset) {
60 return MakeAbstractValue(GetValueOf(u) + int(offset));
61 }
62
63 template<typename range_t, unsigned int N>
64 struct range_utils {
65 using val_t = typename range_t::value_type;
66
67 template<typename EntityType, std::size_t DimSize>
68 using data_type = std::array<typename range_utils<range_t, N - 1>::template data_type<EntityType, DimSize>, DimSize>;
69
70 template<typename EntityType, std::size_t DimSize>
init_datarange_utils71 static void init_data(data_type<EntityType, DimSize>& data) {
72 std::for_each(data.begin(), data.end(), range_utils<range_t, N - 1>::template init_data<EntityType, DimSize>);
73 }
74
75 template<typename EntityType, std::size_t DimSize>
increment_datarange_utils76 static void increment_data(const range_t& range, data_type<EntityType, DimSize>& data) {
77 auto begin = data.begin() + range.dim(N - 1).begin();
78 // same as "auto end = out.begin() + range.dim(N - 1).end();"
79 auto end = begin + range.dim(N - 1).size();
80 for (auto i = begin; i != end; ++i) {
81 range_utils<range_t, N - 1>::template increment_data<EntityType, DimSize>(range, *i);
82 }
83 }
84
85 template<typename EntityType, std::size_t DimSize>
check_datarange_utils86 static void check_data(const range_t& range, data_type<EntityType, DimSize>& data) {
87 auto begin = data.begin() + range.dim(N - 1).begin();
88 // same as "auto end = out.begin() + range.dim(N - 1).end();"
89 auto end = begin + range.dim(N - 1).size();
90 for (auto i = begin; i != end; ++i) {
91 range_utils<range_t, N - 1>::template check_data<EntityType, DimSize>(range, *i);
92 }
93 }
94
95 // BullseyeCoverage Compile C++ with GCC 5.4 warning suppression
96 // Sequence points error in braced initializer list
97 #if __GNUC__ && !defined(__clang__) && !defined(__INTEL_COMPILER)
98 #pragma GCC diagnostic push
99 #pragma GCC diagnostic ignored "-Wsequence-point"
100 #endif
101 template<typename input_t, std::size_t... Is>
make_rangerange_utils102 static range_t make_range(std::size_t shift, bool negative, val_t(*gen)(input_t), oneapi::tbb::detail::index_sequence<Is...>) {
103 return range_t( { { gen(negative ? -input_t(Is + shift) : 0), gen(input_t(Is + shift)), Is + 1} ... } );
104 }
105 #if __GNUC__ && !defined(__clang__) && !defined(__INTEL_COMPILER)
106 #pragma GCC diagnostic pop
107 #endif
108
is_emptyrange_utils109 static bool is_empty(const range_t& range) {
110 if (range.dim(N - 1).empty()) { return true; }
111 return range_utils<range_t, N - 1>::is_empty(range);
112 }
113
is_divisiblerange_utils114 static bool is_divisible(const range_t& range) {
115 if (range.dim(N - 1).is_divisible()) { return true; }
116 return range_utils<range_t, N - 1>::is_divisible(range);
117 }
118
check_splittingrange_utils119 static void check_splitting(const range_t& range_split, const range_t& range_new, int(*get)(const val_t&), bool split_checker = false) {
120 if (get(range_split.dim(N - 1).begin()) == get(range_new.dim(N - 1).begin())) {
121 REQUIRE(get(range_split.dim(N - 1).end()) == get(range_new.dim(N - 1).end()));
122 }
123 else {
124 REQUIRE((get(range_split.dim(N - 1).end()) == get(range_new.dim(N - 1).begin()) && !split_checker));
125 split_checker = true;
126 }
127 range_utils<range_t, N - 1>::check_splitting(range_split, range_new, get, split_checker);
128 }
129
130 };
131
132 template<typename range_t>
133 struct range_utils<range_t, 0> {
134 using val_t = typename range_t::value_type;
135
136 template<typename EntityType, std::size_t DimSize>
137 using data_type = EntityType;
138
139 template<typename EntityType, std::size_t DimSize>
init_datarange_utils140 static void init_data(data_type<EntityType, DimSize>& data) { data = 0; }
141
142 template<typename EntityType, std::size_t DimSize>
increment_datarange_utils143 static void increment_data(const range_t&, data_type<EntityType, DimSize>& data) { ++data; }
144
145 template<typename EntityType, std::size_t DimSize>
check_datarange_utils146 static void check_data(const range_t&, data_type<EntityType, DimSize>& data) {
147 REQUIRE(data == 1);
148 }
149
is_emptyrange_utils150 static bool is_empty(const range_t&) { return false; }
151
is_divisiblerange_utils152 static bool is_divisible(const range_t&) { return false; }
153
check_splittingrange_utils154 static void check_splitting(const range_t&, const range_t&, int(*)(const val_t&), bool) {}
155 };
156
157 // We need MakeInt function to pass it into make_range as factory function
158 // because of matching make_range with AbstractValueType and other types too
MakeInt(int i)159 int MakeInt(int i) { return i; }
160
161 template<unsigned int DimAmount>
SerialTest()162 void SerialTest() {
163 static_assert((oneapi::tbb::blocked_rangeNd<int, DimAmount>::ndims() == oneapi::tbb::blocked_rangeNd<AbstractValueType, DimAmount>::ndims()),
164 "different amount of dimensions");
165
166 using range_t = oneapi::tbb::blocked_rangeNd<AbstractValueType, DimAmount>;
167 using utils_t = range_utils<range_t, DimAmount>;
168
169 // Generate empty range
170 range_t r = utils_t::make_range(0, true, &MakeAbstractValue, oneapi::tbb::detail::make_index_sequence<DimAmount>());
171
172 utils::AssertSameType(r.is_divisible(), bool());
173 utils::AssertSameType(r.empty(), bool());
174 utils::AssertSameType(range_t::ndims(), 0U);
175
176 REQUIRE((r.empty() == utils_t::is_empty(r) && r.empty()));
177 REQUIRE(r.is_divisible() == utils_t::is_divisible(r));
178
179 // Generate not-empty range divisible range
180 r = utils_t::make_range(1, true, &MakeAbstractValue, oneapi::tbb::detail::make_index_sequence<DimAmount>());
181 REQUIRE((r.empty() == utils_t::is_empty(r) && !r.empty()));
182 REQUIRE((r.is_divisible() == utils_t::is_divisible(r) && r.is_divisible()));
183
184 range_t r_new(r, oneapi::tbb::split());
185 utils_t::check_splitting(r, r_new, &GetValueOf);
186
187 SerialTest<DimAmount - 1>();
188 }
SerialTest()189 template<> void SerialTest<0>() {}
190
191 template<unsigned int DimAmount>
ParallelTest()192 void ParallelTest() {
193 using range_t = oneapi::tbb::blocked_rangeNd<int, DimAmount>;
194 using utils_t = range_utils<range_t, DimAmount>;
195
196 // Max size is 1 << 20 - 1 bytes
197 // Thus size of one dimension's elements is 1 << (20 / DimAmount - 1) bytes
198 typename utils_t::template data_type<unsigned char, 1 << (20 / DimAmount - 1)> data;
199 utils_t::init_data(data);
200
201 range_t r = utils_t::make_range((1 << (20 / DimAmount - 1)) - DimAmount, false, &MakeInt, oneapi::tbb::detail::make_index_sequence<DimAmount>());
202
203 oneapi::tbb::parallel_for(r, [&data](const range_t& range) {
204 utils_t::increment_data(range, data);
205 });
206
207 utils_t::check_data(r, data);
208
209 ParallelTest<DimAmount - 1>();
210 }
ParallelTest()211 template<> void ParallelTest<0>() {}
212
213 //! Testing blocked_rangeNd construction
214 //! \brief \ref interface
215 TEST_CASE("Construction") {
216 oneapi::tbb::blocked_rangeNd<int, 1>{ { 0,13,3 } };
217
218 oneapi::tbb::blocked_rangeNd<int, 1>{ oneapi::tbb::blocked_range<int>{ 0,13,3 } };
219
220 oneapi::tbb::blocked_rangeNd<int, 2>(oneapi::tbb::blocked_range<int>(-8923, 8884, 13), oneapi::tbb::blocked_range<int>(-8923, 5, 13));
221
222 oneapi::tbb::blocked_rangeNd<int, 2>({ -8923, 8884, 13 }, { -8923, 8884, 13 });
223
224 oneapi::tbb::blocked_range<int> r1(0, 13);
225
226 oneapi::tbb::blocked_range<int> r2(-12, 23);
227
228 oneapi::tbb::blocked_rangeNd<int, 2>({ { -8923, 8884, 13 }, r1});
229
230 oneapi::tbb::blocked_rangeNd<int, 2>({ r2, r1 });
231
232 oneapi::tbb::blocked_rangeNd<int, 2>(r1, r2);
233
234 oneapi::tbb::blocked_rangeNd<AbstractValueType, 4>({ MakeAbstractValue(-3), MakeAbstractValue(13), 8 },
235 { MakeAbstractValue(-53), MakeAbstractValue(23), 2 },
236 { MakeAbstractValue(-23), MakeAbstractValue(33), 1 },
237 { MakeAbstractValue(-13), MakeAbstractValue(43), 7 });
238 }
239
240 static const std::size_t N = 4;
241
242 //! Testing blocked_rangeNd interface
243 //! \brief \ref interface \ref requirement
244 TEST_CASE("Serial test") {
245 SerialTest<N>();
246 }
247
248 //! Testing blocked_rangeNd interface with parallel_for
249 //! \brief \ref requirement
250 TEST_CASE("Parallel test") {
251 for ( auto concurrency_level : utils::concurrency_range() ) {
252 oneapi::tbb::global_control control(oneapi::tbb::global_control::max_allowed_parallelism, concurrency_level);
253 ParallelTest<N>();
254 }
255 }
256
257 //! Testing blocked_rangeNd with proportional splitting
258 //! \brief \ref interface \ref requirement
259 TEST_CASE("blocked_rangeNd proportional splitting") {
260 oneapi::tbb::blocked_rangeNd<int, 2> original{{0, 100}, {0, 100}};
261 oneapi::tbb::blocked_rangeNd<int, 2> first(original);
262 oneapi::tbb::proportional_split ps(3, 1);
263 oneapi::tbb::blocked_rangeNd<int, 2> second(first, ps);
264
265 int expected_first_end = static_cast<int>(
266 original.dim(0).begin() + ps.left() * (original.dim(0).end() - original.dim(0).begin()) / (ps.left() + ps.right())
267 );
268 if (first.dim(0).size() == second.dim(0).size()) {
269 // Splitting was made by cols
270 utils::check_range_bounds_after_splitting(original.dim(1), first.dim(1), second.dim(1), expected_first_end);
271 } else {
272 // Splitting was made by rows
273 utils::check_range_bounds_after_splitting(original.dim(0), first.dim(0), second.dim(0), expected_first_end);
274 }
275 }
276