blob: 59a636c927aac46afba23956b8f66ce5f7448f10 [file] [log] [blame]
/*
Copyright 2005-2010 Intel Corporation. All Rights Reserved.
This file is part of Threading Building Blocks.
Threading Building Blocks is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
Threading Building Blocks is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Threading Building Blocks; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, you may use this file as part of a free software
library without restriction. Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/
#ifndef __TIME_FRAMEWORK_H__
#define __TIME_FRAMEWORK_H__
#include <cstdlib>
#include <math.h>
#include <vector>
#include <string>
#include <sstream>
#include "tbb/tbb_stddef.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/tick_count.h"
#define HARNESS_CUSTOM_MAIN 1
#include "../test/harness.h"
#include "../test/harness_barrier.h"
#define STATISTICS_INLINE
#include "statistics.h"
#ifndef ARG_TYPE
typedef intptr_t arg_t;
#else
typedef ARG_TYPE arg_t;
#endif
class Timer {
tbb::tick_count tick;
public:
Timer() { tick = tbb::tick_count::now(); }
double get_time() { return (tbb::tick_count::now() - tick).seconds(); }
double diff_time(const Timer &newer) { return (newer.tick - tick).seconds(); }
double mark_time() { tbb::tick_count t1(tbb::tick_count::now()), t2(tick); tick = t1; return (t1 - t2).seconds(); }
double mark_time(const Timer &newer) { tbb::tick_count t(tick); tick = newer.tick; return (tick - t).seconds(); }
};
class TesterBase /*: public tbb::internal::no_copy*/ {
protected:
friend class TestProcessor;
friend class TestRunner;
//! it is barrier for synchronizing between threads
Harness::SpinBarrier *barrier;
//! number of tests per this tester
const int tests_count;
//! number of threads to operate
int threads_count;
//! some value for tester
arg_t value;
// avoid false sharing
char pad[128 - sizeof(arg_t) - sizeof(int)*2 - sizeof(void*) ];
public:
//! init tester base. @arg ntests is number of embeded tests in this tester.
TesterBase(int ntests)
: barrier(NULL), tests_count(ntests)
{}
virtual ~TesterBase() {}
//! internal function
void base_init(arg_t v, int t, Harness::SpinBarrier &b) {
threads_count = t;
barrier = &b;
value = v;
init();
}
//! optionally override to init after value and threads count were set.
virtual void init() { }
//! Override to provide your names
virtual std::string get_name(int testn) {
return Format("test %d", testn);
}
//! optionally override to init test mode just before execution for a given thread number.
virtual void test_prefix(int testn, int threadn) { }
//! Override to provide main test's entry function returns a value to record
virtual value_t test(int testn, int threadn) = 0;
//! Type of aggregation from results of threads
enum result_t {
SUM, AVG, MIN, MAX
};
//! Override to change result type for the test. Return postfix for test name or 0 if result type is not needed.
virtual const char *get_result_type(int /*testn*/, result_t type) const {
return type == AVG ? "" : 0; // only average result by default
}
};
/*****
a user's tester concept:
class tester: public TesterBase {
public:
//! init tester with known amount of work
tester() : TesterBase(<user-specified tests count>) { ... }
//! run a test with sequental number @arg test_number for @arg thread.
/ *override* / value_t test(int test_number, int thread);
};
******/
template<typename Tester, int scale = 1>
class TimeTest : public Tester {
/*override*/ value_t test(int testn, int threadn) {
Timer timer;
Tester::test(testn, threadn);
return timer.get_time() * double(scale);
}
};
template<typename Tester>
class NanosecPerValue : public Tester {
/*override*/ value_t test(int testn, int threadn) {
Timer timer;
Tester::test(testn, threadn);
// return time (ns) per value
return timer.get_time()*1000000.0/double(Tester::value);
}
};
template<typename Tester, int scale = 1>
class ValuePerSecond : public Tester {
/*override*/ value_t test(int testn, int threadn) {
Timer timer;
Tester::test(testn, threadn);
// return time value per seconds/scale
return double(Tester::value)/(timer.get_time()*scale);
}
};
// operate with single tester
class TestRunner {
friend class TestProcessor;
friend struct RunArgsBody;
TestRunner(const TestRunner &); // don't copy
const char *tester_name;
StatisticsCollector *stat;
std::vector<std::vector<StatisticsCollector::TestCase> > keys;
public:
TesterBase &tester;
template<typename Test>
TestRunner(const char *name, Test *test)
: tester_name(name), tester(*static_cast<TesterBase*>(test))
{}
~TestRunner() { delete &tester; }
void init(arg_t value, int threads, Harness::SpinBarrier &barrier, StatisticsCollector *s) {
tester.base_init(value, threads, barrier);
stat = s;
keys.resize(tester.tests_count);
for(int testn = 0; testn < tester.tests_count; testn++) {
keys[testn].resize(threads);
std::string test_name(tester.get_name(testn));
for(int threadn = 0; threadn < threads; threadn++)
keys[testn][threadn] = stat->SetTestCase(tester_name, test_name.c_str(), threadn);
}
}
void run_test(int threadn) {
for(int testn = 0; testn < tester.tests_count; testn++) {
tester.test_prefix(testn, threadn);
tester.barrier->wait(); // <<<<<<<<<<<<<<<<< Barrier before running test mode
value_t result = tester.test(testn, threadn);
stat->AddRoundResult(keys[testn][threadn], result);
}
}
void post_process(StatisticsCollector &report) {
const int threads = tester.threads_count;
for(int testn = 0; testn < tester.tests_count; testn++) {
size_t coln = keys[testn][0].getResults().size()-1;
value_t rsum = keys[testn][0].getResults()[coln];
value_t rmin = rsum, rmax = rsum;
for(int threadn = 1; threadn < threads; threadn++) {
value_t result = keys[testn][threadn].getResults()[coln];
rsum += result; // for both SUM or AVG
if(rmin > result) rmin = result;
if(rmax < result) rmax = result;
}
std::string test_name(tester.get_name(testn));
const char *rname = tester.get_result_type(testn, TesterBase::SUM);
if( rname ) {
report.SetTestCase(tester_name, (test_name+rname).c_str(), threads);
report.AddRoundResult(rsum);
}
rname = tester.get_result_type(testn, TesterBase::MIN);
if( rname ) {
report.SetTestCase(tester_name, (test_name+rname).c_str(), threads);
report.AddRoundResult(rmin);
}
rname = tester.get_result_type(testn, TesterBase::AVG);
if( rname ) {
report.SetTestCase(tester_name, (test_name+rname).c_str(), threads);
report.AddRoundResult(rsum / threads);
}
rname = tester.get_result_type(testn, TesterBase::MAX);
if( rname ) {
report.SetTestCase(tester_name, (test_name+rname).c_str(), threads);
report.AddRoundResult(rmax);
}
}
}
};
struct RunArgsBody {
const vector<TestRunner*> &run_list;
RunArgsBody(const vector<TestRunner*> &a) : run_list(a) { }
#ifndef __TBB_parallel_for_H
void operator()(int thread) const {
#else
void operator()(const tbb::blocked_range<int> &r) const {
ASSERT( r.begin() + 1 == r.end(), 0);
int thread = r.begin();
#endif
for(size_t i = 0; i < run_list.size(); i++)
run_list[i]->run_test(thread);
}
};
//! Main test processor.
/** Override or use like this:
class MyTestCollection : public TestProcessor {
void factory(arg_t value, int threads) {
process( value, threads,
run("my1", new tester<my1>() ),
run("my2", new tester<my2>() ),
end );
if(value == threads)
stat->Print();
}
};
*/
class TestProcessor {
friend class TesterBase;
// <threads, collector>
typedef std::map<int, StatisticsCollector *> statistics_collection;
statistics_collection stat_by_threads;
protected:
// Members
const char *collection_name;
// current stat
StatisticsCollector *stat;
// token
size_t end;
public:
StatisticsCollector report;
// token of tests list
template<typename Test>
TestRunner *run(const char *name, Test *test) {
return new TestRunner(name, test);
}
// iteration processing
void process(arg_t value, int threads, ...) {
// prepare items
stat = stat_by_threads[threads];
if(!stat) {
stat_by_threads[threads] = stat = new StatisticsCollector((collection_name + Format("@%d", threads)).c_str(), StatisticsCollector::ByAlg);
stat->SetTitle("Detailed log of %s running with %d threads.", collection_name, threads);
}
Harness::SpinBarrier barrier(threads);
// init args
va_list args; va_start(args, threads);
vector<TestRunner*> run_list; run_list.reserve(16);
while(true) {
TestRunner *item = va_arg(args, TestRunner*);
if( !item ) break;
item->init(value, threads, barrier, stat);
run_list.push_back(item);
}
va_end(args);
std::ostringstream buf;
buf << value;
const size_t round_number = stat->GetRoundsCount();
stat->SetRoundTitle(round_number, buf.str().c_str());
report.SetRoundTitle(round_number, buf.str().c_str());
// run them
#ifndef __TBB_parallel_for_H
NativeParallelFor(threads, RunArgsBody(run_list));
#else
tbb::parallel_for(tbb::blocked_range<int>(0,threads,1), RunArgsBody(run_list));
#endif
// destroy args
for(size_t i = 0; i < run_list.size(); i++) {
run_list[i]->post_process(report);
delete run_list[i];
}
}
public:
TestProcessor(const char *name, StatisticsCollector::Sorting sort_by = StatisticsCollector::ByAlg)
: collection_name(name), stat(NULL), end(0), report(collection_name, sort_by)
{ }
~TestProcessor() {
for(statistics_collection::iterator i = stat_by_threads.begin(); i != stat_by_threads.end(); i++)
delete i->second;
}
};
#endif// __TIME_FRAMEWORK_H__