/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright (C) 2008-2008 Google Inc
opensource@google.com
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307, USA.
The GNU General Public License is contained in the file COPYING.
*/
// Author: Timur Iskhodzhanov <opensource@google.com>
//
// This file contains a set of benchmarks for data race detection tools.
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include <cstring> // strlen(), index(), rindex()
#include <ctime>
#include <math.h>
#include "thread_wrappers.h"
#include "linear_solver.h"
class Mutex64: public Mutex {
// force sizeof(Mutex64) >= 64
private:
char ___[(sizeof(Mutex) > 64) ? (0) : (64 - sizeof(Mutex))];
};
enum StatType {
ZZZERO,
N_THREADS,
N_CV,
N_CV_SIGNALS,
N_CV_WAITS,
N_MUTEXES,
N_MUTEX_LOCK_UNLOCK,
N_MEM_ACCESSES_K, // kiloaccesses
/*N_RACEY_ACCESSES*/
};
class Test{
typedef void (*void_func_void_t)(void);
/* may return false to indicate smth like "Wait didn't succeed" */
typedef bool (*bool_func_void_t)(void);
//typedef TestStats (*TestStats_func_void_t)(void);
//TestStats_func_void_t GetStats_;
void_func_void_t Run_v_;
bool_func_void_t Run_b_;
public:
Test() : Run_v_(0), Run_b_(0) {}
Test(int id, void_func_void_t _Run) : Run_v_(_Run), Run_b_(0) {
CHECK(Run_v_ != NULL);
}
Test(int id, bool_func_void_t _Run) : Run_v_(0), Run_b_(_Run) {
CHECK(Run_b_ != NULL);
}
bool Run() {
if (Run_v_ == NULL) {
CHECK(Run_b_ != NULL);
return (*Run_b_)();
} else {
Run_v_();
return true;
}
}
};
typedef std::map<int, Test> MapOfTests;
MapOfTests the_map_of_tests;
class GoalStats {
public:
typedef std::vector<StatType> TypesVector;
private:
//TODO: think of better names
std::map<StatType, int> goal;
TypesVector types;
Vector * stats;
typedef std::set<void (*)()> void_f_void_set;
void_f_void_set param_registerers, param_appliers;
std::vector<double*> parameters;
Matrix * cost_m;
bool calculated;
template<typename T> static int v_find(const std::vector<T> & vec, const T & val) {
for (int i = 0; i < vec.size(); i++)
if (vec[i] == val)
return i;
return -1;
}
public:
GoalStats(): stats(NULL), cost_m(NULL), calculated(false) {}
~GoalStats() { delete stats; delete cost_m; }
// Note that this function is called before main()
void AddPattern(void (*register_func)(), void (*paramapply_func)()) {
param_registerers.insert(register_func);
param_appliers.insert(paramapply_func);
}
void RegisterPatterns() {
for(void_f_void_set::iterator i = param_registerers.begin();
i != param_registerers.end(); i++)
{
(*(*i))(); // call each param_registerer
}
}
void AddGoal(StatType type, int value) {
CHECK(stats == NULL);
CHECK(goal.find(type) == goal.end());
goal[type] = value;
types.push_back(type);
}
void CompileStatsIntoVector() {
CHECK(stats == NULL);
CHECK(types.size() == goal.size());
stats = new Vector(types.size());
for (int i = 0; i < types.size(); i++)
(*stats)[i] = goal[types[i]];
cost_m = new Matrix(types.size(), 0);
}
const Vector & GetStatsVector() {
return *stats;
}
TypesVector GetTypes() {
CHECK(stats != NULL);
return types;
}
void RegisterParameter(double * param) {
CHECK(stats != NULL);
// int param_id = parameters.size();
parameters.push_back(param);
cost_m->IncN();
}
void SetParameterStat(StatType stat_type, double * param, double cost) {
int type_id = v_find(types, stat_type);
if (type_id == -1)
return; // this stat type isn't required - ignore
int param_id = v_find(parameters, param);
CHECK(param_id != -1);
cost_m->At(type_id, param_id) = cost;
}
void CalculateAndApplyParameters() {
CHECK(calculated == false);
printf("Cost matrix:\n%s\n", cost_m->ToString().c_str());
printf("Stats vector:\n%s\n", stats->ToString().c_str());
int iterations = 0;
Vector params = EstimateParameters(*cost_m, *stats, 0.0005, &iterations);
CHECK(params.GetSize() == parameters.size());
/*params[0] = 1000;
params[1] = 3600;
params[2] = 80;
params[3] = 0;
params[4] = 19530;
params[5] = 1720;*/
printf("Parameters (estimated in %d steps) :\n", iterations);
for (int i = 0; i < parameters.size(); i++) {
printf("param[%i] = %lf\n", i, params[i]);
*(parameters[i]) = params[i];
}
printf("Est. stats: %s\n", cost_m->MultiplyRight(params).ToString().c_str());
for (void_f_void_set::iterator i = param_appliers.begin();
i != param_appliers.end(); i++)
{
(*(*i))();
}
fflush(stdout);
calculated = true;
}
} goals;
template <typename RetVal>
struct TestAdder {
TestAdder(int id, RetVal (*_Run)(),
void (*paramreg)(void),
void (*paramapply)(void))
{
CHECK(the_map_of_tests.count(id) == 0);
the_map_of_tests[id] = Test(id, _Run);
goals.AddPattern(paramreg, paramapply);
}
};
#define REGISTER_PATTERN(id) TestAdder<void> add_test##id(id, Pattern##id, \
ParametersRegistration##id, ApplyParameters##id)
#define REGISTER_PATTERN_PROB(id) TestAdder<bool> add_test##id(id, Pattern##id, \
ParametersRegistration##id, ApplyParameters##id)
ThreadPool * mainThreadPool;
std::map<int, double> map_of_counts; // test -> average run count
inline double round(double lf) {
return floor(lf + 0.5);
}
// Accessing memory locations holding one lock {{{1
namespace one_lock {
struct Params {
double num_contexts;
int NUM_CONTEXTS;
double num_iterations_times_runcount;
int NUM_ITERATIONS;
//double data_size_times_;
static const int DATA_SIZE = 128;
static const int REDO_CNT = 2;
} params;
struct TestContext {
Mutex64 MU;
int * data;
TestContext() {
data = new int[params.DATA_SIZE];
}
} *contexts;
// Write accesses
void Pattern101() {
int id = rand() % params.NUM_CONTEXTS;
TestContext * context = &contexts[id];
for (int i = 0; i < params.NUM_ITERATIONS; i++) {
context->MU.Lock();
for (int j = 0; j < params.DATA_SIZE; j++) {
for (int k = 0; k < params.REDO_CNT; k++)
context->data[j] = 77; // write
}
context->MU.Unlock();
}
}
void ParametersRegistration101() {
map_of_counts[101] = 100;
//goals.RegisterParameter(&map_of_counts[101]);
goals.RegisterParameter(¶ms.num_iterations_times_runcount);
goals.SetParameterStat(N_MUTEX_LOCK_UNLOCK, ¶ms.num_iterations_times_runcount, 3 /*don't ask why*/);
goals.SetParameterStat(N_MEM_ACCESSES_K, ¶ms.num_iterations_times_runcount,
params.DATA_SIZE * params.REDO_CNT * 2.0 / 1000.0);
goals.RegisterParameter(¶ms.num_contexts);
goals.SetParameterStat(N_MUTEXES, ¶ms.num_contexts, 1);
}
void ApplyParameters101() {
if (map_of_counts[101] < 1.0)
map_of_counts[101] = 1.0;
params.NUM_CONTEXTS = round(params.num_contexts);
if (params.NUM_CONTEXTS <= 0)
params.NUM_CONTEXTS = 1;
params.NUM_ITERATIONS = round(params.num_iterations_times_runcount / map_of_counts[101]);
contexts = new TestContext[params.NUM_CONTEXTS];
}
REGISTER_PATTERN(101);
/* other tests...
// Read accesses
void Pattern102() {
int id = rand() % NUM_CONTEXTS;
TestContext * context = &contexts[id];
for (int i = 0; i < NUM_ITERATIONS; i++) {
int temp = 0;
context->MU.Lock();
for (int j = 0; j < DATA_SIZE; j++) {
for (int k = 0; k < 10; k++)
temp += context->data[j]; // read
}
context->MU.Unlock();
}
}
REGISTER_PATTERN(102);
int atomic_integers[NUM_CONTEXTS] = {0};
// Atomic increment
void Pattern103() {
int id = rand() % NUM_CONTEXTS;
for (int i = 0; i < NUM_ITERATIONS; i++)
__sync_add_and_fetch(&atomic_integers[id], 1);
}
REGISTER_PATTERN(103);
*/
} // namespace one_lock
/* other namespaces...
// Accessing memory locations holding random LockSets {{{1
namespace multiple_locks {
// TODO: make these constants as parameters
const int NUM_CONTEXTS = 1024;
const int DATA_SIZE = 4096;
const int NUM_ITERATIONS = 1;
const int LOCKSET_SIZE = 2;
struct TestContext {
Mutex64 MU;
int data[DATA_SIZE];
} contexts[NUM_CONTEXTS];
// Access random context holding a random LS including context->MU
void Pattern201() {
TestContext * context = &contexts[rand() % NUM_CONTEXTS];
std::vector<Mutex64*> LS;
// STL nightmare starts here - calculate random LS{{{1
{
std::vector<int> tmp_LS;
for (int i = 0; i < NUM_CONTEXTS; i++)
tmp_LS.push_back(i);
std::random_shuffle(tmp_LS.begin(), tmp_LS.end());
// TODO: #LS as a parameter
for (int i = 0; i < LOCKSET_SIZE; i++)
LS.push_back(&contexts[tmp_LS[i]].MU);
// This LS should contain context's Mutex to have proper synchronization
LS.push_back(&context->MU);
// LS should be sorted to avoid deadlocks
std::sort(LS.begin(), LS.end());
// LS should not contain context->MU twice
std::vector<Mutex64*>::iterator new_end = std::unique(LS.begin(), LS.end());
LS.erase(new_end, LS.end());
} // end of STL nightmare :-)
for (int i = 0; i < NUM_ITERATIONS; i++) {
for (std::vector<Mutex64*>::iterator it = LS.begin(); it != LS.end(); it++)
(*it)->Lock();
for (int j = 0; j < DATA_SIZE; j++)
context->data[j] = 77;
for (std::vector<Mutex64*>::reverse_iterator it = LS.rbegin(); it != LS.rend(); it++)
(*it)->Unlock();
}
}
REGISTER_PATTERN(201);
const int MAX_LOCKSET_SIZE = 3;
const int NUM_LOCKSETS = 1 << MAX_LOCKSET_SIZE;
Mutex64 ls_mu[MAX_LOCKSET_SIZE];
char ls_data[NUM_LOCKSETS][DATA_SIZE];
// Access random context holding a corresponding LockSet
void Pattern202() {
int ls_idx = 0;
while (ls_idx == 0)
ls_idx = rand() % NUM_LOCKSETS;
char * data = ls_data[ls_idx];
for (int i = 0; i < MAX_LOCKSET_SIZE; i++)
if (ls_idx & (1 << i))
ls_mu[i].Lock();
for (int j = 0; j < DATA_SIZE; j++)
data[j] = 77;
for (int i = MAX_LOCKSET_SIZE - 1; i >= 0; i--)
if (ls_idx & (1 << i))
ls_mu[i].Unlock();
}
REGISTER_PATTERN(202);
} // namespace multiple_locks
*/
// Publishing objects using different synchronization patterns {{{1
namespace publishing {
/*namespace pcq {
const int NUM_CONTEXTS = 16;
struct Params {
double num_contexts;
int NUM_CONTEXTS;
double data_size_times_runcount;
int DATA_SIZE;
} params;
struct TestContext {
ProducerConsumerQueue pcq;
TestContext() : pcq(0) {}
~TestContext() {
void * ptr = NULL;
// Erase the contents of the PCQ. We assume NULL can't be there
pcq.Put(NULL);
while(ptr = pcq.Get())
free(ptr);
}
} * contexts;
// Publish a random string into a random PCQ
void Pattern301() {
TestContext * context = &contexts[rand() % params.NUM_CONTEXTS];
// TODO: str_len as a parameter
int str_len = 1 + (rand() % params.DATA_SIZE);
char * str = (char*)malloc(str_len + 1);
CHECK(str != NULL);
memset(str, 'a', str_len);
str[str_len] = '\0';
context->pcq.Put(str);
}
REGISTER_PATTERN(301);
// Read a published string from a random PCQ. MAYFAIL!
bool Pattern302() {
TestContext * context = &contexts[rand() % NUM_CONTEXTS];
char * str = NULL;
if (context->pcq.TryGet((void**)&str)) {
int tmp = strlen(str);
free(str);
return true;
}
return false;
}
REGISTER_PATTERN(302);
}*/
namespace condvar {
struct Params {
double num_contexts;
int NUM_CONTEXTS;
double data_size_times_runcount;
int DATA_SIZE;
Params() {
DATA_SIZE = 1;
HIT_PROBABILITY = 0.3; // estimate. TODO: think of a better idea
}
const static int REDO = 100;
double HIT_PROBABILITY;
double EstimateRuncount() {
return map_of_counts[311] + HIT_PROBABILITY * map_of_counts[312];
}
} params;
struct TestContext {
Mutex64 MU;
CondVar CV;
int CV_Signalled;
char * data;
TestContext () {
data = NULL;
CV_Signalled = 0;
}
} *contexts;
// Signal a random CV
void Pattern311() {
int id = rand() % params.NUM_CONTEXTS;
TestContext * context = &contexts[id];
context->MU.Lock();
if (context->data) {
free(context->data);
}
int LEN = params.DATA_SIZE;
context->data = (char*)malloc(LEN + 1);
for (int i = 0; i < params.REDO; i++)
for (int j = 0; j < LEN; j++)
context->data[j] = 'a';
context->data[LEN] = '\0';
context->CV.Signal();
context->CV_Signalled = params.NUM_CONTEXTS;
context->MU.Unlock();
}
void ParametersRegistration311() {
double * num_CV_Signals = &map_of_counts[311];
goals.RegisterParameter(num_CV_Signals);
goals.SetParameterStat(N_CV_SIGNALS, num_CV_Signals, 1);
goals.SetParameterStat(N_MUTEX_LOCK_UNLOCK, num_CV_Signals, 9 /* don't ask why */);
goals.RegisterParameter(¶ms.num_contexts);
goals.SetParameterStat(N_CV, ¶ms.num_contexts, 1);
goals.SetParameterStat(N_MUTEXES, ¶ms.num_contexts, 1);
goals.RegisterParameter(¶ms.data_size_times_runcount);
goals.SetParameterStat(N_MEM_ACCESSES_K, ¶ms.data_size_times_runcount,
(2*params.REDO) * (1.0 + params.HIT_PROBABILITY) / 1000.0);
}
void ApplyParameters311() {
if (map_of_counts[311] < 1.0)
map_of_counts[311] = 1.0;
params.DATA_SIZE = 1 + round(params.data_size_times_runcount / params.EstimateRuncount());
/*if (params.DATA_SIZE < 1)
params.DATA_SIZE = 1;*/
params.NUM_CONTEXTS = round(params.num_contexts);
if (params.NUM_CONTEXTS < 1)
params.NUM_CONTEXTS = 1;
contexts = new TestContext[params.NUM_CONTEXTS];
}
/*void TMP311 () {
map_of_counts[311] = 1000;
params.NUM_CONTEXTS = 100;
params.DATA_SIZE = 10000;
contexts = new TestContext[params.NUM_CONTEXTS];
}*/
REGISTER_PATTERN(311);
// Wait on a random CV
bool Pattern312() {
int nAttempts = 0,
id = rand() % params.NUM_CONTEXTS;
TestContext * context;
do {
if (nAttempts++ > params.NUM_CONTEXTS)
return false;
context = &contexts[id];
id = (id + 1) % params.NUM_CONTEXTS;
} while (ANNOTATE_UNPROTECTED_READ(context->CV_Signalled) == 0);
context->MU.Lock();
context->CV_Signalled--;
bool ret = !context->CV.WaitWithTimeout(&context->MU, 10);
if (ret && context->data) {
// int tmp = strlen(context->data);
free(context->data);
context->data = NULL;
}
context->MU.Unlock();
return ret;
}
void ParametersRegistration312() {
double * num_CV_Waits = &map_of_counts[312];
goals.RegisterParameter(num_CV_Waits);
goals.SetParameterStat(N_CV_WAITS, num_CV_Waits, params.HIT_PROBABILITY);
goals.SetParameterStat(N_MUTEX_LOCK_UNLOCK, num_CV_Waits, 16);
// N_MEM_ACCESSES_K is counted in ParametersRegistration312
}
void ApplyParameters312() {
//nothing to do, see ApplyParameters311
}
REGISTER_PATTERN_PROB(312);
}
} // namespace publishing
/*
// Threads work with their memory exclusively {{{1
namespace thread_local {
// Thread accesses heap
void Pattern401() {
// TODO: parameters
const int DATA_SIZE = 1024;
const int ITERATIONS = 16;
char * temp = (char*)malloc(DATA_SIZE + 1);
for (int i = 1; i <= ITERATIONS; i++) {
memset(temp, i, DATA_SIZE);
temp[DATA_SIZE] = 0;
int size = strlen(temp);
}
free(temp);
}
REGISTER_PATTERN(401);
// Thread accesses stack
void Pattern402() {
// TODO: parameters
const int DATA_SIZE = 1024;
const int ITERATIONS = 16;
char temp[DATA_SIZE];
for (int i = 1; i <= ITERATIONS; i++) {
memset(temp, i, DATA_SIZE);
temp[DATA_SIZE] = 0;
int size = strlen(temp);
}
}
REGISTER_PATTERN(402);
} // namespace thread_local
// Different benign races scenarios {{{1
namespace benign_races {
namespace stats {
int simple_counter = 0;
int odd_counter = 1;
Mutex64 odd_counter_mu;
struct __ {
__() {
ANNOTATE_BENIGN_RACE(&simple_counter, "Pattern501");
}
} _;
void Pattern501() {
simple_counter++;
}
REGISTER_PATTERN(501);
// increment odd_counter, but first check it is >0 (double-check)
void Pattern502() {
if (ANNOTATE_UNPROTECTED_READ(odd_counter) > 0) {
odd_counter_mu.Lock();
if (odd_counter > 0)
odd_counter++;
odd_counter_mu.Unlock();
}
}
REGISTER_PATTERN(502);
}
} // namespace benign_races
*/
typedef std::map<std::string, StatType> StatMap;
StatMap statNames;
int nThreads = 2;
void PatternDispatcher() {
/*std::vector<int> availablePatterns;
for (MapOfTests::iterator it = the_map_of_tests.begin();
it != the_map_of_tests.end(); it++) {
if (map_of_counts[it->first] > 0.0)
availablePatterns.push_back(it->first);
}*/
std::map<int, int> this_thread_runcounts;
int total = 0;
for (std::map<int,double>::iterator it = map_of_counts.begin(); it != map_of_counts.end(); it++) {
CHECK(it->second >= 0.0);
int count = round(it->second / nThreads);
this_thread_runcounts[it->first] = count;
total += count;
}
CHECK(total > 0);
for (int i = 0; i < total; i++) {
//while (total > 0) {
int rnd = rand() % total;
int test_idx = -1;
for (std::map<int,int>::iterator it = this_thread_runcounts.begin(); it != this_thread_runcounts.end(); it++) {
int this_test_count = it->second;
if (rnd < this_test_count) {
test_idx = it->first;
break;
}
rnd -= this_test_count;
}
CHECK(test_idx >= 0);
// TODO: the above code should be replaced with a proper randomizer
// with a "specify distribution function" feature
if (the_map_of_tests[test_idx].Run()) {
/* this_thread_runcounts[test_idx]--;
total--;*/
}
}
}
namespace N_THREADS_hack {
double nThreads_double = 2.0;
void Registerer() {
goals.RegisterParameter(&nThreads_double);
goals.SetParameterStat(N_THREADS, &nThreads_double, 1);
}
void Applier() {
nThreads = round(nThreads_double);
CHECK(nThreads >= 2);
}
}
void RegisterStatNames() {
#define REGISTER_STAT_NAME(a) statNames[#a] = a
goals.AddPattern(N_THREADS_hack::Registerer, N_THREADS_hack::Applier);
REGISTER_STAT_NAME(N_THREADS);
REGISTER_STAT_NAME(N_CV);
REGISTER_STAT_NAME(N_CV_SIGNALS);
REGISTER_STAT_NAME(N_CV_WAITS);
REGISTER_STAT_NAME(N_MUTEXES);
REGISTER_STAT_NAME(N_MUTEX_LOCK_UNLOCK);
REGISTER_STAT_NAME(N_MEM_ACCESSES_K);
}
int main(int argc, const char **argv) {
long init = GetTimeInMs();
RegisterStatNames();
const char *default_goals[] = {"N_THREADS=20", "N_MEM_ACCESSES_K=130000",
"N_MUTEXES=1800", "N_CV=80", "N_MUTEX_LOCK_UNLOCK=107000",
"N_CV_SIGNALS=3600", "N_CV_WAITS=500"};
const char ** goal_list = NULL;
int goal_cnt = 0;
if (argc == 1) {
printf("Running the default pattern\n");
goal_list = default_goals;
goal_cnt = sizeof(default_goals) / sizeof(*default_goals);
} else if (argc == 2 && !strcmp(argv[1], "--help")) {
printf("Usage: bigtest [PARAM=VALUE] ...\n Available params: ");
for (StatMap::iterator i = statNames.begin(); i != statNames.end(); i++) {
printf ("%s%s", (i == statNames.begin()) ? "" : ", ",
(*i).first.c_str());
}
printf("\n");
return 0;
} else {
goal_list = argv + 1;
goal_cnt = argc - 1;
}
{
// Parse goal strings
for (int i = 0; i < goal_cnt; i++) {
const char * goal = goal_list[i];
char stat[256] = "";
int stat_val = -1, j = 0;
for (; j < sizeof(stat) - 1
&& goal[j] != '='
&& goal[j] != '\0'; j++) {
stat[j] = goal[j];
}
stat[j] = '\0';
if (goal[j] == '=')
sscanf(goal + j + 1, "%i", &stat_val);
printf("%s = %i\n", stat, stat_val);
if (goal[j] != '='
|| strlen(stat) == 0
|| stat_val < 0
|| statNames.find(stat) == statNames.end()
) {
fprintf(stderr, "Error parsing goal \"%s\"\n", goal);
CHECK(0);
}
goals.AddGoal(statNames[stat], stat_val);
}
printf("\n");
}
goals.CompileStatsIntoVector();
Vector statsVector = goals.GetStatsVector();
goals.RegisterPatterns();
goals.CalculateAndApplyParameters();/**/
long start = GetTimeInMs();
printf("\nParameters calculated in %dms\nBenchmarking...\n",
(int)(start - init));
// Start (N_THREADS - 1) new threads...
mainThreadPool = new ThreadPool(nThreads - 1);
mainThreadPool->StartWorkers();
for (int i = 0; i < nThreads - 1; i++) {
mainThreadPool->Add(NewCallback(PatternDispatcher));
}
PatternDispatcher(); // and 1 more in the main thread
delete mainThreadPool;
long end = GetTimeInMs();
printf("...done in %dms\n", (int)(end - start));
printf("*RESULT bigtest: time= %d ms\n", (int)(end - start));
return 0;
}