1 /* 2 Copyright (c) 2005-2020 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 #ifndef __TIME_FRAMEWORK_H__ 18 #define __TIME_FRAMEWORK_H__ 19 20 #include <cstdlib> 21 #include <math.h> 22 #include <vector> 23 #include <string> 24 #include <sstream> 25 #include "tbb/tbb_stddef.h" 26 #include "tbb/task_scheduler_init.h" 27 #include "tbb/tick_count.h" 28 #define HARNESS_CUSTOM_MAIN 1 29 #include "../test/harness.h" 30 #include "../test/harness_barrier.h" 31 #define STATISTICS_INLINE 32 #include "statistics.h" 33 34 #ifndef ARG_TYPE 35 typedef intptr_t arg_t; 36 #else 37 typedef ARG_TYPE arg_t; 38 #endif 39 40 class Timer { 41 tbb::tick_count tick; 42 public: Timer()43 Timer() { tick = tbb::tick_count::now(); } get_time()44 double get_time() { return (tbb::tick_count::now() - tick).seconds(); } diff_time(const Timer & newer)45 double diff_time(const Timer &newer) { return (newer.tick - tick).seconds(); } mark_time()46 double mark_time() { tbb::tick_count t1(tbb::tick_count::now()), t2(tick); tick = t1; return (t1 - t2).seconds(); } mark_time(const Timer & newer)47 double mark_time(const Timer &newer) { tbb::tick_count t(tick); tick = newer.tick; return (tick - t).seconds(); } 48 }; 49 50 class TesterBase /*: public tbb::internal::no_copy*/ { 51 protected: 52 friend class TestProcessor; 53 friend class TestRunner; 54 55 //! it is barrier for synchronizing between threads 56 Harness::SpinBarrier *barrier; 57 58 //! number of tests per this tester 59 const int tests_count; 60 61 //! number of threads to operate 62 int threads_count; 63 64 //! some value for tester 65 arg_t value; 66 67 //! tester name 68 const char *tester_name; 69 70 // avoid false sharing 71 char pad[128 - sizeof(arg_t) - sizeof(int)*2 - sizeof(void*)*2 ]; 72 73 public: 74 //! init tester base. @arg ntests is number of embedded tests in this tester. TesterBase(int ntests)75 TesterBase(int ntests) 76 : barrier(NULL), tests_count(ntests) 77 {} ~TesterBase()78 virtual ~TesterBase() {} 79 80 //! internal function base_init(arg_t v,int t,Harness::SpinBarrier & b)81 void base_init(arg_t v, int t, Harness::SpinBarrier &b) { 82 threads_count = t; 83 barrier = &b; 84 value = v; 85 init(); 86 } 87 88 //! optionally override to init after value and threads count were set. init()89 virtual void init() { } 90 91 //! Override to provide your names get_name(int testn)92 virtual std::string get_name(int testn) { 93 return Format("test %d", testn); 94 } 95 96 //! optionally override to init test mode just before execution for a given thread number. test_prefix(int testn,int threadn)97 virtual void test_prefix(int testn, int threadn) { } 98 99 //! Override to provide main test's entry function returns a value to record 100 virtual value_t test(int testn, int threadn) = 0; 101 102 //! Type of aggregation from results of threads 103 enum result_t { 104 SUM, AVG, MIN, MAX 105 }; 106 107 //! Override to change result type for the test. Return postfix for test name or 0 if result type is not needed. get_result_type(int,result_t type)108 virtual const char *get_result_type(int /*testn*/, result_t type) const { 109 return type == AVG ? "" : 0; // only average result by default 110 } 111 }; 112 113 /***** 114 a user's tester concept: 115 116 class tester: public TesterBase { 117 public: 118 //! init tester with known amount of work 119 tester() : TesterBase(<user-specified tests count>) { ... } 120 121 //! run a test with sequental number @arg test_number for @arg thread. 122 / *override* / value_t test(int test_number, int thread); 123 }; 124 125 ******/ 126 127 template<typename Tester, int scale = 1> 128 class TimeTest : public Tester { test(int testn,int threadn)129 /*override*/ value_t test(int testn, int threadn) { 130 Timer timer; 131 Tester::test(testn, threadn); 132 return timer.get_time() * double(scale); 133 } 134 }; 135 136 template<typename Tester> 137 class NanosecPerValue : public Tester { test(int testn,int threadn)138 /*override*/ value_t test(int testn, int threadn) { 139 Timer timer; 140 Tester::test(testn, threadn); 141 // return time (ns) per value 142 return timer.get_time()*1e+9/double(Tester::value); 143 } 144 }; 145 146 template<typename Tester, int scale = 1> 147 class ValuePerSecond : public Tester { test(int testn,int threadn)148 /*override*/ value_t test(int testn, int threadn) { 149 Timer timer; 150 Tester::test(testn, threadn); 151 // return value per seconds/scale 152 return double(Tester::value)/(timer.get_time()*scale); 153 } 154 }; 155 156 template<typename Tester, int scale = 1> 157 class NumberPerSecond : public Tester { test(int testn,int threadn)158 /*override*/ value_t test(int testn, int threadn) { 159 Timer timer; 160 Tester::test(testn, threadn); 161 // return a scale per seconds 162 return double(scale)/timer.get_time(); 163 } 164 }; 165 166 // operate with single tester 167 class TestRunner { 168 friend class TestProcessor; 169 friend struct RunArgsBody; 170 TestRunner(const TestRunner &); // don't copy 171 172 const char *tester_name; 173 StatisticsCollector *stat; 174 std::vector<std::vector<StatisticsCollector::TestCase> > keys; 175 176 public: 177 TesterBase &tester; 178 179 template<typename Test> TestRunner(const char * name,Test * test)180 TestRunner(const char *name, Test *test) 181 : tester_name(name), tester(*static_cast<TesterBase*>(test)) 182 { 183 test->tester_name = name; 184 } 185 ~TestRunner()186 ~TestRunner() { delete &tester; } 187 init(arg_t value,int threads,Harness::SpinBarrier & barrier,StatisticsCollector * s)188 void init(arg_t value, int threads, Harness::SpinBarrier &barrier, StatisticsCollector *s) { 189 tester.base_init(value, threads, barrier); 190 stat = s; 191 keys.resize(tester.tests_count); 192 for(int testn = 0; testn < tester.tests_count; testn++) { 193 keys[testn].resize(threads); 194 std::string test_name(tester.get_name(testn)); 195 for(int threadn = 0; threadn < threads; threadn++) 196 keys[testn][threadn] = stat->SetTestCase(tester_name, test_name.c_str(), threadn); 197 } 198 } 199 run_test(int threadn)200 void run_test(int threadn) { 201 for(int testn = 0; testn < tester.tests_count; testn++) { 202 tester.test_prefix(testn, threadn); 203 tester.barrier->wait(); // <<<<<<<<<<<<<<<<< Barrier before running test mode 204 value_t result = tester.test(testn, threadn); 205 stat->AddRoundResult(keys[testn][threadn], result); 206 } 207 } 208 post_process(StatisticsCollector & report)209 void post_process(StatisticsCollector &report) { 210 const int threads = tester.threads_count; 211 for(int testn = 0; testn < tester.tests_count; testn++) { 212 size_t coln = keys[testn][0].getResults().size()-1; 213 value_t rsum = keys[testn][0].getResults()[coln]; 214 value_t rmin = rsum, rmax = rsum; 215 for(int threadn = 1; threadn < threads; threadn++) { 216 value_t result = keys[testn][threadn].getResults()[coln]; 217 rsum += result; // for both SUM or AVG 218 if(rmin > result) rmin = result; 219 if(rmax < result) rmax = result; 220 } 221 std::string test_name(tester.get_name(testn)); 222 const char *rname = tester.get_result_type(testn, TesterBase::SUM); 223 if( rname ) { 224 report.SetTestCase(tester_name, (test_name+rname).c_str(), threads); 225 report.AddRoundResult(rsum); 226 } 227 rname = tester.get_result_type(testn, TesterBase::MIN); 228 if( rname ) { 229 report.SetTestCase(tester_name, (test_name+rname).c_str(), threads); 230 report.AddRoundResult(rmin); 231 } 232 rname = tester.get_result_type(testn, TesterBase::AVG); 233 if( rname ) { 234 report.SetTestCase(tester_name, (test_name+rname).c_str(), threads); 235 report.AddRoundResult(rsum / threads); 236 } 237 rname = tester.get_result_type(testn, TesterBase::MAX); 238 if( rname ) { 239 report.SetTestCase(tester_name, (test_name+rname).c_str(), threads); 240 report.AddRoundResult(rmax); 241 } 242 } 243 } 244 }; 245 246 struct RunArgsBody { 247 const vector<TestRunner*> &run_list; RunArgsBodyRunArgsBody248 RunArgsBody(const vector<TestRunner*> &a) : run_list(a) { } 249 #ifndef __TBB_parallel_for_H operatorRunArgsBody250 void operator()(int thread) const { 251 #else 252 void operator()(const tbb::blocked_range<int> &r) const { 253 ASSERT( r.begin() + 1 == r.end(), 0); 254 int thread = r.begin(); 255 #endif 256 for(size_t i = 0; i < run_list.size(); i++) 257 run_list[i]->run_test(thread); 258 } 259 }; 260 261 //! Main test processor. 262 /** Override or use like this: 263 class MyTestCollection : public TestProcessor { 264 void factory(arg_t value, int threads) { 265 process( value, threads, 266 run("my1", new tester<my1>() ), 267 run("my2", new tester<my2>() ), 268 end ); 269 if(value == threads) 270 stat->Print(); 271 } 272 }; 273 */ 274 275 class TestProcessor { 276 friend class TesterBase; 277 278 // <threads, collector> 279 typedef std::map<int, StatisticsCollector *> statistics_collection; 280 statistics_collection stat_by_threads; 281 282 protected: 283 // Members 284 const char *collection_name; 285 // current stat 286 StatisticsCollector *stat; 287 // token 288 size_t end; 289 290 public: 291 StatisticsCollector report; 292 293 // token of tests list 294 template<typename Test> run(const char * name,Test * test)295 TestRunner *run(const char *name, Test *test) { 296 return new TestRunner(name, test); 297 } 298 299 // iteration processing process(arg_t value,int threads,...)300 void process(arg_t value, int threads, ...) { 301 // prepare items 302 stat = stat_by_threads[threads]; 303 if(!stat) { 304 stat_by_threads[threads] = stat = new StatisticsCollector((collection_name + Format("@%d", threads)).c_str(), StatisticsCollector::ByAlg); 305 stat->SetTitle("Detailed log of %s running with %d threads.", collection_name, threads); 306 } 307 Harness::SpinBarrier barrier(threads); 308 // init args 309 va_list args; va_start(args, threads); 310 vector<TestRunner*> run_list; run_list.reserve(16); 311 while(true) { 312 TestRunner *item = va_arg(args, TestRunner*); 313 if( !item ) break; 314 item->init(value, threads, barrier, stat); 315 run_list.push_back(item); 316 } 317 va_end(args); 318 std::ostringstream buf; 319 buf << value; 320 const size_t round_number = stat->GetRoundsCount(); 321 stat->SetRoundTitle(round_number, buf.str().c_str()); 322 report.SetRoundTitle(round_number, buf.str().c_str()); 323 // run them 324 #ifndef __TBB_parallel_for_H 325 NativeParallelFor(threads, RunArgsBody(run_list)); 326 #else 327 tbb::parallel_for(tbb::blocked_range<int>(0,threads,1), RunArgsBody(run_list)); 328 #endif 329 // destroy args 330 for(size_t i = 0; i < run_list.size(); i++) { 331 run_list[i]->post_process(report); 332 delete run_list[i]; 333 } 334 } 335 336 public: 337 TestProcessor(const char *name, StatisticsCollector::Sorting sort_by = StatisticsCollector::ByAlg) collection_name(name)338 : collection_name(name), stat(NULL), end(0), report(collection_name, sort_by) 339 { } 340 ~TestProcessor()341 ~TestProcessor() { 342 for(statistics_collection::iterator i = stat_by_threads.begin(); i != stat_by_threads.end(); i++) 343 delete i->second; 344 } 345 }; 346 347 #endif// __TIME_FRAMEWORK_H__ 348