Commit b123abdc by Roman Lebedev Committed by Dominic Hamon

Add Iteration-related Counter::Flags. Fixes #618 (#621)

Inspired by these [two](https://github.com/darktable-org/rawspeed/commit/a1ebe07bea5738f8607b48a7596c172be249590e) [bugs](https://github.com/darktable-org/rawspeed/commit/0891555be56b24f9f4af716604cedfa0da1efc6b) in my code due to the lack of those i have found fixed in my code: * `kIsIterationInvariant` - `* state.iterations()` The value is constant for every iteration, and needs to be **multiplied** by the iteration count. * `kAvgIterations` - `/ state.iterations()` The is global over all the iterations, and needs to be **divided** by the iteration count. They play nice with `kIsRate`: * `kIsIterationInvariantRate` * `kAvgIterationsRate`. I'm not sure how meaningful they are when combined with `kAvgThreads`. I guess the `kIsThreadInvariant` can be added, too, for symmetry with `kAvgThreads`.
parent d8584bda
...@@ -48,6 +48,7 @@ bazel-* ...@@ -48,6 +48,7 @@ bazel-*
# out-of-source build top-level folders. # out-of-source build top-level folders.
build/ build/
_build/ _build/
build*/
# in-source dependencies # in-source dependencies
/googletest/ /googletest/
......
...@@ -343,12 +343,24 @@ class Counter { ...@@ -343,12 +343,24 @@ class Counter {
kDefaults = 0, kDefaults = 0,
// Mark the counter as a rate. It will be presented divided // Mark the counter as a rate. It will be presented divided
// by the duration of the benchmark. // by the duration of the benchmark.
kIsRate = 1, kIsRate = 1U << 0U,
// Mark the counter as a thread-average quantity. It will be // Mark the counter as a thread-average quantity. It will be
// presented divided by the number of threads. // presented divided by the number of threads.
kAvgThreads = 2, kAvgThreads = 1U << 1U,
// Mark the counter as a thread-average rate. See above. // Mark the counter as a thread-average rate. See above.
kAvgThreadsRate = kIsRate | kAvgThreads kAvgThreadsRate = kIsRate | kAvgThreads,
// Mark the counter as a constant value, valid/same for *every* iteration.
// When reporting, it will be *multiplied* by the iteration count.
kIsIterationInvariant = 1U << 2U,
// Mark the counter as a constant rate.
// When reporting, it will be *multiplied* by the iteration count
// and then divided by the duration of the benchmark.
kIsIterationInvariantRate = kIsRate | kIsIterationInvariant,
// Mark the counter as a iteration-average quantity.
// It will be presented divided by the number of iterations.
kAvgIterations = 1U << 3U,
// Mark the counter as a iteration-average rate. See above.
kAvgIterationsRate = kIsRate | kAvgIterations
}; };
double value; double value;
...@@ -361,6 +373,14 @@ class Counter { ...@@ -361,6 +373,14 @@ class Counter {
BENCHMARK_ALWAYS_INLINE operator double&() { return value; } BENCHMARK_ALWAYS_INLINE operator double&() { return value; }
}; };
// A helper for user code to create unforeseen combinations of Flags, without
// having to do this cast manually each time, or providing this operator.
Counter::Flags inline operator|(const Counter::Flags& LHS,
const Counter::Flags& RHS) {
return static_cast<Counter::Flags>(static_cast<int>(LHS) |
static_cast<int>(RHS));
}
// This is the container for the user-defined counters. // This is the container for the user-defined counters.
typedef std::map<std::string, Counter> UserCounters; typedef std::map<std::string, Counter> UserCounters;
......
...@@ -150,7 +150,7 @@ BenchmarkReporter::Run CreateRunReport( ...@@ -150,7 +150,7 @@ BenchmarkReporter::Run CreateRunReport(
report.complexity_lambda = b.complexity_lambda; report.complexity_lambda = b.complexity_lambda;
report.statistics = b.statistics; report.statistics = b.statistics;
report.counters = results.counters; report.counters = results.counters;
internal::Finish(&report.counters, seconds, b.threads); internal::Finish(&report.counters, results.iterations, seconds, b.threads);
} }
return report; return report;
} }
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
namespace benchmark { namespace benchmark {
namespace internal { namespace internal {
double Finish(Counter const& c, double cpu_time, double num_threads) { double Finish(Counter const& c, int64_t iterations, double cpu_time,
double num_threads) {
double v = c.value; double v = c.value;
if (c.flags & Counter::kIsRate) { if (c.flags & Counter::kIsRate) {
v /= cpu_time; v /= cpu_time;
...@@ -25,12 +26,18 @@ double Finish(Counter const& c, double cpu_time, double num_threads) { ...@@ -25,12 +26,18 @@ double Finish(Counter const& c, double cpu_time, double num_threads) {
if (c.flags & Counter::kAvgThreads) { if (c.flags & Counter::kAvgThreads) {
v /= num_threads; v /= num_threads;
} }
if (c.flags & Counter::kIsIterationInvariant) {
v *= iterations;
}
if (c.flags & Counter::kAvgIterations) {
v /= iterations;
}
return v; return v;
} }
void Finish(UserCounters* l, double cpu_time, double num_threads) { void Finish(UserCounters* l, int64_t iterations, double cpu_time, double num_threads) {
for (auto& c : *l) { for (auto& c : *l) {
c.second.value = Finish(c.second, cpu_time, num_threads); c.second.value = Finish(c.second, iterations, cpu_time, num_threads);
} }
} }
......
...@@ -18,7 +18,7 @@ namespace benchmark { ...@@ -18,7 +18,7 @@ namespace benchmark {
// these counter-related functions are hidden to reduce API surface. // these counter-related functions are hidden to reduce API surface.
namespace internal { namespace internal {
void Finish(UserCounters* l, double time, double num_threads); void Finish(UserCounters* l, int64_t iterations, double time, double num_threads);
void Increment(UserCounters* l, UserCounters const& r); void Increment(UserCounters* l, UserCounters const& r);
bool SameNames(UserCounters const& l, UserCounters const& r); bool SameNames(UserCounters const& l, UserCounters const& r);
} // end namespace internal } // end namespace internal
......
...@@ -92,6 +92,8 @@ struct Results { ...@@ -92,6 +92,8 @@ struct Results {
int NumThreads() const; int NumThreads() const;
double NumIterations() const;
typedef enum { kCpuTime, kRealTime } BenchmarkTime; typedef enum { kCpuTime, kRealTime } BenchmarkTime;
// get cpu_time or real_time in seconds // get cpu_time or real_time in seconds
...@@ -101,11 +103,11 @@ struct Results { ...@@ -101,11 +103,11 @@ struct Results {
// it is better to use fuzzy float checks for this, as the float // it is better to use fuzzy float checks for this, as the float
// ASCII formatting is lossy. // ASCII formatting is lossy.
double DurationRealTime() const { double DurationRealTime() const {
return GetAs<double>("iterations") * GetTime(kRealTime); return NumIterations() * GetTime(kRealTime);
} }
// get the cpu_time duration of the benchmark in seconds // get the cpu_time duration of the benchmark in seconds
double DurationCPUTime() const { double DurationCPUTime() const {
return GetAs<double>("iterations") * GetTime(kCpuTime); return NumIterations() * GetTime(kCpuTime);
} }
// get the string for a result by name, or nullptr if the name // get the string for a result by name, or nullptr if the name
......
...@@ -301,6 +301,10 @@ int Results::NumThreads() const { ...@@ -301,6 +301,10 @@ int Results::NumThreads() const {
return num; return num;
} }
double Results::NumIterations() const {
return GetAs<double>("iterations");
}
double Results::GetTime(BenchmarkTime which) const { double Results::GetTime(BenchmarkTime which) const {
CHECK(which == kCpuTime || which == kRealTime); CHECK(which == kCpuTime || which == kRealTime);
const char* which_str = which == kCpuTime ? "cpu_time" : "real_time"; const char* which_str = which == kCpuTime ? "cpu_time" : "real_time";
......
...@@ -43,7 +43,7 @@ ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Simple\",%csv_report,%float,%float$"}}); ...@@ -43,7 +43,7 @@ ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_Simple\",%csv_report,%float,%float$"}});
// VS2013 does not allow this function to be passed as a lambda argument // VS2013 does not allow this function to be passed as a lambda argument
// to CHECK_BENCHMARK_RESULTS() // to CHECK_BENCHMARK_RESULTS()
void CheckSimple(Results const& e) { void CheckSimple(Results const& e) {
double its = e.GetAs<double>("iterations"); double its = e.NumIterations();
CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1); CHECK_COUNTER_VALUE(e, int, "foo", EQ, 1);
// check that the value of bar is within 0.1% of the expected value // check that the value of bar is within 0.1% of the expected value
CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001); CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001);
...@@ -229,6 +229,151 @@ CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreadsRate/threads:%int", ...@@ -229,6 +229,151 @@ CHECK_BENCHMARK_RESULTS("BM_Counters_AvgThreadsRate/threads:%int",
&CheckAvgThreadsRate); &CheckAvgThreadsRate);
// ========================================================================= // // ========================================================================= //
// ------------------- IterationInvariant Counters Output ------------------ //
// ========================================================================= //
void BM_Counters_IterationInvariant(benchmark::State& state) {
for (auto _ : state) {
}
namespace bm = benchmark;
state.counters["foo"] = bm::Counter{1, bm::Counter::kIsIterationInvariant};
state.counters["bar"] = bm::Counter{2, bm::Counter::kIsIterationInvariant};
}
BENCHMARK(BM_Counters_IterationInvariant);
ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_IterationInvariant %console_report "
"bar=%hrfloat foo=%hrfloat$"}});
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_IterationInvariant\",$"},
{"\"iterations\": %int,$", MR_Next},
{"\"real_time\": %float,$", MR_Next},
{"\"cpu_time\": %float,$", MR_Next},
{"\"time_unit\": \"ns\",$", MR_Next},
{"\"bar\": %float,$", MR_Next},
{"\"foo\": %float$", MR_Next},
{"}", MR_Next}});
ADD_CASES(TC_CSVOut,
{{"^\"BM_Counters_IterationInvariant\",%csv_report,%float,%float$"}});
// VS2013 does not allow this function to be passed as a lambda argument
// to CHECK_BENCHMARK_RESULTS()
void CheckIterationInvariant(Results const& e) {
double its = e.NumIterations();
// check that the values are within 0.1% of the expected value
CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its, 0.001);
CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. * its, 0.001);
}
CHECK_BENCHMARK_RESULTS("BM_Counters_IterationInvariant",
&CheckIterationInvariant);
// ========================================================================= //
// ----------------- IterationInvariantRate Counters Output ---------------- //
// ========================================================================= //
void BM_Counters_kIsIterationInvariantRate(benchmark::State& state) {
for (auto _ : state) {
}
namespace bm = benchmark;
state.counters["foo"] =
bm::Counter{1, bm::Counter::kIsIterationInvariantRate};
state.counters["bar"] =
bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kIsIterationInvariant};
}
BENCHMARK(BM_Counters_kIsIterationInvariantRate);
ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kIsIterationInvariantRate "
"%console_report bar=%hrfloat/s foo=%hrfloat/s$"}});
ADD_CASES(TC_JSONOut,
{{"\"name\": \"BM_Counters_kIsIterationInvariantRate\",$"},
{"\"iterations\": %int,$", MR_Next},
{"\"real_time\": %float,$", MR_Next},
{"\"cpu_time\": %float,$", MR_Next},
{"\"time_unit\": \"ns\",$", MR_Next},
{"\"bar\": %float,$", MR_Next},
{"\"foo\": %float$", MR_Next},
{"}", MR_Next}});
ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kIsIterationInvariantRate\",%csv_report,"
"%float,%float$"}});
// VS2013 does not allow this function to be passed as a lambda argument
// to CHECK_BENCHMARK_RESULTS()
void CheckIsIterationInvariantRate(Results const& e) {
double its = e.NumIterations();
double t = e.DurationCPUTime(); // this (and not real time) is the time used
// check that the values are within 0.1% of the expected values
CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, its * 1. / t, 0.001);
CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, its * 2. / t, 0.001);
}
CHECK_BENCHMARK_RESULTS("BM_Counters_kIsIterationInvariantRate",
&CheckIsIterationInvariantRate);
// ========================================================================= //
// ------------------- AvgIterations Counters Output ------------------ //
// ========================================================================= //
void BM_Counters_AvgIterations(benchmark::State& state) {
for (auto _ : state) {
}
namespace bm = benchmark;
state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterations};
state.counters["bar"] = bm::Counter{2, bm::Counter::kAvgIterations};
}
BENCHMARK(BM_Counters_AvgIterations);
ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_AvgIterations %console_report "
"bar=%hrfloat foo=%hrfloat$"}});
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_AvgIterations\",$"},
{"\"iterations\": %int,$", MR_Next},
{"\"real_time\": %float,$", MR_Next},
{"\"cpu_time\": %float,$", MR_Next},
{"\"time_unit\": \"ns\",$", MR_Next},
{"\"bar\": %float,$", MR_Next},
{"\"foo\": %float$", MR_Next},
{"}", MR_Next}});
ADD_CASES(TC_CSVOut,
{{"^\"BM_Counters_AvgIterations\",%csv_report,%float,%float$"}});
// VS2013 does not allow this function to be passed as a lambda argument
// to CHECK_BENCHMARK_RESULTS()
void CheckAvgIterations(Results const& e) {
double its = e.NumIterations();
// check that the values are within 0.1% of the expected value
CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its, 0.001);
CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its, 0.001);
}
CHECK_BENCHMARK_RESULTS("BM_Counters_AvgIterations", &CheckAvgIterations);
// ========================================================================= //
// ----------------- AvgIterationsRate Counters Output ---------------- //
// ========================================================================= //
void BM_Counters_kAvgIterationsRate(benchmark::State& state) {
for (auto _ : state) {
}
namespace bm = benchmark;
state.counters["foo"] = bm::Counter{1, bm::Counter::kAvgIterationsRate};
state.counters["bar"] =
bm::Counter{2, bm::Counter::kIsRate | bm::Counter::kAvgIterations};
}
BENCHMARK(BM_Counters_kAvgIterationsRate);
ADD_CASES(TC_ConsoleOut, {{"^BM_Counters_kAvgIterationsRate "
"%console_report bar=%hrfloat/s foo=%hrfloat/s$"}});
ADD_CASES(TC_JSONOut, {{"\"name\": \"BM_Counters_kAvgIterationsRate\",$"},
{"\"iterations\": %int,$", MR_Next},
{"\"real_time\": %float,$", MR_Next},
{"\"cpu_time\": %float,$", MR_Next},
{"\"time_unit\": \"ns\",$", MR_Next},
{"\"bar\": %float,$", MR_Next},
{"\"foo\": %float$", MR_Next},
{"}", MR_Next}});
ADD_CASES(TC_CSVOut, {{"^\"BM_Counters_kAvgIterationsRate\",%csv_report,"
"%float,%float$"}});
// VS2013 does not allow this function to be passed as a lambda argument
// to CHECK_BENCHMARK_RESULTS()
void CheckAvgIterationsRate(Results const& e) {
double its = e.NumIterations();
double t = e.DurationCPUTime(); // this (and not real time) is the time used
// check that the values are within 0.1% of the expected values
CHECK_FLOAT_COUNTER_VALUE(e, "foo", EQ, 1. / its / t, 0.001);
CHECK_FLOAT_COUNTER_VALUE(e, "bar", EQ, 2. / its / t, 0.001);
}
CHECK_BENCHMARK_RESULTS("BM_Counters_kAvgIterationsRate",
&CheckAvgIterationsRate);
// ========================================================================= //
// --------------------------- TEST CASES END ------------------------------ // // --------------------------- TEST CASES END ------------------------------ //
// ========================================================================= // // ========================================================================= //
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment