| 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 | |