Merge pull request #120 from google/benchmark-fixtures

Add ability to use benchmark fixtures
This commit is contained in:
Eric 2015-04-06 19:38:16 -04:00
commit d51ba32791
5 changed files with 219 additions and 69 deletions

View file

@ -175,6 +175,36 @@ static void BM_test(benchmark::State& state) {
} }
``` ```
Benchmark Fixtures
------------------
Fixture tests are created by
first defining a type that derives from ::benchmark::Fixture and then
creating/registering the tests using the following macros:
* `BENCHMARK_F(ClassName, Method)`
* `BENCHMARK_DEFINE_F(ClassName, Method)`
* `BENCHMARK_REGISTER_F(ClassName, Method)`
For Example:
```c++
class MyFixture : public benchmark::Fixture {};
BENCHMARK_F(MyFixture, FooTest)(benchmark::State& st) {
while (st.KeepRunning()) {
...
}
}
BENCHMARK_DEFINE_F(MyFixture, BarTest)(benchmark::State& st) {
while (st.KeepRunning()) {
...
}
}
/* BarTest is NOT registered */
BENCHMARK_REGISTER_F(MyFixture, BarTest)->Threads(2);
/* BarTest is now registered */
```
Output Formats Output Formats
-------------- --------------

View file

@ -169,6 +169,7 @@ void RunSpecifiedBenchmarks(BenchmarkReporter* reporter);
namespace internal { namespace internal {
class Benchmark; class Benchmark;
class BenchmarkImp; class BenchmarkImp;
class BenchmarkFamilies;
template <class T> struct Voider { template <class T> struct Voider {
typedef void type; typedef void type;
@ -184,8 +185,13 @@ struct EnableIfString<T, typename Voider<typename T::basic_string>::type> {
void UseCharPointer(char const volatile*); void UseCharPointer(char const volatile*);
// Take ownership of the pointer and register the benchmark. Return the
// registered benchmark.
Benchmark* RegisterBenchmarkInternal(Benchmark*);
} // end namespace internal } // end namespace internal
// The DoNotOptimize(...) function can be used to prevent a value or // The DoNotOptimize(...) function can be used to prevent a value or
// expression from being optimized away by the compiler. This function is // expression from being optimized away by the compiler. This function is
// intented to add little to no overhead. // intented to add little to no overhead.
@ -371,10 +377,8 @@ typedef void(Function)(State&);
// Each method returns "this" so that multiple method calls can // Each method returns "this" so that multiple method calls can
// chained into one expression. // chained into one expression.
class Benchmark { class Benchmark {
public: public:
Benchmark(const char* name, Function* f); virtual ~Benchmark();
~Benchmark();
// Note: the following methods all return "this" so that multiple // Note: the following methods all return "this" so that multiple
// method calls can be chained together in one expression. // method calls can be chained together in one expression.
@ -445,15 +449,56 @@ class Benchmark {
// Equivalent to ThreadRange(NumCPUs(), NumCPUs()) // Equivalent to ThreadRange(NumCPUs(), NumCPUs())
Benchmark* ThreadPerCpu(); Benchmark* ThreadPerCpu();
virtual void Run(State& state) = 0;
// Used inside the benchmark implementation // Used inside the benchmark implementation
struct Instance; struct Instance;
private: protected:
BenchmarkImp* imp_; explicit Benchmark(const char* name);
BENCHMARK_DISALLOW_COPY_AND_ASSIGN(Benchmark); Benchmark(Benchmark const&);
void SetName(const char* name);
private:
friend class BenchmarkFamilies;
BenchmarkImp* imp_;
Benchmark& operator=(Benchmark const&);
};
// The class used to hold all Benchmarks created from static function.
// (ie those created using the BENCHMARK(...) macros.
class FunctionBenchmark : public Benchmark {
public:
FunctionBenchmark(const char* name, Function* func)
: Benchmark(name), func_(func)
{}
virtual void Run(State& st);
private:
Function* func_;
}; };
} // end namespace internal } // end namespace internal
// The base class for all fixture tests.
class Fixture: public internal::Benchmark {
public:
Fixture() : internal::Benchmark("") {}
virtual void Run(State& st) {
this->SetUp();
this->BenchmarkCase(st);
this->TearDown();
}
virtual void SetUp() {}
virtual void TearDown() {}
protected:
virtual void BenchmarkCase(State&) = 0;
};
} // end namespace benchmark } // end namespace benchmark
@ -480,7 +525,9 @@ class Benchmark {
BENCHMARK_PRIVATE_NAME(n) BENCHMARK_UNUSED BENCHMARK_PRIVATE_NAME(n) BENCHMARK_UNUSED
#define BENCHMARK(n) \ #define BENCHMARK(n) \
BENCHMARK_PRIVATE_DECLARE(n) = (new ::benchmark::internal::Benchmark(#n, n)) BENCHMARK_PRIVATE_DECLARE(n) = \
(::benchmark::internal::RegisterBenchmarkInternal( \
new ::benchmark::internal::FunctionBenchmark(#n, n)))
// Old-style macros // Old-style macros
#define BENCHMARK_WITH_ARG(n, a) BENCHMARK(n)->Arg((a)) #define BENCHMARK_WITH_ARG(n, a) BENCHMARK(n)->Arg((a))
@ -499,21 +546,53 @@ class Benchmark {
// will register BM_Foo<1> as a benchmark. // will register BM_Foo<1> as a benchmark.
#define BENCHMARK_TEMPLATE1(n, a) \ #define BENCHMARK_TEMPLATE1(n, a) \
BENCHMARK_PRIVATE_DECLARE(n) = \ BENCHMARK_PRIVATE_DECLARE(n) = \
(new ::benchmark::internal::Benchmark(#n "<" #a ">", n<a>)) (::benchmark::internal::RegisterBenchmarkInternal( \
new ::benchmark::internal::FunctionBenchmark(#n "<" #a ">", n<a>)))
#define BENCHMARK_TEMPLATE2(n, a, b) \ #define BENCHMARK_TEMPLATE2(n, a, b) \
BENCHMARK_PRIVATE_DECLARE(n) = \ BENCHMARK_PRIVATE_DECLARE(n) = \
(new ::benchmark::internal::Benchmark(#n "<" #a "," #b ">", n<a, b>)) (::benchmark::internal::RegisterBenchmarkInternal( \
new ::benchmark::internal::FunctionBenchmark( \
#n "<" #a "," #b ">", n<a, b>)))
#if __cplusplus >= 201103L #if __cplusplus >= 201103L
#define BENCHMARK_TEMPLATE(n, ...) \ #define BENCHMARK_TEMPLATE(n, ...) \
BENCHMARK_PRIVATE_DECLARE(n) = \ BENCHMARK_PRIVATE_DECLARE(n) = \
(new ::benchmark::internal::Benchmark( \ (::benchmark::internal::RegisterBenchmarkInternal( \
#n "<" #__VA_ARGS__ ">", n<__VA_ARGS__>)) new ::benchmark::internal::FunctionBenchmark( \
#n "<" #__VA_ARGS__ ">", n<__VA_ARGS__>)))
#else #else
#define BENCHMARK_TEMPLATE(n, a) BENCHMARK_TEMPLATE1(n, a) #define BENCHMARK_TEMPLATE(n, a) BENCHMARK_TEMPLATE1(n, a)
#endif #endif
#define BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \
class BaseClass##_##Method##_Benchmark : public BaseClass { \
public: \
BaseClass##_##Method##_Benchmark() : BaseClass() { \
this->SetName(#BaseClass "/" #Method);} \
protected: \
virtual void BenchmarkCase(::benchmark::State&); \
};
#define BENCHMARK_DEFINE_F(BaseClass, Method) \
BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \
void BaseClass##_##Method##_Benchmark::BenchmarkCase
#define BENCHMARK_REGISTER_F(BaseClass, Method) \
BENCHMARK_PRIVATE_REGISTER_F(BaseClass##_##Method##_Benchmark)
#define BENCHMARK_PRIVATE_REGISTER_F(TestName) \
BENCHMARK_PRIVATE_DECLARE(TestName) = \
(::benchmark::internal::RegisterBenchmarkInternal(new TestName()))
// This macro will define and register a benchmark within a fixture class.
#define BENCHMARK_F(BaseClass, Method) \
BENCHMARK_PRIVATE_DECLARE_F(BaseClass, Method) \
BENCHMARK_REGISTER_F(BaseClass, Method); \
void BaseClass##_##Method##_Benchmark::BenchmarkCase
// Helper macro to create a main routine in a test that runs the benchmarks // Helper macro to create a main routine in a test that runs the benchmarks
#define BENCHMARK_MAIN() \ #define BENCHMARK_MAIN() \
int main(int argc, const char** argv) { \ int main(int argc, const char** argv) { \

View file

@ -254,16 +254,16 @@ namespace internal {
// Information kept per benchmark we may want to run // Information kept per benchmark we may want to run
struct Benchmark::Instance { struct Benchmark::Instance {
std::string name; std::string name;
Function* function; Benchmark* benchmark;
bool has_arg1; bool has_arg1;
int arg1; int arg1;
bool has_arg2; bool has_arg2;
int arg2; int arg2;
bool use_real_time; bool use_real_time;
double min_time; double min_time;
int threads; // Number of concurrent threads to use int threads; // Number of concurrent threads to use
bool multithreaded; // Is benchmark multi-threaded? bool multithreaded; // Is benchmark multi-threaded?
}; };
// Class for managing registered benchmarks. Note that each registered // Class for managing registered benchmarks. Note that each registered
@ -273,27 +273,23 @@ class BenchmarkFamilies {
static BenchmarkFamilies* GetInstance(); static BenchmarkFamilies* GetInstance();
// Registers a benchmark family and returns the index assigned to it. // Registers a benchmark family and returns the index assigned to it.
size_t AddBenchmark(BenchmarkImp* family); size_t AddBenchmark(std::unique_ptr<Benchmark> family);
// Unregisters a family at the given index.
void RemoveBenchmark(size_t index);
// Extract the list of benchmark instances that match the specified // Extract the list of benchmark instances that match the specified
// regular expression. // regular expression.
bool FindBenchmarks(const std::string& re, bool FindBenchmarks(const std::string& re,
std::vector<Benchmark::Instance>* benchmarks); std::vector<Benchmark::Instance>* benchmarks);
private: private:
BenchmarkFamilies(); BenchmarkFamilies() {}
~BenchmarkFamilies();
std::vector<BenchmarkImp*> families_; std::vector<std::unique_ptr<Benchmark>> families_;
Mutex mutex_; Mutex mutex_;
}; };
class BenchmarkImp { class BenchmarkImp {
public: public:
BenchmarkImp(const char* name, Function* func); explicit BenchmarkImp(const char* name);
~BenchmarkImp(); ~BenchmarkImp();
void Arg(int x); void Arg(int x);
@ -306,6 +302,7 @@ public:
void Threads(int t); void Threads(int t);
void ThreadRange(int min_threads, int max_threads); void ThreadRange(int min_threads, int max_threads);
void ThreadPerCpu(); void ThreadPerCpu();
void SetName(const char* name);
static void AddRange(std::vector<int>* dst, int lo, int hi, int mult); static void AddRange(std::vector<int>* dst, int lo, int hi, int mult);
@ -313,15 +310,13 @@ private:
friend class BenchmarkFamilies; friend class BenchmarkFamilies;
std::string name_; std::string name_;
Function* function_;
int arg_count_; int arg_count_;
std::vector< std::pair<int, int> > args_; // Args for all benchmark runs std::vector< std::pair<int, int> > args_; // Args for all benchmark runs
double min_time_; double min_time_;
bool use_real_time_; bool use_real_time_;
std::vector<int> thread_counts_; std::vector<int> thread_counts_;
std::size_t registration_index_;
BENCHMARK_DISALLOW_COPY_AND_ASSIGN(BenchmarkImp); BenchmarkImp& operator=(BenchmarkImp const&);
}; };
BenchmarkFamilies* BenchmarkFamilies::GetInstance() { BenchmarkFamilies* BenchmarkFamilies::GetInstance() {
@ -329,36 +324,14 @@ BenchmarkFamilies* BenchmarkFamilies::GetInstance() {
return &instance; return &instance;
} }
BenchmarkFamilies::BenchmarkFamilies() { }
BenchmarkFamilies::~BenchmarkFamilies() { size_t BenchmarkFamilies::AddBenchmark(std::unique_ptr<Benchmark> family) {
for (BenchmarkImp* family : families_) {
delete family;
}
}
size_t BenchmarkFamilies::AddBenchmark(BenchmarkImp* family) {
MutexLock l(mutex_); MutexLock l(mutex_);
// This loop attempts to reuse an entry that was previously removed to avoid
// unncessary growth of the vector.
for (size_t index = 0; index < families_.size(); ++index) {
if (families_[index] == nullptr) {
families_[index] = family;
return index;
}
}
size_t index = families_.size(); size_t index = families_.size();
families_.push_back(family); families_.push_back(std::move(family));
return index; return index;
} }
void BenchmarkFamilies::RemoveBenchmark(size_t index) {
MutexLock l(mutex_);
families_[index] = nullptr;
// Don't shrink families_ here, we might be called by the destructor of
// BenchmarkFamilies which iterates over the vector.
}
bool BenchmarkFamilies::FindBenchmarks( bool BenchmarkFamilies::FindBenchmarks(
const std::string& spec, const std::string& spec,
std::vector<Benchmark::Instance>* benchmarks) { std::vector<Benchmark::Instance>* benchmarks) {
@ -375,9 +348,10 @@ bool BenchmarkFamilies::FindBenchmarks(
one_thread.push_back(1); one_thread.push_back(1);
MutexLock l(mutex_); MutexLock l(mutex_);
for (BenchmarkImp* family : families_) { for (std::unique_ptr<Benchmark>& bench_family : families_) {
// Family was deleted or benchmark doesn't match // Family was deleted or benchmark doesn't match
if (family == nullptr) continue; if (!bench_family) continue;
BenchmarkImp* family = bench_family->imp_;
if (family->arg_count_ == -1) { if (family->arg_count_ == -1) {
family->arg_count_ = 0; family->arg_count_ = 0;
@ -392,7 +366,7 @@ bool BenchmarkFamilies::FindBenchmarks(
Benchmark::Instance instance; Benchmark::Instance instance;
instance.name = family->name_; instance.name = family->name_;
instance.function = family->function_; instance.benchmark = bench_family.get();
instance.has_arg1 = family->arg_count_ >= 1; instance.has_arg1 = family->arg_count_ >= 1;
instance.arg1 = args.first; instance.arg1 = args.first;
instance.has_arg2 = family->arg_count_ == 2; instance.has_arg2 = family->arg_count_ == 2;
@ -430,14 +404,12 @@ bool BenchmarkFamilies::FindBenchmarks(
return true; return true;
} }
BenchmarkImp::BenchmarkImp(const char* name, Function* func) BenchmarkImp::BenchmarkImp(const char* name)
: name_(name), function_(func), arg_count_(-1), : name_(name), arg_count_(-1),
min_time_(0.0), use_real_time_(false) { min_time_(0.0), use_real_time_(false) {
registration_index_ = BenchmarkFamilies::GetInstance()->AddBenchmark(this);
} }
BenchmarkImp::~BenchmarkImp() { BenchmarkImp::~BenchmarkImp() {
BenchmarkFamilies::GetInstance()->RemoveBenchmark(registration_index_);
} }
void BenchmarkImp::Arg(int x) { void BenchmarkImp::Arg(int x) {
@ -513,6 +485,10 @@ void BenchmarkImp::ThreadPerCpu() {
thread_counts_.push_back(num_cpus); thread_counts_.push_back(num_cpus);
} }
void BenchmarkImp::SetName(const char* name) {
name_ = name;
}
void BenchmarkImp::AddRange(std::vector<int>* dst, int lo, int hi, int mult) { void BenchmarkImp::AddRange(std::vector<int>* dst, int lo, int hi, int mult) {
CHECK_GE(lo, 0); CHECK_GE(lo, 0);
CHECK_GE(hi, lo); CHECK_GE(hi, lo);
@ -535,8 +511,8 @@ void BenchmarkImp::AddRange(std::vector<int>* dst, int lo, int hi, int mult) {
} }
} }
Benchmark::Benchmark(const char* name, Function* f) Benchmark::Benchmark(const char* name)
: imp_(new BenchmarkImp(name, f)) : imp_(new BenchmarkImp(name))
{ {
} }
@ -544,6 +520,11 @@ Benchmark::~Benchmark() {
delete imp_; delete imp_;
} }
Benchmark::Benchmark(Benchmark const& other)
: imp_(new BenchmarkImp(*other.imp_))
{
}
Benchmark* Benchmark::Arg(int x) { Benchmark* Benchmark::Arg(int x) {
imp_->Arg(x); imp_->Arg(x);
return this; return this;
@ -599,6 +580,14 @@ Benchmark* Benchmark::ThreadPerCpu() {
return this; return this;
} }
void Benchmark::SetName(const char* name) {
imp_->SetName(name);
}
void FunctionBenchmark::Run(State& st) {
func_(st);
}
} // end namespace internal } // end namespace internal
namespace { namespace {
@ -610,7 +599,7 @@ void RunInThread(const benchmark::internal::Benchmark::Instance* b,
int iters, int thread_id, int iters, int thread_id,
ThreadStats* total) EXCLUDES(GetBenchmarkLock()) { ThreadStats* total) EXCLUDES(GetBenchmarkLock()) {
State st(iters, b->has_arg1, b->arg1, b->has_arg2, b->arg2, thread_id); State st(iters, b->has_arg1, b->arg1, b->has_arg2, b->arg2, thread_id);
b->function(st); b->benchmark->Run(st);
CHECK(st.iterations() == st.max_iterations) << CHECK(st.iterations() == st.max_iterations) <<
"Benchmark returned before State::KeepRunning() returned false!"; "Benchmark returned before State::KeepRunning() returned false!";
{ {
@ -906,6 +895,13 @@ void ParseCommandLineFlags(int* argc, const char** argv) {
} }
} }
Benchmark* RegisterBenchmarkInternal(Benchmark* bench) {
std::unique_ptr<Benchmark> bench_ptr(bench);
BenchmarkFamilies* families = BenchmarkFamilies::GetInstance();
families->AddBenchmark(std::move(bench_ptr));
return bench;
}
} // end namespace internal } // end namespace internal
void Initialize(int* argc, const char** argv) { void Initialize(int* argc, const char** argv) {

View file

@ -36,6 +36,9 @@ add_test(options_benchmarks options_test --benchmark_min_time=0.01)
compile_benchmark_test(basic_test) compile_benchmark_test(basic_test)
add_test(basic_benchmark basic_test --benchmark_min_time=0.01) add_test(basic_benchmark basic_test --benchmark_min_time=0.01)
compile_benchmark_test(fixture_test)
add_test(fixture_test fixture_test --benchmark_min_time=0.01)
compile_benchmark_test(cxx03_test) compile_benchmark_test(cxx03_test)
set_target_properties(cxx03_test set_target_properties(cxx03_test
PROPERTIES COMPILE_FLAGS "${CXX03_FLAGS}") PROPERTIES COMPILE_FLAGS "${CXX03_FLAGS}")

42
test/fixture_test.cc Normal file
View file

@ -0,0 +1,42 @@
#include "benchmark/benchmark.h"
#include <cassert>
class MyFixture : public ::benchmark::Fixture
{
public:
void SetUp() {
data = new int(42);
}
void TearDown() {
assert(data != nullptr);
delete data;
data = nullptr;
}
~MyFixture() {
assert(data == nullptr);
}
int* data;
};
BENCHMARK_F(MyFixture, Foo)(benchmark::State& st) {
assert(data != nullptr);
assert(*data == 42);
while (st.KeepRunning()) {
}
}
BENCHMARK_DEFINE_F(MyFixture, Bar)(benchmark::State& st) {
while (st.KeepRunning()) {
}
st.SetItemsProcessed(st.range_x());
}
BENCHMARK_REGISTER_F(MyFixture, Bar)->Arg(42);
BENCHMARK_MAIN()