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