1 | /* |
2 | Copyright (c) 2005-2019 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 "harness.h" |
18 | #include "harness_graph.h" |
19 | #include "tbb/flow_graph.h" |
20 | #include "tbb/task_scheduler_init.h" |
21 | |
22 | #if defined(_MSC_VER) && _MSC_VER < 1600 |
23 | #pragma warning (disable : 4503) //disabling the "decorated name length exceeded" warning for VS2008 and earlier |
24 | #endif |
25 | |
26 | // |
27 | // Tests |
28 | // |
29 | |
30 | const int Count = 300; |
31 | const int MaxPorts = 10; |
32 | const int MaxNSources = 5; // max # of source_nodes to register for each split_node input in parallel test |
33 | |
34 | std::vector<bool> flags; // for checking output |
35 | |
36 | template<typename T> |
37 | class name_of { |
38 | public: |
39 | static const char* name() { return "Unknown" ; } |
40 | }; |
41 | template<> |
42 | class name_of<int> { |
43 | public: |
44 | static const char* name() { return "int" ; } |
45 | }; |
46 | template<> |
47 | class name_of<float> { |
48 | public: |
49 | static const char* name() { return "float" ; } |
50 | }; |
51 | template<> |
52 | class name_of<double> { |
53 | public: |
54 | static const char* name() { return "double" ; } |
55 | }; |
56 | template<> |
57 | class name_of<long> { |
58 | public: |
59 | static const char* name() { return "long" ; } |
60 | }; |
61 | template<> |
62 | class name_of<short> { |
63 | public: |
64 | static const char* name() { return "short" ; } |
65 | }; |
66 | |
67 | // T must be arithmetic, and shouldn't wrap around for reasonable sizes of Count (which is now 150, and maxPorts is 10, |
68 | // so the max number generated right now is 1500 or so.) Source will generate a series of TT with value |
69 | // (init_val + (i-1)*addend) * my_mult, where i is the i-th invocation of the body. We are attaching addend |
70 | // source nodes to a join_port, and each will generate part of the numerical series the port is expecting |
71 | // to receive. If there is only one source node, the series order will be maintained; if more than one, |
72 | // this is not guaranteed. |
73 | |
74 | template<int N> |
75 | struct tuple_helper { |
76 | template<typename TupleType> |
77 | static void set_element( TupleType &t, int i) { |
78 | tbb::flow::get<N-1>(t) = (typename tbb::flow::tuple_element<N-1,TupleType>::type)(i * (N+1)); |
79 | tuple_helper<N-1>::set_element(t, i); |
80 | } |
81 | }; |
82 | |
83 | template<> |
84 | struct tuple_helper<1> { |
85 | template<typename TupleType> |
86 | static void set_element(TupleType &t, int i) { |
87 | tbb::flow::get<0>(t) = (typename tbb::flow::tuple_element<0,TupleType>::type)(i * 2); |
88 | } |
89 | }; |
90 | |
91 | // if we start N source_bodys they will all have the addend N, and my_count should be initialized to 0 .. N-1. |
92 | // the output tuples should have all the sequence, but the order will in general vary. |
93 | template<typename TupleType> |
94 | class source_body { |
95 | typedef TupleType TT; |
96 | static const int N = tbb::flow::tuple_size<TT>::value; |
97 | int my_count; |
98 | int addend; |
99 | public: |
100 | source_body(int init_val, int addto) : my_count(init_val), addend(addto) { } |
101 | void operator=( const source_body& other) { my_count = other.my_count; addend = other.addend; } |
102 | bool operator()( TT &v) { |
103 | if(my_count >= Count) return false; |
104 | tuple_helper<N>::set_element(v, my_count); |
105 | my_count += addend; |
106 | return true; |
107 | } |
108 | }; |
109 | |
110 | // allocator for split_node. |
111 | |
112 | template<int N, typename SType> |
113 | class makeSplit { |
114 | public: |
115 | static SType *create(tbb::flow::graph& g) { |
116 | SType *temp = new SType(g); |
117 | return temp; |
118 | } |
119 | static void destroy(SType *p) { delete p; } |
120 | }; |
121 | |
122 | // holder for sink_node pointers for eventual deletion |
123 | |
124 | static void* all_sink_nodes[MaxPorts]; |
125 | |
126 | |
127 | template<int ELEM, typename SType> |
128 | class sink_node_helper { |
129 | public: |
130 | typedef typename SType::input_type TT; |
131 | typedef typename tbb::flow::tuple_element<ELEM-1,TT>::type IT; |
132 | typedef typename tbb::flow::queue_node<IT> my_sink_node_type; |
133 | static void () { |
134 | sink_node_helper<ELEM-1,SType>::print_parallel_remark(); |
135 | REMARK(", %s" , name_of<IT>::name()); |
136 | } |
137 | static void () { |
138 | sink_node_helper<ELEM-1,SType>::print_serial_remark(); |
139 | REMARK(", %s" , name_of<IT>::name()); |
140 | } |
141 | static void add_sink_nodes(SType &my_split, tbb::flow::graph &g) { |
142 | my_sink_node_type *new_node = new my_sink_node_type(g); |
143 | tbb::flow::make_edge( tbb::flow::output_port<ELEM-1>(my_split) , *new_node); |
144 | all_sink_nodes[ELEM-1] = (void *)new_node; |
145 | sink_node_helper<ELEM-1, SType>::add_sink_nodes(my_split, g); |
146 | } |
147 | |
148 | static void check_sink_values() { |
149 | my_sink_node_type *dp = reinterpret_cast<my_sink_node_type *>(all_sink_nodes[ELEM-1]); |
150 | for(int i = 0; i < Count; ++i) { |
151 | IT v; |
152 | ASSERT(dp->try_get(v), NULL); |
153 | flags[((int)v) / (ELEM+1)] = true; |
154 | } |
155 | for(int i = 0; i < Count; ++i) { |
156 | ASSERT(flags[i], NULL); |
157 | flags[i] = false; // reset for next test |
158 | } |
159 | sink_node_helper<ELEM-1,SType>::check_sink_values(); |
160 | } |
161 | static void remove_sink_nodes(SType& my_split) { |
162 | my_sink_node_type *dp = reinterpret_cast<my_sink_node_type *>(all_sink_nodes[ELEM-1]); |
163 | tbb::flow::remove_edge( tbb::flow::output_port<ELEM-1>(my_split) , *dp); |
164 | delete dp; |
165 | sink_node_helper<ELEM-1, SType>::remove_sink_nodes(my_split); |
166 | } |
167 | }; |
168 | |
169 | template<typename SType> |
170 | class sink_node_helper<1, SType> { |
171 | typedef typename SType::input_type TT; |
172 | typedef typename tbb::flow::tuple_element<0,TT>::type IT; |
173 | typedef typename tbb::flow::queue_node<IT> my_sink_node_type; |
174 | public: |
175 | static void () { |
176 | REMARK("Parallel test of split_node< %s" , name_of<IT>::name()); |
177 | } |
178 | static void () { |
179 | REMARK("Serial test of split_node< %s" , name_of<IT>::name()); |
180 | } |
181 | static void add_sink_nodes(SType &my_split, tbb::flow::graph &g) { |
182 | my_sink_node_type *new_node = new my_sink_node_type(g); |
183 | tbb::flow::make_edge( tbb::flow::output_port<0>(my_split) , *new_node); |
184 | all_sink_nodes[0] = (void *)new_node; |
185 | } |
186 | static void check_sink_values() { |
187 | my_sink_node_type *dp = reinterpret_cast<my_sink_node_type *>(all_sink_nodes[0]); |
188 | for(int i = 0; i < Count; ++i) { |
189 | IT v; |
190 | ASSERT(dp->try_get(v), NULL); |
191 | flags[((int)v) / 2] = true; |
192 | } |
193 | for(int i = 0; i < Count; ++i) { |
194 | ASSERT(flags[i], NULL); |
195 | flags[i] = false; // reset for next test |
196 | } |
197 | } |
198 | static void remove_sink_nodes(SType& my_split) { |
199 | my_sink_node_type *dp = reinterpret_cast<my_sink_node_type *>(all_sink_nodes[0]); |
200 | tbb::flow::remove_edge( tbb::flow::output_port<0>(my_split) , *dp); |
201 | delete dp; |
202 | } |
203 | }; |
204 | |
205 | // parallel_test: create source_nodes that feed tuples into the split node |
206 | // and queue_nodes that receive the output. |
207 | template<typename SType> |
208 | class parallel_test { |
209 | public: |
210 | typedef typename SType::input_type TType; |
211 | typedef tbb::flow::source_node<TType> source_type; |
212 | static const int N = tbb::flow::tuple_size<TType>::value; |
213 | static void test() { |
214 | source_type* all_source_nodes[MaxNSources]; |
215 | sink_node_helper<N,SType>::print_parallel_remark(); |
216 | REMARK(" >\n" ); |
217 | for(int i=0; i < MaxPorts; ++i) { |
218 | all_sink_nodes[i] = NULL; |
219 | } |
220 | // try test for # sources 1 .. MaxNSources |
221 | for(int nInputs = 1; nInputs <= MaxNSources; ++nInputs) { |
222 | tbb::flow::graph g; |
223 | SType* my_split = makeSplit<N,SType>::create(g); |
224 | |
225 | // add sinks first so when sources start spitting out values they are there to catch them |
226 | sink_node_helper<N, SType>::add_sink_nodes((*my_split), g); |
227 | |
228 | // now create nInputs source_nodes, each spitting out i, i+nInputs, i+2*nInputs ... |
229 | // each element of the tuple is i*(n+1), where n is the tuple element index (1-N) |
230 | for(int i = 0; i < nInputs; ++i) { |
231 | // create source node |
232 | source_type *s = new source_type(g, source_body<TType>(i, nInputs) ); |
233 | tbb::flow::make_edge(*s, *my_split); |
234 | all_source_nodes[i] = s; |
235 | } |
236 | |
237 | g.wait_for_all(); |
238 | |
239 | // check that we got Count values in each output queue, and all the index values |
240 | // are there. |
241 | sink_node_helper<N, SType>::check_sink_values(); |
242 | |
243 | sink_node_helper<N, SType>::remove_sink_nodes(*my_split); |
244 | for(int i = 0; i < nInputs; ++i) { |
245 | delete all_source_nodes[i]; |
246 | } |
247 | makeSplit<N,SType>::destroy(my_split); |
248 | } |
249 | } |
250 | }; |
251 | |
252 | // |
253 | // Single predecessor, single accepting successor at each port |
254 | |
255 | template<typename SType> |
256 | void test_one_serial( SType &my_split, tbb::flow::graph &g) { |
257 | typedef typename SType::input_type TType; |
258 | static const int TUPLE_SIZE = tbb::flow::tuple_size<TType>::value; |
259 | sink_node_helper<TUPLE_SIZE, SType>::add_sink_nodes(my_split,g); |
260 | typedef TType q3_input_type; |
261 | tbb::flow::queue_node< q3_input_type > q3(g); |
262 | |
263 | tbb::flow::make_edge( q3, my_split ); |
264 | |
265 | // fill the queue with its value one-at-a-time |
266 | flags.clear(); |
267 | for (int i = 0; i < Count; ++i ) { |
268 | TType v; |
269 | tuple_helper<TUPLE_SIZE>::set_element(v, i); |
270 | ASSERT(my_split.try_put(v), NULL); |
271 | flags.push_back(false); |
272 | } |
273 | |
274 | g.wait_for_all(); |
275 | |
276 | sink_node_helper<TUPLE_SIZE,SType>::check_sink_values(); |
277 | |
278 | sink_node_helper<TUPLE_SIZE, SType>::remove_sink_nodes(my_split); |
279 | |
280 | } |
281 | |
282 | template<typename SType> |
283 | class serial_test { |
284 | typedef typename SType::input_type TType; |
285 | static const int TUPLE_SIZE = tbb::flow::tuple_size<TType>::value; |
286 | static const int ELEMS = 3; |
287 | public: |
288 | static void test() { |
289 | tbb::flow::graph g; |
290 | flags.reserve(Count); |
291 | SType* my_split = makeSplit<TUPLE_SIZE,SType>::create(g); |
292 | sink_node_helper<TUPLE_SIZE, SType>::print_serial_remark(); REMARK(" >\n" ); |
293 | |
294 | test_output_ports_return_ref(*my_split); |
295 | |
296 | test_one_serial<SType>(*my_split, g); |
297 | // build the vector with copy construction from the used split node. |
298 | std::vector<SType>split_vector(ELEMS, *my_split); |
299 | // destroy the tired old split_node in case we're accidentally reusing pieces of it. |
300 | makeSplit<TUPLE_SIZE,SType>::destroy(my_split); |
301 | |
302 | |
303 | for(int e = 0; e < ELEMS; ++e) { // exercise each of the vector elements |
304 | test_one_serial<SType>(split_vector[e], g); |
305 | } |
306 | } |
307 | |
308 | }; // serial_test |
309 | |
310 | template< |
311 | template<typename> class TestType, // serial_test or parallel_test |
312 | typename TupleType > // type of the input of the split |
313 | struct generate_test { |
314 | typedef tbb::flow::split_node<TupleType> split_node_type; |
315 | static void do_test() { |
316 | TestType<split_node_type>::test(); |
317 | } |
318 | }; // generate_test |
319 | |
320 | int TestMain() { |
321 | #if __TBB_USE_TBB_TUPLE |
322 | REMARK(" Using TBB tuple\n" ); |
323 | #else |
324 | REMARK(" Using platform tuple\n" ); |
325 | #endif |
326 | for (int p = 0; p < 2; ++p) { |
327 | generate_test<serial_test, tbb::flow::tuple<float, double> >::do_test(); |
328 | #if MAX_TUPLE_TEST_SIZE >= 4 |
329 | generate_test<serial_test, tbb::flow::tuple<float, double, int, long> >::do_test(); |
330 | #endif |
331 | #if MAX_TUPLE_TEST_SIZE >= 6 |
332 | generate_test<serial_test, tbb::flow::tuple<double, double, int, long, int, short> >::do_test(); |
333 | #endif |
334 | #if MAX_TUPLE_TEST_SIZE >= 8 |
335 | generate_test<serial_test, tbb::flow::tuple<float, double, double, double, float, int, float, long> >::do_test(); |
336 | #endif |
337 | #if MAX_TUPLE_TEST_SIZE >= 10 |
338 | generate_test<serial_test, tbb::flow::tuple<float, double, int, double, double, float, long, int, float, long> >::do_test(); |
339 | #endif |
340 | generate_test<parallel_test, tbb::flow::tuple<float, double> >::do_test(); |
341 | #if MAX_TUPLE_TEST_SIZE >= 3 |
342 | generate_test<parallel_test, tbb::flow::tuple<float, int, long> >::do_test(); |
343 | #endif |
344 | #if MAX_TUPLE_TEST_SIZE >= 5 |
345 | generate_test<parallel_test, tbb::flow::tuple<double, double, int, int, short> >::do_test(); |
346 | #endif |
347 | #if MAX_TUPLE_TEST_SIZE >= 7 |
348 | generate_test<parallel_test, tbb::flow::tuple<float, int, double, float, long, float, long> >::do_test(); |
349 | #endif |
350 | #if MAX_TUPLE_TEST_SIZE >= 9 |
351 | generate_test<parallel_test, tbb::flow::tuple<float, double, int, double, double, long, int, float, long> >::do_test(); |
352 | #endif |
353 | } |
354 | return Harness::Done; |
355 | } |
356 | |