LIBS: updated googlebenchmark
parent
e9a39be4a7
commit
bd8300b11c
|
@ -3,6 +3,10 @@ set(SRCS
|
|||
src/benchmark_api_internal.h
|
||||
src/benchmark.cc
|
||||
src/benchmark_register.cc
|
||||
src/benchmark_api_internal.cc
|
||||
src/benchmark_name.cc
|
||||
src/benchmark_runner.h
|
||||
src/benchmark_runner.cc
|
||||
src/check.h
|
||||
src/colorprint.cc
|
||||
src/colorprint.h
|
||||
|
@ -34,6 +38,7 @@ set(SRCS
|
|||
src/timers.h
|
||||
)
|
||||
|
||||
|
||||
set(LIB benchmark)
|
||||
engine_add_library(
|
||||
LIB ${LIB}
|
||||
|
|
|
@ -56,8 +56,7 @@ static void BM_memcpy(benchmark::State& state) {
|
|||
memset(src, 'x', state.range(0));
|
||||
for (auto _ : state)
|
||||
memcpy(dst, src, state.range(0));
|
||||
state.SetBytesProcessed(int64_t(state.iterations()) *
|
||||
int64_t(state.range(0)));
|
||||
state.SetBytesProcessed(state.iterations() * state.range(0));
|
||||
delete[] src; delete[] dst;
|
||||
}
|
||||
BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(1<<10)->Arg(8<<10);
|
||||
|
@ -122,8 +121,7 @@ template <class Q> int BM_Sequential(benchmark::State& state) {
|
|||
q.Wait(&v);
|
||||
}
|
||||
// actually messages, not bytes:
|
||||
state.SetBytesProcessed(
|
||||
static_cast<int64_t>(state.iterations())*state.range(0));
|
||||
state.SetBytesProcessed(state.iterations() * state.range(0));
|
||||
}
|
||||
BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue<int>)->Range(1<<0, 1<<10);
|
||||
|
||||
|
@ -241,8 +239,21 @@ BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);
|
|||
#define BENCHMARK_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || __has_builtin(__builtin_unreachable)
|
||||
#define BENCHMARK_UNREACHABLE() __builtin_unreachable()
|
||||
#elif defined(_MSC_VER)
|
||||
#define BENCHMARK_UNREACHABLE() __assume(false)
|
||||
#else
|
||||
#define BENCHMARK_UNREACHABLE() ((void)0)
|
||||
#endif
|
||||
|
||||
namespace benchmark {
|
||||
class BenchmarkReporter;
|
||||
class MemoryManager;
|
||||
|
||||
void Initialize(int* argc, char** argv);
|
||||
|
||||
|
@ -255,7 +266,7 @@ bool ReportUnrecognizedArguments(int argc, char** argv);
|
|||
// of each matching benchmark. Otherwise run each matching benchmark and
|
||||
// report the results.
|
||||
//
|
||||
// The second and third overload use the specified 'console_reporter' and
|
||||
// The second and third overload use the specified 'display_reporter' and
|
||||
// 'file_reporter' respectively. 'file_reporter' will write to the file
|
||||
// specified
|
||||
// by '--benchmark_output'. If '--benchmark_output' is not given the
|
||||
|
@ -263,16 +274,13 @@ bool ReportUnrecognizedArguments(int argc, char** argv);
|
|||
//
|
||||
// RETURNS: The number of matching benchmarks.
|
||||
size_t RunSpecifiedBenchmarks();
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter);
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter);
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter,
|
||||
BenchmarkReporter* file_reporter);
|
||||
|
||||
// If this routine is called, peak memory allocation past this point in the
|
||||
// benchmark is reported at the end of the benchmark report line. (It is
|
||||
// computed by running the benchmark once with a single iteration and a memory
|
||||
// tracer.)
|
||||
// TODO(dominic)
|
||||
// void MemoryUsage();
|
||||
// Register a MemoryManager instance that will be used to collect and report
|
||||
// allocation measurements for benchmark runs.
|
||||
void RegisterMemoryManager(MemoryManager* memory_manager);
|
||||
|
||||
namespace internal {
|
||||
class Benchmark;
|
||||
|
@ -343,24 +351,56 @@ class Counter {
|
|||
kDefaults = 0,
|
||||
// Mark the counter as a rate. It will be presented divided
|
||||
// by the duration of the benchmark.
|
||||
kIsRate = 1,
|
||||
kIsRate = 1U << 0U,
|
||||
// Mark the counter as a thread-average quantity. It will be
|
||||
// presented divided by the number of threads.
|
||||
kAvgThreads = 2,
|
||||
kAvgThreads = 1U << 1U,
|
||||
// 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,
|
||||
|
||||
// In the end, invert the result. This is always done last!
|
||||
kInvert = 1U << 31U
|
||||
};
|
||||
|
||||
enum OneK {
|
||||
// 1'000 items per 1k
|
||||
kIs1000 = 1000,
|
||||
// 1'024 items per 1k
|
||||
kIs1024 = 1024
|
||||
};
|
||||
|
||||
double value;
|
||||
Flags flags;
|
||||
OneK oneK;
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
Counter(double v = 0., Flags f = kDefaults) : value(v), flags(f) {}
|
||||
Counter(double v = 0., Flags f = kDefaults, OneK k = kIs1000)
|
||||
: value(v), flags(f), oneK(k) {}
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE operator double const&() const { 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.
|
||||
typedef std::map<std::string, Counter> UserCounters;
|
||||
|
||||
|
@ -374,34 +414,49 @@ enum TimeUnit { kNanosecond, kMicrosecond, kMillisecond };
|
|||
// calculated automatically to the best fit.
|
||||
enum BigO { oNone, o1, oN, oNSquared, oNCubed, oLogN, oNLogN, oAuto, oLambda };
|
||||
|
||||
typedef uint64_t IterationCount;
|
||||
|
||||
// BigOFunc is passed to a benchmark in order to specify the asymptotic
|
||||
// computational complexity for the benchmark.
|
||||
typedef double(BigOFunc)(int64_t);
|
||||
typedef double(BigOFunc)(IterationCount);
|
||||
|
||||
// StatisticsFunc is passed to a benchmark in order to compute some descriptive
|
||||
// statistics over all the measurements of some type
|
||||
typedef double(StatisticsFunc)(const std::vector<double>&);
|
||||
|
||||
namespace internal {
|
||||
struct Statistics {
|
||||
std::string name_;
|
||||
StatisticsFunc* compute_;
|
||||
|
||||
Statistics(std::string name, StatisticsFunc* compute)
|
||||
Statistics(const std::string& name, StatisticsFunc* compute)
|
||||
: name_(name), compute_(compute) {}
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
struct BenchmarkInstance;
|
||||
class ThreadTimer;
|
||||
class ThreadManager;
|
||||
|
||||
enum ReportMode
|
||||
enum AggregationReportMode
|
||||
#if defined(BENCHMARK_HAS_CXX11)
|
||||
: unsigned
|
||||
#else
|
||||
#endif
|
||||
{ RM_Unspecified, // The mode has not been manually specified
|
||||
RM_Default, // The mode is user-specified as default.
|
||||
RM_ReportAggregatesOnly };
|
||||
{
|
||||
// The mode has not been manually specified
|
||||
ARM_Unspecified = 0,
|
||||
// The mode is user-specified.
|
||||
// This may or may not be set when the following bit-flags are set.
|
||||
ARM_Default = 1U << 0U,
|
||||
// File reporter should only output aggregates.
|
||||
ARM_FileReportAggregatesOnly = 1U << 1U,
|
||||
// Display reporter should only output aggregates
|
||||
ARM_DisplayReportAggregatesOnly = 1U << 2U,
|
||||
// Both reporters should only display aggregates.
|
||||
ARM_ReportAggregatesOnly =
|
||||
ARM_FileReportAggregatesOnly | ARM_DisplayReportAggregatesOnly
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// State is passed to a running Benchmark and contains state for the
|
||||
|
@ -436,7 +491,7 @@ class State {
|
|||
// while (state.KeepRunningBatch(1000)) {
|
||||
// // process 1000 elements
|
||||
// }
|
||||
bool KeepRunningBatch(size_t n);
|
||||
bool KeepRunningBatch(IterationCount n);
|
||||
|
||||
// REQUIRES: timer is running and 'SkipWithError(...)' has not been called
|
||||
// by the current thread.
|
||||
|
@ -486,6 +541,9 @@ class State {
|
|||
// responsibility to exit the scope as needed.
|
||||
void SkipWithError(const char* msg);
|
||||
|
||||
// Returns true if an error has been reported with 'SkipWithError(...)'.
|
||||
bool error_occurred() const { return error_occurred_; }
|
||||
|
||||
// REQUIRES: called exactly once per iteration of the benchmarking loop.
|
||||
// Set the manually measured time for this benchmark iteration, which
|
||||
// is used instead of automatically measured time if UseManualTime() was
|
||||
|
@ -497,16 +555,21 @@ class State {
|
|||
|
||||
// Set the number of bytes processed by the current benchmark
|
||||
// execution. This routine is typically called once at the end of a
|
||||
// throughput oriented benchmark. If this routine is called with a
|
||||
// value > 0, the report is printed in MB/sec instead of nanoseconds
|
||||
// per iteration.
|
||||
// throughput oriented benchmark.
|
||||
//
|
||||
// REQUIRES: a benchmark has exited its benchmarking loop.
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
void SetBytesProcessed(int64_t bytes) { bytes_processed_ = bytes; }
|
||||
void SetBytesProcessed(int64_t bytes) {
|
||||
counters["bytes_per_second"] =
|
||||
Counter(static_cast<double>(bytes), Counter::kIsRate, Counter::kIs1024);
|
||||
}
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
int64_t bytes_processed() const { return bytes_processed_; }
|
||||
int64_t bytes_processed() const {
|
||||
if (counters.find("bytes_per_second") != counters.end())
|
||||
return static_cast<int64_t>(counters.at("bytes_per_second"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If this routine is called with complexity_n > 0 and complexity report is
|
||||
// requested for the
|
||||
|
@ -517,7 +580,7 @@ class State {
|
|||
void SetComplexityN(int64_t complexity_n) { complexity_n_ = complexity_n; }
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
int64_t complexity_length_n() { return complexity_n_; }
|
||||
int64_t complexity_length_n() const { return complexity_n_; }
|
||||
|
||||
// If this routine is called with items > 0, then an items/s
|
||||
// label is printed on the benchmark report line for the currently
|
||||
|
@ -526,10 +589,17 @@ class State {
|
|||
//
|
||||
// REQUIRES: a benchmark has exited its benchmarking loop.
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
void SetItemsProcessed(int64_t items) { items_processed_ = items; }
|
||||
void SetItemsProcessed(int64_t items) {
|
||||
counters["items_per_second"] =
|
||||
Counter(static_cast<double>(items), benchmark::Counter::kIsRate);
|
||||
}
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
int64_t items_processed() const { return items_processed_; }
|
||||
int64_t items_processed() const {
|
||||
if (counters.find("items_per_second") != counters.end())
|
||||
return static_cast<int64_t>(counters.at("items_per_second"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If this routine is called, the specified label is printed at the
|
||||
// end of the benchmark report line for the currently executing
|
||||
|
@ -563,7 +633,7 @@ class State {
|
|||
int64_t range_y() const { return range(1); }
|
||||
|
||||
BENCHMARK_ALWAYS_INLINE
|
||||
size_t iterations() const {
|
||||
IterationCount iterations() const {
|
||||
if (BENCHMARK_BUILTIN_EXPECT(!started_, false)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -574,15 +644,15 @@ class State {
|
|||
: // items we expect on the first cache line (ie 64 bytes of the struct)
|
||||
// When total_iterations_ is 0, KeepRunning() and friends will return false.
|
||||
// May be larger than max_iterations.
|
||||
size_t total_iterations_;
|
||||
IterationCount total_iterations_;
|
||||
|
||||
// When using KeepRunningBatch(), batch_leftover_ holds the number of
|
||||
// iterations beyond max_iters that were run. Used to track
|
||||
// completed_iterations_ accurately.
|
||||
size_t batch_leftover_;
|
||||
IterationCount batch_leftover_;
|
||||
|
||||
public:
|
||||
const size_t max_iterations;
|
||||
const IterationCount max_iterations;
|
||||
|
||||
private:
|
||||
bool started_;
|
||||
|
@ -592,9 +662,6 @@ class State {
|
|||
private: // items we don't need on the first cache line
|
||||
std::vector<int64_t> range_;
|
||||
|
||||
int64_t bytes_processed_;
|
||||
int64_t items_processed_;
|
||||
|
||||
int64_t complexity_n_;
|
||||
|
||||
public:
|
||||
|
@ -605,31 +672,31 @@ class State {
|
|||
// Number of threads concurrently executing the benchmark.
|
||||
const int threads;
|
||||
|
||||
// TODO(EricWF) make me private
|
||||
State(size_t max_iters, const std::vector<int64_t>& ranges, int thread_i,
|
||||
int n_threads, internal::ThreadTimer* timer,
|
||||
private:
|
||||
State(IterationCount max_iters, const std::vector<int64_t>& ranges,
|
||||
int thread_i, int n_threads, internal::ThreadTimer* timer,
|
||||
internal::ThreadManager* manager);
|
||||
|
||||
private:
|
||||
void StartKeepRunning();
|
||||
// Implementation of KeepRunning() and KeepRunningBatch().
|
||||
// is_batch must be true unless n is 1.
|
||||
bool KeepRunningInternal(size_t n, bool is_batch);
|
||||
bool KeepRunningInternal(IterationCount n, bool is_batch);
|
||||
void FinishKeepRunning();
|
||||
internal::ThreadTimer* timer_;
|
||||
internal::ThreadManager* manager_;
|
||||
BENCHMARK_DISALLOW_COPY_AND_ASSIGN(State);
|
||||
|
||||
friend struct internal::BenchmarkInstance;
|
||||
};
|
||||
|
||||
inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunning() {
|
||||
return KeepRunningInternal(1, /*is_batch=*/false);
|
||||
}
|
||||
|
||||
inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningBatch(size_t n) {
|
||||
inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningBatch(IterationCount n) {
|
||||
return KeepRunningInternal(n, /*is_batch=*/true);
|
||||
}
|
||||
|
||||
inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningInternal(size_t n,
|
||||
inline BENCHMARK_ALWAYS_INLINE bool State::KeepRunningInternal(IterationCount n,
|
||||
bool is_batch) {
|
||||
// total_iterations_ is set to 0 by the constructor, and always set to a
|
||||
// nonzero value by StartKepRunning().
|
||||
|
@ -693,7 +760,7 @@ struct State::StateIterator {
|
|||
}
|
||||
|
||||
private:
|
||||
size_t cached_;
|
||||
IterationCount cached_;
|
||||
State* const parent_;
|
||||
};
|
||||
|
||||
|
@ -797,7 +864,7 @@ class Benchmark {
|
|||
// NOTE: This function should only be used when *exact* iteration control is
|
||||
// needed and never to control or limit how long a benchmark runs, where
|
||||
// `--benchmark_min_time=N` or `MinTime(...)` should be used instead.
|
||||
Benchmark* Iterations(size_t n);
|
||||
Benchmark* Iterations(IterationCount n);
|
||||
|
||||
// Specify the amount of times to repeat this benchmark. This option overrides
|
||||
// the `benchmark_repetitions` flag.
|
||||
|
@ -807,13 +874,24 @@ class Benchmark {
|
|||
// Specify if each repetition of the benchmark should be reported separately
|
||||
// or if only the final statistics should be reported. If the benchmark
|
||||
// is not repeated then the single result is always reported.
|
||||
// Applies to *ALL* reporters (display and file).
|
||||
Benchmark* ReportAggregatesOnly(bool value = true);
|
||||
|
||||
// If a particular benchmark is I/O bound, runs multiple threads internally or
|
||||
// if for some reason CPU timings are not representative, call this method. If
|
||||
// called, the elapsed time will be used to control how many iterations are
|
||||
// run, and in the printing of items/second or MB/seconds values. If not
|
||||
// called, the cpu time used by the benchmark will be used.
|
||||
// Same as ReportAggregatesOnly(), but applies to display reporter only.
|
||||
Benchmark* DisplayAggregatesOnly(bool value = true);
|
||||
|
||||
// By default, the CPU time is measured only for the main thread, which may
|
||||
// be unrepresentative if the benchmark uses threads internally. If called,
|
||||
// the total CPU time spent by all the threads will be measured instead.
|
||||
// By default, the only the main thread CPU time will be measured.
|
||||
Benchmark* MeasureProcessCPUTime();
|
||||
|
||||
// If a particular benchmark should use the Wall clock instead of the CPU time
|
||||
// (be it either the CPU time of the main thread only (default), or the
|
||||
// total CPU usage of the benchmark), call this method. If called, the elapsed
|
||||
// (wall) time will be used to control how many iterations are run, and in the
|
||||
// printing of items/second or MB/seconds values.
|
||||
// If not called, the CPU time used by the benchmark will be used.
|
||||
Benchmark* UseRealTime();
|
||||
|
||||
// If a benchmark must measure time manually (e.g. if GPU execution time is
|
||||
|
@ -868,9 +946,6 @@ class Benchmark {
|
|||
|
||||
virtual void Run(State& state) = 0;
|
||||
|
||||
// Used inside the benchmark implementation
|
||||
struct Instance;
|
||||
|
||||
protected:
|
||||
explicit Benchmark(const char* name);
|
||||
Benchmark(Benchmark const&);
|
||||
|
@ -882,14 +957,15 @@ class Benchmark {
|
|||
friend class BenchmarkFamilies;
|
||||
|
||||
std::string name_;
|
||||
ReportMode report_mode_;
|
||||
AggregationReportMode aggregation_report_mode_;
|
||||
std::vector<std::string> arg_names_; // Args for all benchmark runs
|
||||
std::vector<std::vector<int64_t> > args_; // Args for all benchmark runs
|
||||
TimeUnit time_unit_;
|
||||
int range_multiplier_;
|
||||
double min_time_;
|
||||
size_t iterations_;
|
||||
IterationCount iterations_;
|
||||
int repetitions_;
|
||||
bool measure_process_cpu_time_;
|
||||
bool use_real_time_;
|
||||
bool use_manual_time_;
|
||||
BigO complexity_;
|
||||
|
@ -1222,6 +1298,7 @@ struct CPUInfo {
|
|||
double cycles_per_second;
|
||||
std::vector<CacheInfo> caches;
|
||||
bool scaling_enabled;
|
||||
std::vector<double> load_avg;
|
||||
|
||||
static const CPUInfo& Get();
|
||||
|
||||
|
@ -1230,6 +1307,33 @@ struct CPUInfo {
|
|||
BENCHMARK_DISALLOW_COPY_AND_ASSIGN(CPUInfo);
|
||||
};
|
||||
|
||||
// Adding Struct for System Information
|
||||
struct SystemInfo {
|
||||
std::string name;
|
||||
static const SystemInfo& Get();
|
||||
|
||||
private:
|
||||
SystemInfo();
|
||||
BENCHMARK_DISALLOW_COPY_AND_ASSIGN(SystemInfo);
|
||||
};
|
||||
|
||||
// BenchmarkName contains the components of the Benchmark's name
|
||||
// which allows individual fields to be modified or cleared before
|
||||
// building the final name using 'str()'.
|
||||
struct BenchmarkName {
|
||||
std::string function_name;
|
||||
std::string args;
|
||||
std::string min_time;
|
||||
std::string iterations;
|
||||
std::string repetitions;
|
||||
std::string time_type;
|
||||
std::string threads;
|
||||
|
||||
// Return the full name of the benchmark with each non-empty
|
||||
// field separated by a '/'
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
// Interface for custom benchmark result printers.
|
||||
// By default, benchmark reports are printed to stdout. However an application
|
||||
// can control the destination of the reports by calling
|
||||
|
@ -1239,6 +1343,7 @@ class BenchmarkReporter {
|
|||
public:
|
||||
struct Context {
|
||||
CPUInfo const& cpu_info;
|
||||
SystemInfo const& sys_info;
|
||||
// The number of chars in the longest benchmark name.
|
||||
size_t name_field_width;
|
||||
static const char* executable_name;
|
||||
|
@ -1246,28 +1351,40 @@ class BenchmarkReporter {
|
|||
};
|
||||
|
||||
struct Run {
|
||||
static const int64_t no_repetition_index = -1;
|
||||
enum RunType { RT_Iteration, RT_Aggregate };
|
||||
|
||||
Run()
|
||||
: error_occurred(false),
|
||||
: run_type(RT_Iteration),
|
||||
error_occurred(false),
|
||||
iterations(1),
|
||||
threads(1),
|
||||
time_unit(kNanosecond),
|
||||
real_accumulated_time(0),
|
||||
cpu_accumulated_time(0),
|
||||
bytes_per_second(0),
|
||||
items_per_second(0),
|
||||
max_heapbytes_used(0),
|
||||
complexity(oNone),
|
||||
complexity_lambda(),
|
||||
complexity_n(0),
|
||||
report_big_o(false),
|
||||
report_rms(false),
|
||||
counters() {}
|
||||
counters(),
|
||||
has_memory_result(false),
|
||||
allocs_per_iter(0.0),
|
||||
max_bytes_used(0) {}
|
||||
|
||||
std::string benchmark_name;
|
||||
std::string benchmark_name() const;
|
||||
BenchmarkName run_name;
|
||||
RunType run_type;
|
||||
std::string aggregate_name;
|
||||
std::string report_label; // Empty if not set by benchmark.
|
||||
bool error_occurred;
|
||||
std::string error_message;
|
||||
|
||||
int64_t iterations;
|
||||
IterationCount iterations;
|
||||
int64_t threads;
|
||||
int64_t repetition_index;
|
||||
int64_t repetitions;
|
||||
TimeUnit time_unit;
|
||||
double real_accumulated_time;
|
||||
double cpu_accumulated_time;
|
||||
|
@ -1284,10 +1401,6 @@ class BenchmarkReporter {
|
|||
// accumulated time.
|
||||
double GetAdjustedCPUTime() const;
|
||||
|
||||
// Zero if not set by benchmark.
|
||||
double bytes_per_second;
|
||||
double items_per_second;
|
||||
|
||||
// This is set to 0.0 if memory tracing is not enabled.
|
||||
double max_heapbytes_used;
|
||||
|
||||
|
@ -1297,13 +1410,18 @@ class BenchmarkReporter {
|
|||
int64_t complexity_n;
|
||||
|
||||
// what statistics to compute from the measurements
|
||||
const std::vector<Statistics>* statistics;
|
||||
const std::vector<internal::Statistics>* statistics;
|
||||
|
||||
// Inform print function whether the current run is a complexity report
|
||||
bool report_big_o;
|
||||
bool report_rms;
|
||||
|
||||
UserCounters counters;
|
||||
|
||||
// Memory metrics.
|
||||
bool has_memory_result;
|
||||
double allocs_per_iter;
|
||||
int64_t max_bytes_used;
|
||||
};
|
||||
|
||||
// Construct a BenchmarkReporter with the output stream set to 'std::cout'
|
||||
|
@ -1404,7 +1522,9 @@ class JSONReporter : public BenchmarkReporter {
|
|||
bool first_report_;
|
||||
};
|
||||
|
||||
class CSVReporter : public BenchmarkReporter {
|
||||
class BENCHMARK_DEPRECATED_MSG(
|
||||
"The CSV Reporter will be removed in a future release") CSVReporter
|
||||
: public BenchmarkReporter {
|
||||
public:
|
||||
CSVReporter() : printed_header_(false) {}
|
||||
virtual bool ReportContext(const Context& context);
|
||||
|
@ -1417,6 +1537,29 @@ class CSVReporter : public BenchmarkReporter {
|
|||
std::set<std::string> user_counter_names_;
|
||||
};
|
||||
|
||||
// If a MemoryManager is registered, it can be used to collect and report
|
||||
// allocation metrics for a run of the benchmark.
|
||||
class MemoryManager {
|
||||
public:
|
||||
struct Result {
|
||||
Result() : num_allocs(0), max_bytes_used(0) {}
|
||||
|
||||
// The number of allocations made in total between Start and Stop.
|
||||
int64_t num_allocs;
|
||||
|
||||
// The peak memory use between Start and Stop.
|
||||
int64_t max_bytes_used;
|
||||
};
|
||||
|
||||
virtual ~MemoryManager() {}
|
||||
|
||||
// Implement this to start recording allocation information.
|
||||
virtual void Start() = 0;
|
||||
|
||||
// Implement this to stop recording and fill out the given Result structure.
|
||||
virtual void Stop(Result* result) = 0;
|
||||
};
|
||||
|
||||
inline const char* GetTimeUnitString(TimeUnit unit) {
|
||||
switch (unit) {
|
||||
case kMillisecond:
|
||||
|
@ -1424,9 +1567,9 @@ inline const char* GetTimeUnitString(TimeUnit unit) {
|
|||
case kMicrosecond:
|
||||
return "us";
|
||||
case kNanosecond:
|
||||
default:
|
||||
return "ns";
|
||||
}
|
||||
BENCHMARK_UNREACHABLE();
|
||||
}
|
||||
|
||||
inline double GetTimeUnitMultiplier(TimeUnit unit) {
|
||||
|
@ -1436,9 +1579,9 @@ inline double GetTimeUnitMultiplier(TimeUnit unit) {
|
|||
case kMicrosecond:
|
||||
return 1e6;
|
||||
case kNanosecond:
|
||||
default:
|
||||
return 1e9;
|
||||
}
|
||||
BENCHMARK_UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace benchmark
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Allow the source files to find headers in src/
|
||||
include(GNUInstallDirs)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/src)
|
||||
|
||||
if (DEFINED BENCHMARK_CXX_LINKER_FLAGS)
|
||||
|
@ -17,6 +18,7 @@ foreach(item ${BENCHMARK_MAIN})
|
|||
endforeach()
|
||||
|
||||
add_library(benchmark ${SOURCE_FILES})
|
||||
add_library(benchmark::benchmark ALIAS benchmark)
|
||||
set_target_properties(benchmark PROPERTIES
|
||||
OUTPUT_NAME "benchmark"
|
||||
VERSION ${GENERIC_LIB_VERSION}
|
||||
|
@ -33,9 +35,17 @@ if(LIBRT)
|
|||
target_link_libraries(benchmark ${LIBRT})
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE)
|
||||
string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER)
|
||||
endif()
|
||||
if(NOT CMAKE_THREAD_LIBS_INIT AND "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" MATCHES ".*-fsanitize=[^ ]*address.*")
|
||||
message(WARNING "CMake's FindThreads.cmake did not fail, but CMAKE_THREAD_LIBS_INIT ended up being empty. This was fixed in https://github.com/Kitware/CMake/commit/d53317130e84898c5328c237186dbd995aaf1c12 Let's guess that -pthread is sufficient.")
|
||||
target_link_libraries(benchmark -pthread)
|
||||
endif()
|
||||
|
||||
# We need extra libraries on Windows
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||
target_link_libraries(benchmark Shlwapi)
|
||||
target_link_libraries(benchmark shlwapi)
|
||||
endif()
|
||||
|
||||
# We need extra libraries on Solaris
|
||||
|
@ -45,6 +55,7 @@ endif()
|
|||
|
||||
# Benchmark main library
|
||||
add_library(benchmark_main "benchmark_main.cc")
|
||||
add_library(benchmark::benchmark_main ALIAS benchmark_main)
|
||||
set_target_properties(benchmark_main PROPERTIES
|
||||
OUTPUT_NAME "benchmark_main"
|
||||
VERSION ${GENERIC_LIB_VERSION}
|
||||
|
@ -53,13 +64,8 @@ set_target_properties(benchmark_main PROPERTIES
|
|||
target_include_directories(benchmark PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
|
||||
)
|
||||
target_link_libraries(benchmark_main benchmark)
|
||||
target_link_libraries(benchmark_main benchmark::benchmark)
|
||||
|
||||
set(include_install_dir "include")
|
||||
set(lib_install_dir "lib/")
|
||||
set(bin_install_dir "bin/")
|
||||
set(config_install_dir "lib/cmake/${PROJECT_NAME}")
|
||||
set(pkgconfig_install_dir "lib/pkgconfig")
|
||||
|
||||
set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
|
||||
|
@ -83,26 +89,26 @@ if (BENCHMARK_ENABLE_INSTALL)
|
|||
install(
|
||||
TARGETS benchmark benchmark_main
|
||||
EXPORT ${targets_export_name}
|
||||
ARCHIVE DESTINATION ${lib_install_dir}
|
||||
LIBRARY DESTINATION ${lib_install_dir}
|
||||
RUNTIME DESTINATION ${bin_install_dir}
|
||||
INCLUDES DESTINATION ${include_install_dir})
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
install(
|
||||
DIRECTORY "${PROJECT_SOURCE_DIR}/include/benchmark"
|
||||
DESTINATION ${include_install_dir}
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING PATTERN "*.*h")
|
||||
|
||||
install(
|
||||
FILES "${project_config}" "${version_config}"
|
||||
DESTINATION "${config_install_dir}")
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
|
||||
install(
|
||||
FILES "${pkg_config}"
|
||||
DESTINATION "${pkgconfig_install_dir}")
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
|
||||
install(
|
||||
EXPORT "${targets_export_name}"
|
||||
NAMESPACE "${namespace}"
|
||||
DESTINATION "${config_install_dir}")
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
endif()
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "benchmark_api_internal.h"
|
||||
#include "benchmark_runner.h"
|
||||
#include "internal_macros.h"
|
||||
|
||||
#ifndef BENCHMARK_OS_WINDOWS
|
||||
|
@ -34,6 +35,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "check.h"
|
||||
#include "colorprint.h"
|
||||
|
@ -49,251 +51,72 @@
|
|||
#include "thread_manager.h"
|
||||
#include "thread_timer.h"
|
||||
|
||||
DEFINE_bool(benchmark_list_tests, false,
|
||||
"Print a list of benchmarks. This option overrides all other "
|
||||
"options.");
|
||||
// Print a list of benchmarks. This option overrides all other options.
|
||||
DEFINE_bool(benchmark_list_tests, false);
|
||||
|
||||
DEFINE_string(benchmark_filter, ".",
|
||||
"A regular expression that specifies the set of benchmarks "
|
||||
"to execute. If this flag is empty, no benchmarks are run. "
|
||||
"If this flag is the string \"all\", all benchmarks linked "
|
||||
"into the process are run.");
|
||||
// A regular expression that specifies the set of benchmarks to execute. If
|
||||
// this flag is empty, or if this flag is the string \"all\", all benchmarks
|
||||
// linked into the binary are run.
|
||||
DEFINE_string(benchmark_filter, ".");
|
||||
|
||||
DEFINE_double(benchmark_min_time, 0.5,
|
||||
"Minimum number of seconds we should run benchmark before "
|
||||
"results are considered significant. For cpu-time based "
|
||||
"tests, this is the lower bound on the total cpu time "
|
||||
"used by all threads that make up the test. For real-time "
|
||||
"based tests, this is the lower bound on the elapsed time "
|
||||
"of the benchmark execution, regardless of number of "
|
||||
"threads.");
|
||||
// Minimum number of seconds we should run benchmark before results are
|
||||
// considered significant. For cpu-time based tests, this is the lower bound
|
||||
// on the total cpu time used by all threads that make up the test. For
|
||||
// real-time based tests, this is the lower bound on the elapsed time of the
|
||||
// benchmark execution, regardless of number of threads.
|
||||
DEFINE_double(benchmark_min_time, 0.5);
|
||||
|
||||
DEFINE_int32(benchmark_repetitions, 1,
|
||||
"The number of runs of each benchmark. If greater than 1, the "
|
||||
"mean and standard deviation of the runs will be reported.");
|
||||
// The number of runs of each benchmark. If greater than 1, the mean and
|
||||
// standard deviation of the runs will be reported.
|
||||
DEFINE_int32(benchmark_repetitions, 1);
|
||||
|
||||
DEFINE_bool(benchmark_report_aggregates_only, false,
|
||||
"Report the result of each benchmark repetitions. When 'true' is "
|
||||
"specified only the mean, standard deviation, and other statistics "
|
||||
"are reported for repeated benchmarks.");
|
||||
// Report the result of each benchmark repetitions. When 'true' is specified
|
||||
// only the mean, standard deviation, and other statistics are reported for
|
||||
// repeated benchmarks. Affects all reporters.
|
||||
DEFINE_bool(benchmark_report_aggregates_only, false);
|
||||
|
||||
DEFINE_string(benchmark_format, "console",
|
||||
"The format to use for console output. Valid values are "
|
||||
"'console', 'json', or 'csv'.");
|
||||
// Display the result of each benchmark repetitions. When 'true' is specified
|
||||
// only the mean, standard deviation, and other statistics are displayed for
|
||||
// repeated benchmarks. Unlike benchmark_report_aggregates_only, only affects
|
||||
// the display reporter, but *NOT* file reporter, which will still contain
|
||||
// all the output.
|
||||
DEFINE_bool(benchmark_display_aggregates_only, false);
|
||||
|
||||
DEFINE_string(benchmark_out_format, "json",
|
||||
"The format to use for file output. Valid values are "
|
||||
"'console', 'json', or 'csv'.");
|
||||
// The format to use for console output.
|
||||
// Valid values are 'console', 'json', or 'csv'.
|
||||
DEFINE_string(benchmark_format, "console");
|
||||
|
||||
DEFINE_string(benchmark_out, "", "The file to write additional output to");
|
||||
// The format to use for file output.
|
||||
// Valid values are 'console', 'json', or 'csv'.
|
||||
DEFINE_string(benchmark_out_format, "json");
|
||||
|
||||
DEFINE_string(benchmark_color, "auto",
|
||||
"Whether to use colors in the output. Valid values: "
|
||||
"'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use "
|
||||
"colors if the output is being sent to a terminal and the TERM "
|
||||
"environment variable is set to a terminal type that supports "
|
||||
"colors.");
|
||||
// The file to write additional output to.
|
||||
DEFINE_string(benchmark_out, "");
|
||||
|
||||
DEFINE_bool(benchmark_counters_tabular, false,
|
||||
"Whether to use tabular format when printing user counters to "
|
||||
"the console. Valid values: 'true'/'yes'/1, 'false'/'no'/0."
|
||||
"Defaults to false.");
|
||||
// Whether to use colors in the output. Valid values:
|
||||
// 'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use colors if
|
||||
// the output is being sent to a terminal and the TERM environment variable is
|
||||
// set to a terminal type that supports colors.
|
||||
DEFINE_string(benchmark_color, "auto");
|
||||
|
||||
DEFINE_int32(v, 0, "The level of verbose logging to output");
|
||||
// Whether to use tabular format when printing user counters to the console.
|
||||
// Valid values: 'true'/'yes'/1, 'false'/'no'/0. Defaults to false.
|
||||
DEFINE_bool(benchmark_counters_tabular, false);
|
||||
|
||||
// The level of verbose logging to output
|
||||
DEFINE_int32(v, 0);
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
namespace {
|
||||
static const size_t kMaxIterations = 1000000000;
|
||||
} // end namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
// FIXME: wouldn't LTO mess this up?
|
||||
void UseCharPointer(char const volatile*) {}
|
||||
|
||||
namespace {
|
||||
|
||||
BenchmarkReporter::Run CreateRunReport(
|
||||
const benchmark::internal::Benchmark::Instance& b,
|
||||
const internal::ThreadManager::Result& results, double seconds) {
|
||||
// Create report about this benchmark run.
|
||||
BenchmarkReporter::Run report;
|
||||
|
||||
report.benchmark_name = b.name;
|
||||
report.error_occurred = results.has_error_;
|
||||
report.error_message = results.error_message_;
|
||||
report.report_label = results.report_label_;
|
||||
// This is the total iterations across all threads.
|
||||
report.iterations = results.iterations;
|
||||
report.time_unit = b.time_unit;
|
||||
|
||||
if (!report.error_occurred) {
|
||||
double bytes_per_second = 0;
|
||||
if (results.bytes_processed > 0 && seconds > 0.0) {
|
||||
bytes_per_second = (results.bytes_processed / seconds);
|
||||
}
|
||||
double items_per_second = 0;
|
||||
if (results.items_processed > 0 && seconds > 0.0) {
|
||||
items_per_second = (results.items_processed / seconds);
|
||||
}
|
||||
|
||||
if (b.use_manual_time) {
|
||||
report.real_accumulated_time = results.manual_time_used;
|
||||
} else {
|
||||
report.real_accumulated_time = results.real_time_used;
|
||||
}
|
||||
report.cpu_accumulated_time = results.cpu_time_used;
|
||||
report.bytes_per_second = bytes_per_second;
|
||||
report.items_per_second = items_per_second;
|
||||
report.complexity_n = results.complexity_n;
|
||||
report.complexity = b.complexity;
|
||||
report.complexity_lambda = b.complexity_lambda;
|
||||
report.statistics = b.statistics;
|
||||
report.counters = results.counters;
|
||||
internal::Finish(&report.counters, seconds, b.threads);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
// Execute one thread of benchmark b for the specified number of iterations.
|
||||
// Adds the stats collected for the thread into *total.
|
||||
void RunInThread(const benchmark::internal::Benchmark::Instance* b,
|
||||
size_t iters, int thread_id,
|
||||
internal::ThreadManager* manager) {
|
||||
internal::ThreadTimer timer;
|
||||
State st(iters, b->arg, thread_id, b->threads, &timer, manager);
|
||||
b->benchmark->Run(st);
|
||||
CHECK(st.iterations() >= st.max_iterations)
|
||||
<< "Benchmark returned before State::KeepRunning() returned false!";
|
||||
{
|
||||
MutexLock l(manager->GetBenchmarkMutex());
|
||||
internal::ThreadManager::Result& results = manager->results;
|
||||
results.iterations += st.iterations();
|
||||
results.cpu_time_used += timer.cpu_time_used();
|
||||
results.real_time_used += timer.real_time_used();
|
||||
results.manual_time_used += timer.manual_time_used();
|
||||
results.bytes_processed += st.bytes_processed();
|
||||
results.items_processed += st.items_processed();
|
||||
results.complexity_n += st.complexity_length_n();
|
||||
internal::Increment(&results.counters, st.counters);
|
||||
}
|
||||
manager->NotifyThreadComplete();
|
||||
}
|
||||
|
||||
std::vector<BenchmarkReporter::Run> RunBenchmark(
|
||||
const benchmark::internal::Benchmark::Instance& b,
|
||||
std::vector<BenchmarkReporter::Run>* complexity_reports) {
|
||||
std::vector<BenchmarkReporter::Run> reports; // return value
|
||||
|
||||
const bool has_explicit_iteration_count = b.iterations != 0;
|
||||
size_t iters = has_explicit_iteration_count ? b.iterations : 1;
|
||||
std::unique_ptr<internal::ThreadManager> manager;
|
||||
std::vector<std::thread> pool(b.threads - 1);
|
||||
const int repeats =
|
||||
b.repetitions != 0 ? b.repetitions : FLAGS_benchmark_repetitions;
|
||||
const bool report_aggregates_only =
|
||||
repeats != 1 &&
|
||||
(b.report_mode == internal::RM_Unspecified
|
||||
? FLAGS_benchmark_report_aggregates_only
|
||||
: b.report_mode == internal::RM_ReportAggregatesOnly);
|
||||
for (int repetition_num = 0; repetition_num < repeats; repetition_num++) {
|
||||
for (;;) {
|
||||
// Try benchmark
|
||||
VLOG(2) << "Running " << b.name << " for " << iters << "\n";
|
||||
|
||||
manager.reset(new internal::ThreadManager(b.threads));
|
||||
for (std::size_t ti = 0; ti < pool.size(); ++ti) {
|
||||
pool[ti] = std::thread(&RunInThread, &b, iters,
|
||||
static_cast<int>(ti + 1), manager.get());
|
||||
}
|
||||
RunInThread(&b, iters, 0, manager.get());
|
||||
manager->WaitForAllThreads();
|
||||
for (std::thread& thread : pool) thread.join();
|
||||
internal::ThreadManager::Result results;
|
||||
{
|
||||
MutexLock l(manager->GetBenchmarkMutex());
|
||||
results = manager->results;
|
||||
}
|
||||
manager.reset();
|
||||
// Adjust real/manual time stats since they were reported per thread.
|
||||
results.real_time_used /= b.threads;
|
||||
results.manual_time_used /= b.threads;
|
||||
|
||||
VLOG(2) << "Ran in " << results.cpu_time_used << "/"
|
||||
<< results.real_time_used << "\n";
|
||||
|
||||
// Base decisions off of real time if requested by this benchmark.
|
||||
double seconds = results.cpu_time_used;
|
||||
if (b.use_manual_time) {
|
||||
seconds = results.manual_time_used;
|
||||
} else if (b.use_real_time) {
|
||||
seconds = results.real_time_used;
|
||||
}
|
||||
|
||||
const double min_time =
|
||||
!IsZero(b.min_time) ? b.min_time : FLAGS_benchmark_min_time;
|
||||
|
||||
// clang-format off
|
||||
// turn off clang-format since it mangles prettiness here
|
||||
// Determine if this run should be reported; Either it has
|
||||
// run for a sufficient amount of time or because an error was reported.
|
||||
const bool should_report = repetition_num > 0
|
||||
|| has_explicit_iteration_count // An exact iteration count was requested
|
||||
|| results.has_error_
|
||||
|| iters >= kMaxIterations // No chance to try again, we hit the limit.
|
||||
|| seconds >= min_time // the elapsed time is large enough
|
||||
// CPU time is specified but the elapsed real time greatly exceeds the
|
||||
// minimum time. Note that user provided timers are except from this
|
||||
// sanity check.
|
||||
|| ((results.real_time_used >= 5 * min_time) && !b.use_manual_time);
|
||||
// clang-format on
|
||||
|
||||
if (should_report) {
|
||||
BenchmarkReporter::Run report = CreateRunReport(b, results, seconds);
|
||||
if (!report.error_occurred && b.complexity != oNone)
|
||||
complexity_reports->push_back(report);
|
||||
reports.push_back(report);
|
||||
break;
|
||||
}
|
||||
|
||||
// See how much iterations should be increased by
|
||||
// Note: Avoid division by zero with max(seconds, 1ns).
|
||||
double multiplier = min_time * 1.4 / std::max(seconds, 1e-9);
|
||||
// If our last run was at least 10% of FLAGS_benchmark_min_time then we
|
||||
// use the multiplier directly. Otherwise we use at most 10 times
|
||||
// expansion.
|
||||
// NOTE: When the last run was at least 10% of the min time the max
|
||||
// expansion should be 14x.
|
||||
bool is_significant = (seconds / min_time) > 0.1;
|
||||
multiplier = is_significant ? multiplier : std::min(10.0, multiplier);
|
||||
if (multiplier <= 1.0) multiplier = 2.0;
|
||||
double next_iters = std::max(multiplier * iters, iters + 1.0);
|
||||
if (next_iters > kMaxIterations) {
|
||||
next_iters = kMaxIterations;
|
||||
}
|
||||
VLOG(3) << "Next iters: " << next_iters << ", " << multiplier << "\n";
|
||||
iters = static_cast<int>(next_iters + 0.5);
|
||||
}
|
||||
}
|
||||
// Calculate additional statistics
|
||||
auto stat_reports = ComputeStats(reports);
|
||||
if ((b.complexity != oNone) && b.last_benchmark_instance) {
|
||||
auto additional_run_stats = ComputeBigO(*complexity_reports);
|
||||
stat_reports.insert(stat_reports.end(), additional_run_stats.begin(),
|
||||
additional_run_stats.end());
|
||||
complexity_reports->clear();
|
||||
}
|
||||
|
||||
if (report_aggregates_only) reports.clear();
|
||||
reports.insert(reports.end(), stat_reports.begin(), stat_reports.end());
|
||||
return reports;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace internal
|
||||
|
||||
State::State(size_t max_iters, const std::vector<int64_t>& ranges, int thread_i,
|
||||
int n_threads, internal::ThreadTimer* timer,
|
||||
State::State(IterationCount max_iters, const std::vector<int64_t>& ranges,
|
||||
int thread_i, int n_threads, internal::ThreadTimer* timer,
|
||||
internal::ThreadManager* manager)
|
||||
: total_iterations_(0),
|
||||
batch_leftover_(0),
|
||||
|
@ -302,8 +125,6 @@ State::State(size_t max_iters, const std::vector<int64_t>& ranges, int thread_i,
|
|||
finished_(false),
|
||||
error_occurred_(false),
|
||||
range_(ranges),
|
||||
bytes_processed_(0),
|
||||
items_processed_(0),
|
||||
complexity_n_(0),
|
||||
counters(),
|
||||
thread_index(thread_i),
|
||||
|
@ -319,7 +140,10 @@ State::State(size_t max_iters, const std::vector<int64_t>& ranges, int thread_i,
|
|||
// demonstrated since constexpr evaluation must diagnose all undefined
|
||||
// behavior). However, GCC and Clang also warn about this use of offsetof,
|
||||
// which must be suppressed.
|
||||
#ifdef __GNUC__
|
||||
#if defined(__INTEL_COMPILER)
|
||||
#pragma warning push
|
||||
#pragma warning(disable : 1875)
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#endif
|
||||
|
@ -328,7 +152,9 @@ State::State(size_t max_iters, const std::vector<int64_t>& ranges, int thread_i,
|
|||
static_assert(offsetof(State, error_occurred_) <=
|
||||
(cache_line_size - sizeof(error_occurred_)),
|
||||
"");
|
||||
#ifdef __GNUC__
|
||||
#if defined(__INTEL_COMPILER)
|
||||
#pragma warning pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
@ -389,25 +215,25 @@ void State::FinishKeepRunning() {
|
|||
namespace internal {
|
||||
namespace {
|
||||
|
||||
void RunBenchmarks(const std::vector<Benchmark::Instance>& benchmarks,
|
||||
BenchmarkReporter* console_reporter,
|
||||
void RunBenchmarks(const std::vector<BenchmarkInstance>& benchmarks,
|
||||
BenchmarkReporter* display_reporter,
|
||||
BenchmarkReporter* file_reporter) {
|
||||
// Note the file_reporter can be null.
|
||||
CHECK(console_reporter != nullptr);
|
||||
CHECK(display_reporter != nullptr);
|
||||
|
||||
// Determine the width of the name field using a minimum width of 10.
|
||||
bool has_repetitions = FLAGS_benchmark_repetitions > 1;
|
||||
bool might_have_aggregates = FLAGS_benchmark_repetitions > 1;
|
||||
size_t name_field_width = 10;
|
||||
size_t stat_field_width = 0;
|
||||
for (const Benchmark::Instance& benchmark : benchmarks) {
|
||||
for (const BenchmarkInstance& benchmark : benchmarks) {
|
||||
name_field_width =
|
||||
std::max<size_t>(name_field_width, benchmark.name.size());
|
||||
has_repetitions |= benchmark.repetitions > 1;
|
||||
std::max<size_t>(name_field_width, benchmark.name.str().size());
|
||||
might_have_aggregates |= benchmark.repetitions > 1;
|
||||
|
||||
for (const auto& Stat : *benchmark.statistics)
|
||||
stat_field_width = std::max<size_t>(stat_field_width, Stat.name_.size());
|
||||
}
|
||||
if (has_repetitions) name_field_width += 1 + stat_field_width;
|
||||
if (might_have_aggregates) name_field_width += 1 + stat_field_width;
|
||||
|
||||
// Print header here
|
||||
BenchmarkReporter::Context context;
|
||||
|
@ -424,25 +250,46 @@ void RunBenchmarks(const std::vector<Benchmark::Instance>& benchmarks,
|
|||
std::flush(reporter->GetErrorStream());
|
||||
};
|
||||
|
||||
if (console_reporter->ReportContext(context) &&
|
||||
if (display_reporter->ReportContext(context) &&
|
||||
(!file_reporter || file_reporter->ReportContext(context))) {
|
||||
flushStreams(console_reporter);
|
||||
flushStreams(display_reporter);
|
||||
flushStreams(file_reporter);
|
||||
|
||||
for (const auto& benchmark : benchmarks) {
|
||||
std::vector<BenchmarkReporter::Run> reports =
|
||||
RunBenchmark(benchmark, &complexity_reports);
|
||||
console_reporter->ReportRuns(reports);
|
||||
if (file_reporter) file_reporter->ReportRuns(reports);
|
||||
flushStreams(console_reporter);
|
||||
RunResults run_results = RunBenchmark(benchmark, &complexity_reports);
|
||||
|
||||
auto report = [&run_results](BenchmarkReporter* reporter,
|
||||
bool report_aggregates_only) {
|
||||
assert(reporter);
|
||||
// If there are no aggregates, do output non-aggregates.
|
||||
report_aggregates_only &= !run_results.aggregates_only.empty();
|
||||
if (!report_aggregates_only)
|
||||
reporter->ReportRuns(run_results.non_aggregates);
|
||||
if (!run_results.aggregates_only.empty())
|
||||
reporter->ReportRuns(run_results.aggregates_only);
|
||||
};
|
||||
|
||||
report(display_reporter, run_results.display_report_aggregates_only);
|
||||
if (file_reporter)
|
||||
report(file_reporter, run_results.file_report_aggregates_only);
|
||||
|
||||
flushStreams(display_reporter);
|
||||
flushStreams(file_reporter);
|
||||
}
|
||||
}
|
||||
console_reporter->Finalize();
|
||||
display_reporter->Finalize();
|
||||
if (file_reporter) file_reporter->Finalize();
|
||||
flushStreams(console_reporter);
|
||||
flushStreams(display_reporter);
|
||||
flushStreams(file_reporter);
|
||||
}
|
||||
|
||||
// Disable deprecated warnings temporarily because we need to reference
|
||||
// CSVReporter but don't want to trigger -Werror=-Wdeprecated-declarations
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
std::unique_ptr<BenchmarkReporter> CreateReporter(
|
||||
std::string const& name, ConsoleReporter::OutputOptions output_opts) {
|
||||
typedef std::unique_ptr<BenchmarkReporter> PtrType;
|
||||
|
@ -458,6 +305,10 @@ std::unique_ptr<BenchmarkReporter> CreateReporter(
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
} // end namespace
|
||||
|
||||
bool IsZero(double n) {
|
||||
|
@ -466,15 +317,20 @@ bool IsZero(double n) {
|
|||
|
||||
ConsoleReporter::OutputOptions GetOutputOptions(bool force_no_color) {
|
||||
int output_opts = ConsoleReporter::OO_Defaults;
|
||||
if ((FLAGS_benchmark_color == "auto" && IsColorTerminal()) ||
|
||||
IsTruthyFlagValue(FLAGS_benchmark_color)) {
|
||||
auto is_benchmark_color = [force_no_color]() -> bool {
|
||||
if (force_no_color) {
|
||||
return false;
|
||||
}
|
||||
if (FLAGS_benchmark_color == "auto") {
|
||||
return IsColorTerminal();
|
||||
}
|
||||
return IsTruthyFlagValue(FLAGS_benchmark_color);
|
||||
};
|
||||
if (is_benchmark_color()) {
|
||||
output_opts |= ConsoleReporter::OO_Color;
|
||||
} else {
|
||||
output_opts &= ~ConsoleReporter::OO_Color;
|
||||
}
|
||||
if (force_no_color) {
|
||||
output_opts &= ~ConsoleReporter::OO_Color;
|
||||
}
|
||||
if (FLAGS_benchmark_counters_tabular) {
|
||||
output_opts |= ConsoleReporter::OO_Tabular;
|
||||
} else {
|
||||
|
@ -489,11 +345,11 @@ size_t RunSpecifiedBenchmarks() {
|
|||
return RunSpecifiedBenchmarks(nullptr, nullptr);
|
||||
}
|
||||
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter) {
|
||||
return RunSpecifiedBenchmarks(console_reporter, nullptr);
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter) {
|
||||
return RunSpecifiedBenchmarks(display_reporter, nullptr);
|
||||
}
|
||||
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
|
||||
size_t RunSpecifiedBenchmarks(BenchmarkReporter* display_reporter,
|
||||
BenchmarkReporter* file_reporter) {
|
||||
std::string spec = FLAGS_benchmark_filter;
|
||||
if (spec.empty() || spec == "all")
|
||||
|
@ -501,15 +357,15 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
|
|||
|
||||
// Setup the reporters
|
||||
std::ofstream output_file;
|
||||
std::unique_ptr<BenchmarkReporter> default_console_reporter;
|
||||
std::unique_ptr<BenchmarkReporter> default_display_reporter;
|
||||
std::unique_ptr<BenchmarkReporter> default_file_reporter;
|
||||
if (!console_reporter) {
|
||||
default_console_reporter = internal::CreateReporter(
|
||||
if (!display_reporter) {
|
||||
default_display_reporter = internal::CreateReporter(
|
||||
FLAGS_benchmark_format, internal::GetOutputOptions());
|
||||
console_reporter = default_console_reporter.get();
|
||||
display_reporter = default_display_reporter.get();
|
||||
}
|
||||
auto& Out = console_reporter->GetOutputStream();
|
||||
auto& Err = console_reporter->GetErrorStream();
|
||||
auto& Out = display_reporter->GetOutputStream();
|
||||
auto& Err = display_reporter->GetErrorStream();
|
||||
|
||||
std::string const& fname = FLAGS_benchmark_out;
|
||||
if (fname.empty() && file_reporter) {
|
||||
|
@ -533,7 +389,7 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
|
|||
file_reporter->SetErrorStream(&output_file);
|
||||
}
|
||||
|
||||
std::vector<internal::Benchmark::Instance> benchmarks;
|
||||
std::vector<internal::BenchmarkInstance> benchmarks;
|
||||
if (!FindBenchmarksInternal(spec, &benchmarks, &Err)) return 0;
|
||||
|
||||
if (benchmarks.empty()) {
|
||||
|
@ -542,14 +398,19 @@ size_t RunSpecifiedBenchmarks(BenchmarkReporter* console_reporter,
|
|||
}
|
||||
|
||||
if (FLAGS_benchmark_list_tests) {
|
||||
for (auto const& benchmark : benchmarks) Out << benchmark.name << "\n";
|
||||
for (auto const& benchmark : benchmarks)
|
||||
Out << benchmark.name.str() << "\n";
|
||||
} else {
|
||||
internal::RunBenchmarks(benchmarks, console_reporter, file_reporter);
|
||||
internal::RunBenchmarks(benchmarks, display_reporter, file_reporter);
|
||||
}
|
||||
|
||||
return benchmarks.size();
|
||||
}
|
||||
|
||||
void RegisterMemoryManager(MemoryManager* manager) {
|
||||
internal::memory_manager = manager;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
void PrintUsageAndExit() {
|
||||
|
@ -559,7 +420,8 @@ void PrintUsageAndExit() {
|
|||
" [--benchmark_filter=<regex>]\n"
|
||||
" [--benchmark_min_time=<min_time>]\n"
|
||||
" [--benchmark_repetitions=<num_repetitions>]\n"
|
||||
" [--benchmark_report_aggregates_only={true|false}\n"
|
||||
" [--benchmark_report_aggregates_only={true|false}]\n"
|
||||
" [--benchmark_display_aggregates_only={true|false}]\n"
|
||||
" [--benchmark_format=<console|json|csv>]\n"
|
||||
" [--benchmark_out=<filename>]\n"
|
||||
" [--benchmark_out_format=<json|console|csv>]\n"
|
||||
|
@ -573,7 +435,7 @@ void ParseCommandLineFlags(int* argc, char** argv) {
|
|||
using namespace benchmark;
|
||||
BenchmarkReporter::Context::executable_name =
|
||||
(argc && *argc > 0) ? argv[0] : "unknown";
|
||||
for (int i = 1; i < *argc; ++i) {
|
||||
for (int i = 1; argc && i < *argc; ++i) {
|
||||
if (ParseBoolFlag(argv[i], "benchmark_list_tests",
|
||||
&FLAGS_benchmark_list_tests) ||
|
||||
ParseStringFlag(argv[i], "benchmark_filter", &FLAGS_benchmark_filter) ||
|
||||
|
@ -583,6 +445,8 @@ void ParseCommandLineFlags(int* argc, char** argv) {
|
|||
&FLAGS_benchmark_repetitions) ||
|
||||
ParseBoolFlag(argv[i], "benchmark_report_aggregates_only",
|
||||
&FLAGS_benchmark_report_aggregates_only) ||
|
||||
ParseBoolFlag(argv[i], "benchmark_display_aggregates_only",
|
||||
&FLAGS_benchmark_display_aggregates_only) ||
|
||||
ParseStringFlag(argv[i], "benchmark_format", &FLAGS_benchmark_format) ||
|
||||
ParseStringFlag(argv[i], "benchmark_out", &FLAGS_benchmark_out) ||
|
||||
ParseStringFlag(argv[i], "benchmark_out_format",
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#include "benchmark_api_internal.h"
|
||||
|
||||
namespace benchmark {
|
||||
namespace internal {
|
||||
|
||||
State BenchmarkInstance::Run(IterationCount iters, int thread_id,
|
||||
internal::ThreadTimer* timer,
|
||||
internal::ThreadManager* manager) const {
|
||||
State st(iters, arg, thread_id, threads, timer, manager);
|
||||
benchmark->Run(st);
|
||||
return st;
|
||||
}
|
||||
|
||||
} // internal
|
||||
} // benchmark
|
|
@ -2,10 +2,12 @@
|
|||
#define BENCHMARK_API_INTERNAL_H
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "commandlineflags.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iosfwd>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -13,13 +15,14 @@ namespace benchmark {
|
|||
namespace internal {
|
||||
|
||||
// Information kept per benchmark we may want to run
|
||||
struct Benchmark::Instance {
|
||||
std::string name;
|
||||
struct BenchmarkInstance {
|
||||
BenchmarkName name;
|
||||
Benchmark* benchmark;
|
||||
ReportMode report_mode;
|
||||
AggregationReportMode aggregation_report_mode;
|
||||
std::vector<int64_t> arg;
|
||||
TimeUnit time_unit;
|
||||
int range_multiplier;
|
||||
bool measure_process_cpu_time;
|
||||
bool use_real_time;
|
||||
bool use_manual_time;
|
||||
BigO complexity;
|
||||
|
@ -29,12 +32,15 @@ struct Benchmark::Instance {
|
|||
bool last_benchmark_instance;
|
||||
int repetitions;
|
||||
double min_time;
|
||||
size_t iterations;
|
||||
IterationCount iterations;
|
||||
int threads; // Number of concurrent threads to us
|
||||
|
||||
State Run(IterationCount iters, int thread_id, internal::ThreadTimer* timer,
|
||||
internal::ThreadManager* manager) const;
|
||||
};
|
||||
|
||||
bool FindBenchmarksInternal(const std::string& re,
|
||||
std::vector<Benchmark::Instance>* benchmarks,
|
||||
std::vector<BenchmarkInstance>* benchmarks,
|
||||
std::ostream* Err);
|
||||
|
||||
bool IsZero(double n);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
namespace {
|
||||
|
||||
// Compute the total size of a pack of std::strings
|
||||
size_t size_impl() { return 0; }
|
||||
|
||||
template <typename Head, typename... Tail>
|
||||
size_t size_impl(const Head& head, const Tail&... tail) {
|
||||
return head.size() + size_impl(tail...);
|
||||
}
|
||||
|
||||
// Join a pack of std::strings using a delimiter
|
||||
// TODO: use absl::StrJoin
|
||||
void join_impl(std::string&, char) {}
|
||||
|
||||
template <typename Head, typename... Tail>
|
||||
void join_impl(std::string& s, const char delimiter, const Head& head,
|
||||
const Tail&... tail) {
|
||||
if (!s.empty() && !head.empty()) {
|
||||
s += delimiter;
|
||||
}
|
||||
|
||||
s += head;
|
||||
|
||||
join_impl(s, delimiter, tail...);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
std::string join(char delimiter, const Ts&... ts) {
|
||||
std::string s;
|
||||
s.reserve(sizeof...(Ts) + size_impl(ts...));
|
||||
join_impl(s, delimiter, ts...);
|
||||
return s;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string BenchmarkName::str() const {
|
||||
return join('/', function_name, args, min_time, iterations, repetitions,
|
||||
time_type, threads);
|
||||
}
|
||||
} // namespace benchmark
|
|
@ -34,6 +34,11 @@
|
|||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "benchmark_api_internal.h"
|
||||
#include "check.h"
|
||||
|
@ -78,7 +83,7 @@ class BenchmarkFamilies {
|
|||
// Extract the list of benchmark instances that match the specified
|
||||
// regular expression.
|
||||
bool FindBenchmarks(std::string re,
|
||||
std::vector<Benchmark::Instance>* benchmarks,
|
||||
std::vector<BenchmarkInstance>* benchmarks,
|
||||
std::ostream* Err);
|
||||
|
||||
private:
|
||||
|
@ -107,7 +112,7 @@ void BenchmarkFamilies::ClearBenchmarks() {
|
|||
}
|
||||
|
||||
bool BenchmarkFamilies::FindBenchmarks(
|
||||
std::string spec, std::vector<Benchmark::Instance>* benchmarks,
|
||||
std::string spec, std::vector<BenchmarkInstance>* benchmarks,
|
||||
std::ostream* ErrStream) {
|
||||
CHECK(ErrStream);
|
||||
auto& Err = *ErrStream;
|
||||
|
@ -152,16 +157,17 @@ bool BenchmarkFamilies::FindBenchmarks(
|
|||
|
||||
for (auto const& args : family->args_) {
|
||||
for (int num_threads : *thread_counts) {
|
||||
Benchmark::Instance instance;
|
||||
instance.name = family->name_;
|
||||
BenchmarkInstance instance;
|
||||
instance.name.function_name = family->name_;
|
||||
instance.benchmark = family.get();
|
||||
instance.report_mode = family->report_mode_;
|
||||
instance.aggregation_report_mode = family->aggregation_report_mode_;
|
||||
instance.arg = args;
|
||||
instance.time_unit = family->time_unit_;
|
||||
instance.range_multiplier = family->range_multiplier_;
|
||||
instance.min_time = family->min_time_;
|
||||
instance.iterations = family->iterations_;
|
||||
instance.repetitions = family->repetitions_;
|
||||
instance.measure_process_cpu_time = family->measure_process_cpu_time_;
|
||||
instance.use_real_time = family->use_real_time_;
|
||||
instance.use_manual_time = family->use_manual_time_;
|
||||
instance.complexity = family->complexity_;
|
||||
|
@ -172,40 +178,57 @@ bool BenchmarkFamilies::FindBenchmarks(
|
|||
// Add arguments to instance name
|
||||
size_t arg_i = 0;
|
||||
for (auto const& arg : args) {
|
||||
instance.name += "/";
|
||||
if (!instance.name.args.empty()) {
|
||||
instance.name.args += '/';
|
||||
}
|
||||
|
||||
if (arg_i < family->arg_names_.size()) {
|
||||
const auto& arg_name = family->arg_names_[arg_i];
|
||||
if (!arg_name.empty()) {
|
||||
instance.name +=
|
||||
StrFormat("%s:", family->arg_names_[arg_i].c_str());
|
||||
instance.name.args += StrFormat("%s:", arg_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
instance.name += StrFormat("%d", arg);
|
||||
instance.name.args += StrFormat("%" PRId64, arg);
|
||||
++arg_i;
|
||||
}
|
||||
|
||||
if (!IsZero(family->min_time_))
|
||||
instance.name += StrFormat("/min_time:%0.3f", family->min_time_);
|
||||
if (family->iterations_ != 0)
|
||||
instance.name += StrFormat("/iterations:%d", family->iterations_);
|
||||
instance.name.min_time =
|
||||
StrFormat("min_time:%0.3f", family->min_time_);
|
||||
if (family->iterations_ != 0) {
|
||||
instance.name.iterations =
|
||||
StrFormat("iterations:%lu",
|
||||
static_cast<unsigned long>(family->iterations_));
|
||||
}
|
||||
if (family->repetitions_ != 0)
|
||||
instance.name += StrFormat("/repeats:%d", family->repetitions_);
|
||||
instance.name.repetitions =
|
||||
StrFormat("repeats:%d", family->repetitions_);
|
||||
|
||||
if (family->measure_process_cpu_time_) {
|
||||
instance.name.time_type = "process_time";
|
||||
}
|
||||
|
||||
if (family->use_manual_time_) {
|
||||
instance.name += "/manual_time";
|
||||
if (!instance.name.time_type.empty()) {
|
||||
instance.name.time_type += '/';
|
||||
}
|
||||
instance.name.time_type += "manual_time";
|
||||
} else if (family->use_real_time_) {
|
||||
instance.name += "/real_time";
|
||||
if (!instance.name.time_type.empty()) {
|
||||
instance.name.time_type += '/';
|
||||
}
|
||||
instance.name.time_type += "real_time";
|
||||
}
|
||||
|
||||
// Add the number of threads used to the name
|
||||
if (!family->thread_counts_.empty()) {
|
||||
instance.name += StrFormat("/threads:%d", instance.threads);
|
||||
instance.name.threads = StrFormat("threads:%d", instance.threads);
|
||||
}
|
||||
|
||||
if ((re.Match(instance.name) && !isNegativeFilter) ||
|
||||
(!re.Match(instance.name) && isNegativeFilter)) {
|
||||
const auto full_name = instance.name.str();
|
||||
if ((re.Match(full_name) && !isNegativeFilter) ||
|
||||
(!re.Match(full_name) && isNegativeFilter)) {
|
||||
instance.last_benchmark_instance = (&args == &family->args_.back());
|
||||
benchmarks->push_back(std::move(instance));
|
||||
}
|
||||
|
@ -225,7 +248,7 @@ Benchmark* RegisterBenchmarkInternal(Benchmark* bench) {
|
|||
// FIXME: This function is a hack so that benchmark.cc can access
|
||||
// `BenchmarkFamilies`
|
||||
bool FindBenchmarksInternal(const std::string& re,
|
||||
std::vector<Benchmark::Instance>* benchmarks,
|
||||
std::vector<BenchmarkInstance>* benchmarks,
|
||||
std::ostream* Err) {
|
||||
return BenchmarkFamilies::GetInstance()->FindBenchmarks(re, benchmarks, Err);
|
||||
}
|
||||
|
@ -236,12 +259,13 @@ bool FindBenchmarksInternal(const std::string& re,
|
|||
|
||||
Benchmark::Benchmark(const char* name)
|
||||
: name_(name),
|
||||
report_mode_(RM_Unspecified),
|
||||
aggregation_report_mode_(ARM_Unspecified),
|
||||
time_unit_(kNanosecond),
|
||||
range_multiplier_(kRangeMultiplier),
|
||||
min_time_(0),
|
||||
iterations_(0),
|
||||
repetitions_(0),
|
||||
measure_process_cpu_time_(false),
|
||||
use_real_time_(false),
|
||||
use_manual_time_(false),
|
||||
complexity_(oNone),
|
||||
|
@ -323,7 +347,6 @@ Benchmark* Benchmark::ArgNames(const std::vector<std::string>& names) {
|
|||
|
||||
Benchmark* Benchmark::DenseRange(int64_t start, int64_t limit, int step) {
|
||||
CHECK(ArgsCnt() == -1 || ArgsCnt() == 1);
|
||||
CHECK_GE(start, 0);
|
||||
CHECK_LE(start, limit);
|
||||
for (int64_t arg = start; arg <= limit; arg += step) {
|
||||
args_.push_back({arg});
|
||||
|
@ -355,7 +378,7 @@ Benchmark* Benchmark::MinTime(double t) {
|
|||
return this;
|
||||
}
|
||||
|
||||
Benchmark* Benchmark::Iterations(size_t n) {
|
||||
Benchmark* Benchmark::Iterations(IterationCount n) {
|
||||
CHECK(n > 0);
|
||||
CHECK(IsZero(min_time_));
|
||||
iterations_ = n;
|
||||
|
@ -369,7 +392,29 @@ Benchmark* Benchmark::Repetitions(int n) {
|
|||
}
|
||||
|
||||
Benchmark* Benchmark::ReportAggregatesOnly(bool value) {
|
||||
report_mode_ = value ? RM_ReportAggregatesOnly : RM_Default;
|
||||
aggregation_report_mode_ = value ? ARM_ReportAggregatesOnly : ARM_Default;
|
||||
return this;
|
||||
}
|
||||
|
||||
Benchmark* Benchmark::DisplayAggregatesOnly(bool value) {
|
||||
// If we were called, the report mode is no longer 'unspecified', in any case.
|
||||
aggregation_report_mode_ = static_cast<AggregationReportMode>(
|
||||
aggregation_report_mode_ | ARM_Default);
|
||||
|
||||
if (value) {
|
||||
aggregation_report_mode_ = static_cast<AggregationReportMode>(
|
||||
aggregation_report_mode_ | ARM_DisplayReportAggregatesOnly);
|
||||
} else {
|
||||
aggregation_report_mode_ = static_cast<AggregationReportMode>(
|
||||
aggregation_report_mode_ & ~ARM_DisplayReportAggregatesOnly);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Benchmark* Benchmark::MeasureProcessCPUTime() {
|
||||
// Can be used together with UseRealTime() / UseManualTime().
|
||||
measure_process_cpu_time_ = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,29 +5,103 @@
|
|||
|
||||
#include "check.h"
|
||||
|
||||
namespace benchmark {
|
||||
namespace internal {
|
||||
|
||||
// Append the powers of 'mult' in the closed interval [lo, hi].
|
||||
// Returns iterator to the start of the inserted range.
|
||||
template <typename T>
|
||||
typename std::vector<T>::iterator
|
||||
AddPowers(std::vector<T>* dst, T lo, T hi, int mult) {
|
||||
CHECK_GE(lo, 0);
|
||||
CHECK_GE(hi, lo);
|
||||
CHECK_GE(mult, 2);
|
||||
|
||||
const size_t start_offset = dst->size();
|
||||
|
||||
static const T kmax = std::numeric_limits<T>::max();
|
||||
|
||||
// Space out the values in multiples of "mult"
|
||||
for (T i = 1; i <= hi; i *= mult) {
|
||||
if (i >= lo) {
|
||||
dst->push_back(i);
|
||||
}
|
||||
// Break the loop here since multiplying by
|
||||
// 'mult' would move outside of the range of T
|
||||
if (i > kmax / mult) break;
|
||||
}
|
||||
|
||||
return dst->begin() + start_offset;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AddNegatedPowers(std::vector<T>* dst, T lo, T hi, int mult) {
|
||||
// We negate lo and hi so we require that they cannot be equal to 'min'.
|
||||
CHECK_GT(lo, std::numeric_limits<T>::min());
|
||||
CHECK_GT(hi, std::numeric_limits<T>::min());
|
||||
CHECK_GE(hi, lo);
|
||||
CHECK_LE(hi, 0);
|
||||
|
||||
// Add positive powers, then negate and reverse.
|
||||
// Casts necessary since small integers get promoted
|
||||
// to 'int' when negating.
|
||||
const auto lo_complement = static_cast<T>(-lo);
|
||||
const auto hi_complement = static_cast<T>(-hi);
|
||||
|
||||
const auto it = AddPowers(dst, hi_complement, lo_complement, mult);
|
||||
|
||||
std::for_each(it, dst->end(), [](T& t) { t *= -1; });
|
||||
std::reverse(it, dst->end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AddRange(std::vector<T>* dst, T lo, T hi, int mult) {
|
||||
CHECK_GE(lo, 0);
|
||||
static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
|
||||
"Args type must be a signed integer");
|
||||
|
||||
CHECK_GE(hi, lo);
|
||||
CHECK_GE(mult, 2);
|
||||
|
||||
// Add "lo"
|
||||
dst->push_back(lo);
|
||||
|
||||
static const T kmax = std::numeric_limits<T>::max();
|
||||
// Handle lo == hi as a special case, so we then know
|
||||
// lo < hi and so it is safe to add 1 to lo and subtract 1
|
||||
// from hi without falling outside of the range of T.
|
||||
if (lo == hi) return;
|
||||
|
||||
// Now space out the benchmarks in multiples of "mult"
|
||||
for (T i = 1; i < kmax / mult; i *= mult) {
|
||||
if (i >= hi) break;
|
||||
if (i > lo) {
|
||||
dst->push_back(i);
|
||||
}
|
||||
// Ensure that lo_inner <= hi_inner below.
|
||||
if (lo + 1 == hi) {
|
||||
dst->push_back(hi);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add "hi" (if different from "lo")
|
||||
if (hi != lo) {
|
||||
// Add all powers of 'mult' in the range [lo+1, hi-1] (inclusive).
|
||||
const auto lo_inner = static_cast<T>(lo + 1);
|
||||
const auto hi_inner = static_cast<T>(hi - 1);
|
||||
|
||||
// Insert negative values
|
||||
if (lo_inner < 0) {
|
||||
AddNegatedPowers(dst, lo_inner, std::min(hi_inner, T{-1}), mult);
|
||||
}
|
||||
|
||||
// Treat 0 as a special case (see discussion on #762).
|
||||
if (lo <= 0 && hi >= 0) {
|
||||
dst->push_back(0);
|
||||
}
|
||||
|
||||
// Insert positive values
|
||||
if (hi_inner > 0) {
|
||||
AddPowers(dst, std::max(lo_inner, T{1}), hi_inner, mult);
|
||||
}
|
||||
|
||||
// Add "hi" (if different from last value).
|
||||
if (hi != dst->back()) {
|
||||
dst->push_back(hi);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace benchmark
|
||||
|
||||
#endif // BENCHMARK_REGISTER_H
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "benchmark_runner.h"
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "benchmark_api_internal.h"
|
||||
#include "internal_macros.h"
|
||||
|
||||
#ifndef BENCHMARK_OS_WINDOWS
|
||||
#ifndef BENCHMARK_OS_FUCHSIA
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "check.h"
|
||||
#include "colorprint.h"
|
||||
#include "commandlineflags.h"
|
||||
#include "complexity.h"
|
||||
#include "counter.h"
|
||||
#include "internal_macros.h"
|
||||
#include "log.h"
|
||||
#include "mutex.h"
|
||||
#include "re.h"
|
||||
#include "statistics.h"
|
||||
#include "string_util.h"
|
||||
#include "thread_manager.h"
|
||||
#include "thread_timer.h"
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
namespace internal {
|
||||
|
||||
MemoryManager* memory_manager = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr IterationCount kMaxIterations = 1000000000;
|
||||
|
||||
BenchmarkReporter::Run CreateRunReport(
|
||||
const benchmark::internal::BenchmarkInstance& b,
|
||||
const internal::ThreadManager::Result& results,
|
||||
IterationCount memory_iterations,
|
||||
const MemoryManager::Result& memory_result, double seconds,
|
||||
int64_t repetition_index) {
|
||||
// Create report about this benchmark run.
|
||||
BenchmarkReporter::Run report;
|
||||
|
||||
report.run_name = b.name;
|
||||
report.error_occurred = results.has_error_;
|
||||
report.error_message = results.error_message_;
|
||||
report.report_label = results.report_label_;
|
||||
// This is the total iterations across all threads.
|
||||
report.iterations = results.iterations;
|
||||
report.time_unit = b.time_unit;
|
||||
report.threads = b.threads;
|
||||
report.repetition_index = repetition_index;
|
||||
report.repetitions = b.repetitions;
|
||||
|
||||
if (!report.error_occurred) {
|
||||
if (b.use_manual_time) {
|
||||
report.real_accumulated_time = results.manual_time_used;
|
||||
} else {
|
||||
report.real_accumulated_time = results.real_time_used;
|
||||
}
|
||||
report.cpu_accumulated_time = results.cpu_time_used;
|
||||
report.complexity_n = results.complexity_n;
|
||||
report.complexity = b.complexity;
|
||||
report.complexity_lambda = b.complexity_lambda;
|
||||
report.statistics = b.statistics;
|
||||
report.counters = results.counters;
|
||||
|
||||
if (memory_iterations > 0) {
|
||||
report.has_memory_result = true;
|
||||
report.allocs_per_iter =
|
||||
memory_iterations ? static_cast<double>(memory_result.num_allocs) /
|
||||
memory_iterations
|
||||
: 0;
|
||||
report.max_bytes_used = memory_result.max_bytes_used;
|
||||
}
|
||||
|
||||
internal::Finish(&report.counters, results.iterations, seconds, b.threads);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
// Execute one thread of benchmark b for the specified number of iterations.
|
||||
// Adds the stats collected for the thread into *total.
|
||||
void RunInThread(const BenchmarkInstance* b, IterationCount iters,
|
||||
int thread_id, ThreadManager* manager) {
|
||||
internal::ThreadTimer timer(
|
||||
b->measure_process_cpu_time
|
||||
? internal::ThreadTimer::CreateProcessCpuTime()
|
||||
: internal::ThreadTimer::Create());
|
||||
State st = b->Run(iters, thread_id, &timer, manager);
|
||||
CHECK(st.error_occurred() || st.iterations() >= st.max_iterations)
|
||||
<< "Benchmark returned before State::KeepRunning() returned false!";
|
||||
{
|
||||
MutexLock l(manager->GetBenchmarkMutex());
|
||||
internal::ThreadManager::Result& results = manager->results;
|
||||
results.iterations += st.iterations();
|
||||
results.cpu_time_used += timer.cpu_time_used();
|
||||
results.real_time_used += timer.real_time_used();
|
||||
results.manual_time_used += timer.manual_time_used();
|
||||
results.complexity_n += st.complexity_length_n();
|
||||
internal::Increment(&results.counters, st.counters);
|
||||
}
|
||||
manager->NotifyThreadComplete();
|
||||
}
|
||||
|
||||
class BenchmarkRunner {
|
||||
public:
|
||||
BenchmarkRunner(const benchmark::internal::BenchmarkInstance& b_,
|
||||
std::vector<BenchmarkReporter::Run>* complexity_reports_)
|
||||
: b(b_),
|
||||
complexity_reports(*complexity_reports_),
|
||||
min_time(!IsZero(b.min_time) ? b.min_time : FLAGS_benchmark_min_time),
|
||||
repeats(b.repetitions != 0 ? b.repetitions
|
||||
: FLAGS_benchmark_repetitions),
|
||||
has_explicit_iteration_count(b.iterations != 0),
|
||||
pool(b.threads - 1),
|
||||
iters(has_explicit_iteration_count ? b.iterations : 1) {
|
||||
run_results.display_report_aggregates_only =
|
||||
(FLAGS_benchmark_report_aggregates_only ||
|
||||
FLAGS_benchmark_display_aggregates_only);
|
||||
run_results.file_report_aggregates_only =
|
||||
FLAGS_benchmark_report_aggregates_only;
|
||||
if (b.aggregation_report_mode != internal::ARM_Unspecified) {
|
||||
run_results.display_report_aggregates_only =
|
||||
(b.aggregation_report_mode &
|
||||
internal::ARM_DisplayReportAggregatesOnly);
|
||||
run_results.file_report_aggregates_only =
|
||||
(b.aggregation_report_mode & internal::ARM_FileReportAggregatesOnly);
|
||||
}
|
||||
|
||||
for (int repetition_num = 0; repetition_num < repeats; repetition_num++) {
|
||||
DoOneRepetition(repetition_num);
|
||||
}
|
||||
|
||||
// Calculate additional statistics
|
||||
run_results.aggregates_only = ComputeStats(run_results.non_aggregates);
|
||||
|
||||
// Maybe calculate complexity report
|
||||
if ((b.complexity != oNone) && b.last_benchmark_instance) {
|
||||
auto additional_run_stats = ComputeBigO(complexity_reports);
|
||||
run_results.aggregates_only.insert(run_results.aggregates_only.end(),
|
||||
additional_run_stats.begin(),
|
||||
additional_run_stats.end());
|
||||
complexity_reports.clear();
|
||||
}
|
||||
}
|
||||
|
||||
RunResults&& get_results() { return std::move(run_results); }
|
||||
|
||||
private:
|
||||
RunResults run_results;
|
||||
|
||||
const benchmark::internal::BenchmarkInstance& b;
|
||||
std::vector<BenchmarkReporter::Run>& complexity_reports;
|
||||
|
||||
const double min_time;
|
||||
const int repeats;
|
||||
const bool has_explicit_iteration_count;
|
||||
|
||||
std::vector<std::thread> pool;
|
||||
|
||||
IterationCount iters; // preserved between repetitions!
|
||||
// So only the first repetition has to find/calculate it,
|
||||
// the other repetitions will just use that precomputed iteration count.
|
||||
|
||||
struct IterationResults {
|
||||
internal::ThreadManager::Result results;
|
||||
IterationCount iters;
|
||||
double seconds;
|
||||
};
|
||||
IterationResults DoNIterations() {
|
||||
VLOG(2) << "Running " << b.name.str() << " for " << iters << "\n";
|
||||
|
||||
std::unique_ptr<internal::ThreadManager> manager;
|
||||
manager.reset(new internal::ThreadManager(b.threads));
|
||||
|
||||
// Run all but one thread in separate threads
|
||||
for (std::size_t ti = 0; ti < pool.size(); ++ti) {
|
||||
pool[ti] = std::thread(&RunInThread, &b, iters, static_cast<int>(ti + 1),
|
||||
manager.get());
|
||||
}
|
||||
// And run one thread here directly.
|
||||
// (If we were asked to run just one thread, we don't create new threads.)
|
||||
// Yes, we need to do this here *after* we start the separate threads.
|
||||
RunInThread(&b, iters, 0, manager.get());
|
||||
|
||||
// The main thread has finished. Now let's wait for the other threads.
|
||||
manager->WaitForAllThreads();
|
||||
for (std::thread& thread : pool) thread.join();
|
||||
|
||||
IterationResults i;
|
||||
// Acquire the measurements/counters from the manager, UNDER THE LOCK!
|
||||
{
|
||||
MutexLock l(manager->GetBenchmarkMutex());
|
||||
i.results = manager->results;
|
||||
}
|
||||
|
||||
// And get rid of the manager.
|
||||
manager.reset();
|
||||
|
||||
// Adjust real/manual time stats since they were reported per thread.
|
||||
i.results.real_time_used /= b.threads;
|
||||
i.results.manual_time_used /= b.threads;
|
||||
// If we were measuring whole-process CPU usage, adjust the CPU time too.
|
||||
if (b.measure_process_cpu_time) i.results.cpu_time_used /= b.threads;
|
||||
|
||||
VLOG(2) << "Ran in " << i.results.cpu_time_used << "/"
|
||||
<< i.results.real_time_used << "\n";
|
||||
|
||||
// So for how long were we running?
|
||||
i.iters = iters;
|
||||
// Base decisions off of real time if requested by this benchmark.
|
||||
i.seconds = i.results.cpu_time_used;
|
||||
if (b.use_manual_time) {
|
||||
i.seconds = i.results.manual_time_used;
|
||||
} else if (b.use_real_time) {
|
||||
i.seconds = i.results.real_time_used;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
IterationCount PredictNumItersNeeded(const IterationResults& i) const {
|
||||
// See how much iterations should be increased by.
|
||||
// Note: Avoid division by zero with max(seconds, 1ns).
|
||||
double multiplier = min_time * 1.4 / std::max(i.seconds, 1e-9);
|
||||
// If our last run was at least 10% of FLAGS_benchmark_min_time then we
|
||||
// use the multiplier directly.
|
||||
// Otherwise we use at most 10 times expansion.
|
||||
// NOTE: When the last run was at least 10% of the min time the max
|
||||
// expansion should be 14x.
|
||||
bool is_significant = (i.seconds / min_time) > 0.1;
|
||||
multiplier = is_significant ? multiplier : std::min(10.0, multiplier);
|
||||
if (multiplier <= 1.0) multiplier = 2.0;
|
||||
|
||||
// So what seems to be the sufficiently-large iteration count? Round up.
|
||||
const IterationCount max_next_iters = static_cast<IterationCount>(
|
||||
std::lround(std::max(multiplier * static_cast<double>(i.iters),
|
||||
static_cast<double>(i.iters) + 1.0)));
|
||||
// But we do have *some* sanity limits though..
|
||||
const IterationCount next_iters = std::min(max_next_iters, kMaxIterations);
|
||||
|
||||
VLOG(3) << "Next iters: " << next_iters << ", " << multiplier << "\n";
|
||||
return next_iters; // round up before conversion to integer.
|
||||
}
|
||||
|
||||
bool ShouldReportIterationResults(const IterationResults& i) const {
|
||||
// Determine if this run should be reported;
|
||||
// Either it has run for a sufficient amount of time
|
||||
// or because an error was reported.
|
||||
return i.results.has_error_ ||
|
||||
i.iters >= kMaxIterations || // Too many iterations already.
|
||||
i.seconds >= min_time || // The elapsed time is large enough.
|
||||
// CPU time is specified but the elapsed real time greatly exceeds
|
||||
// the minimum time.
|
||||
// Note that user provided timers are except from this sanity check.
|
||||
((i.results.real_time_used >= 5 * min_time) && !b.use_manual_time);
|
||||
}
|
||||
|
||||
void DoOneRepetition(int64_t repetition_index) {
|
||||
const bool is_the_first_repetition = repetition_index == 0;
|
||||
IterationResults i;
|
||||
|
||||
// We *may* be gradually increasing the length (iteration count)
|
||||
// of the benchmark until we decide the results are significant.
|
||||
// And once we do, we report those last results and exit.
|
||||
// Please do note that the if there are repetitions, the iteration count
|
||||
// is *only* calculated for the *first* repetition, and other repetitions
|
||||
// simply use that precomputed iteration count.
|
||||
for (;;) {
|
||||
i = DoNIterations();
|
||||
|
||||
// Do we consider the results to be significant?
|
||||
// If we are doing repetitions, and the first repetition was already done,
|
||||
// it has calculated the correct iteration time, so we have run that very
|
||||
// iteration count just now. No need to calculate anything. Just report.
|
||||
// Else, the normal rules apply.
|
||||
const bool results_are_significant = !is_the_first_repetition ||
|
||||
has_explicit_iteration_count ||
|
||||
ShouldReportIterationResults(i);
|
||||
|
||||
if (results_are_significant) break; // Good, let's report them!
|
||||
|
||||
// Nope, bad iteration. Let's re-estimate the hopefully-sufficient
|
||||
// iteration count, and run the benchmark again...
|
||||
|
||||
iters = PredictNumItersNeeded(i);
|
||||
assert(iters > i.iters &&
|
||||
"if we did more iterations than we want to do the next time, "
|
||||
"then we should have accepted the current iteration run.");
|
||||
}
|
||||
|
||||
// Oh, one last thing, we need to also produce the 'memory measurements'..
|
||||
MemoryManager::Result memory_result;
|
||||
IterationCount memory_iterations = 0;
|
||||
if (memory_manager != nullptr) {
|
||||
// Only run a few iterations to reduce the impact of one-time
|
||||
// allocations in benchmarks that are not properly managed.
|
||||
memory_iterations = std::min<IterationCount>(16, iters);
|
||||
memory_manager->Start();
|
||||
std::unique_ptr<internal::ThreadManager> manager;
|
||||
manager.reset(new internal::ThreadManager(1));
|
||||
RunInThread(&b, memory_iterations, 0, manager.get());
|
||||
manager->WaitForAllThreads();
|
||||
manager.reset();
|
||||
|
||||
memory_manager->Stop(&memory_result);
|
||||
}
|
||||
|
||||
// Ok, now actualy report.
|
||||
BenchmarkReporter::Run report =
|
||||
CreateRunReport(b, i.results, memory_iterations, memory_result,
|
||||
i.seconds, repetition_index);
|
||||
|
||||
if (!report.error_occurred && b.complexity != oNone)
|
||||
complexity_reports.push_back(report);
|
||||
|
||||
run_results.non_aggregates.push_back(report);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace
|
||||
|
||||
RunResults RunBenchmark(
|
||||
const benchmark::internal::BenchmarkInstance& b,
|
||||
std::vector<BenchmarkReporter::Run>* complexity_reports) {
|
||||
internal::BenchmarkRunner r(b, complexity_reports);
|
||||
return r.get_results();
|
||||
}
|
||||
|
||||
} // end namespace internal
|
||||
|
||||
} // end namespace benchmark
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BENCHMARK_RUNNER_H_
|
||||
#define BENCHMARK_RUNNER_H_
|
||||
|
||||
#include "benchmark_api_internal.h"
|
||||
#include "internal_macros.h"
|
||||
|
||||
DECLARE_double(benchmark_min_time);
|
||||
|
||||
DECLARE_int32(benchmark_repetitions);
|
||||
|
||||
DECLARE_bool(benchmark_report_aggregates_only);
|
||||
|
||||
DECLARE_bool(benchmark_display_aggregates_only);
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
namespace internal {
|
||||
|
||||
extern MemoryManager* memory_manager;
|
||||
|
||||
struct RunResults {
|
||||
std::vector<BenchmarkReporter::Run> non_aggregates;
|
||||
std::vector<BenchmarkReporter::Run> aggregates_only;
|
||||
|
||||
bool display_report_aggregates_only = false;
|
||||
bool file_report_aggregates_only = false;
|
||||
};
|
||||
|
||||
RunResults RunBenchmark(
|
||||
const benchmark::internal::BenchmarkInstance& b,
|
||||
std::vector<BenchmarkReporter::Run>* complexity_reports);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // end namespace benchmark
|
||||
|
||||
#endif // BENCHMARK_RUNNER_H_
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "commandlineflags.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
@ -21,6 +22,8 @@
|
|||
#include <limits>
|
||||
|
||||
namespace benchmark {
|
||||
namespace {
|
||||
|
||||
// Parses 'str' for a 32-bit signed integer. If successful, writes
|
||||
// the result to *value and returns true; otherwise leaves *value
|
||||
// unchanged and returns false.
|
||||
|
@ -88,44 +91,42 @@ static std::string FlagToEnvVar(const char* flag) {
|
|||
return "BENCHMARK_" + env_var;
|
||||
}
|
||||
|
||||
// Reads and returns the Boolean environment variable corresponding to
|
||||
// the given flag; if it's not set, returns default_value.
|
||||
//
|
||||
// The value is considered true iff it's not "0".
|
||||
bool BoolFromEnv(const char* flag, bool default_value) {
|
||||
} // namespace
|
||||
|
||||
bool BoolFromEnv(const char* flag, bool default_val) {
|
||||
const std::string env_var = FlagToEnvVar(flag);
|
||||
const char* const string_value = getenv(env_var.c_str());
|
||||
return string_value == nullptr ? default_value
|
||||
: strcmp(string_value, "0") != 0;
|
||||
const char* const value_str = getenv(env_var.c_str());
|
||||
return value_str == nullptr ? default_val : IsTruthyFlagValue(value_str);
|
||||
}
|
||||
|
||||
// Reads and returns a 32-bit integer stored in the environment
|
||||
// variable corresponding to the given flag; if it isn't set or
|
||||
// doesn't represent a valid 32-bit integer, returns default_value.
|
||||
int32_t Int32FromEnv(const char* flag, int32_t default_value) {
|
||||
int32_t Int32FromEnv(const char* flag, int32_t default_val) {
|
||||
const std::string env_var = FlagToEnvVar(flag);
|
||||
const char* const string_value = getenv(env_var.c_str());
|
||||
if (string_value == nullptr) {
|
||||
// The environment variable is not set.
|
||||
return default_value;
|
||||
const char* const value_str = getenv(env_var.c_str());
|
||||
int32_t value = default_val;
|
||||
if (value_str == nullptr ||
|
||||
!ParseInt32(std::string("Environment variable ") + env_var, value_str,
|
||||
&value)) {
|
||||
return default_val;
|
||||
}
|
||||
|
||||
int32_t result = default_value;
|
||||
if (!ParseInt32(std::string("Environment variable ") + env_var, string_value,
|
||||
&result)) {
|
||||
std::cout << "The default value " << default_value << " is used.\n";
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return result;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Reads and returns the string environment variable corresponding to
|
||||
// the given flag; if it's not set, returns default_value.
|
||||
const char* StringFromEnv(const char* flag, const char* default_value) {
|
||||
double DoubleFromEnv(const char* flag, double default_val) {
|
||||
const std::string env_var = FlagToEnvVar(flag);
|
||||
const char* const value_str = getenv(env_var.c_str());
|
||||
double value = default_val;
|
||||
if (value_str == nullptr ||
|
||||
!ParseDouble(std::string("Environment variable ") + env_var, value_str,
|
||||
&value)) {
|
||||
return default_val;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const char* StringFromEnv(const char* flag, const char* default_val) {
|
||||
const std::string env_var = FlagToEnvVar(flag);
|
||||
const char* const value = getenv(env_var.c_str());
|
||||
return value == nullptr ? default_value : value;
|
||||
return value == nullptr ? default_val : value;
|
||||
}
|
||||
|
||||
// Parses a string as a command line flag. The string should have
|
||||
|
@ -210,9 +211,18 @@ bool IsFlag(const char* str, const char* flag) {
|
|||
}
|
||||
|
||||
bool IsTruthyFlagValue(const std::string& value) {
|
||||
if (value.empty()) return true;
|
||||
char ch = value[0];
|
||||
return isalnum(ch) &&
|
||||
!(ch == '0' || ch == 'f' || ch == 'F' || ch == 'n' || ch == 'N');
|
||||
if (value.size() == 1) {
|
||||
char v = value[0];
|
||||
return isalnum(v) &&
|
||||
!(v == '0' || v == 'f' || v == 'F' || v == 'n' || v == 'N');
|
||||
} else if (!value.empty()) {
|
||||
std::string value_lower(value);
|
||||
std::transform(value_lower.begin(), value_lower.end(), value_lower.begin(),
|
||||
[](char c) { return static_cast<char>(::tolower(c)); });
|
||||
return !(value_lower == "false" || value_lower == "no" ||
|
||||
value_lower == "off");
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
|
||||
} // end namespace benchmark
|
||||
|
|
|
@ -10,29 +10,51 @@
|
|||
// Macros for declaring flags.
|
||||
#define DECLARE_bool(name) extern bool FLAG(name)
|
||||
#define DECLARE_int32(name) extern int32_t FLAG(name)
|
||||
#define DECLARE_int64(name) extern int64_t FLAG(name)
|
||||
#define DECLARE_double(name) extern double FLAG(name)
|
||||
#define DECLARE_string(name) extern std::string FLAG(name)
|
||||
|
||||
// Macros for defining flags.
|
||||
#define DEFINE_bool(name, default_val, doc) bool FLAG(name) = (default_val)
|
||||
#define DEFINE_int32(name, default_val, doc) int32_t FLAG(name) = (default_val)
|
||||
#define DEFINE_int64(name, default_val, doc) int64_t FLAG(name) = (default_val)
|
||||
#define DEFINE_double(name, default_val, doc) double FLAG(name) = (default_val)
|
||||
#define DEFINE_string(name, default_val, doc) \
|
||||
std::string FLAG(name) = (default_val)
|
||||
#define DEFINE_bool(name, default_val) \
|
||||
bool FLAG(name) = \
|
||||
benchmark::BoolFromEnv(#name, default_val)
|
||||
#define DEFINE_int32(name, default_val) \
|
||||
int32_t FLAG(name) = \
|
||||
benchmark::Int32FromEnv(#name, default_val)
|
||||
#define DEFINE_double(name, default_val) \
|
||||
double FLAG(name) = \
|
||||
benchmark::DoubleFromEnv(#name, default_val)
|
||||
#define DEFINE_string(name, default_val) \
|
||||
std::string FLAG(name) = \
|
||||
benchmark::StringFromEnv(#name, default_val)
|
||||
|
||||
namespace benchmark {
|
||||
// Parses 'str' for a 32-bit signed integer. If successful, writes the result
|
||||
// to *value and returns true; otherwise leaves *value unchanged and returns
|
||||
// false.
|
||||
bool ParseInt32(const std::string& src_text, const char* str, int32_t* value);
|
||||
|
||||
// Parses a bool/Int32/string from the environment variable
|
||||
// corresponding to the given Google Test flag.
|
||||
// Parses a bool from the environment variable
|
||||
// corresponding to the given flag.
|
||||
//
|
||||
// If the variable exists, returns IsTruthyFlagValue() value; if not,
|
||||
// returns the given default value.
|
||||
bool BoolFromEnv(const char* flag, bool default_val);
|
||||
|
||||
// Parses an Int32 from the environment variable
|
||||
// corresponding to the given flag.
|
||||
//
|
||||
// If the variable exists, returns ParseInt32() value; if not, returns
|
||||
// the given default value.
|
||||
int32_t Int32FromEnv(const char* flag, int32_t default_val);
|
||||
|
||||
// Parses an Double from the environment variable
|
||||
// corresponding to the given flag.
|
||||
//
|
||||
// If the variable exists, returns ParseDouble(); if not, returns
|
||||
// the given default value.
|
||||
double DoubleFromEnv(const char* flag, double default_val);
|
||||
|
||||
// Parses a string from the environment variable
|
||||
// corresponding to the given flag.
|
||||
//
|
||||
// If variable exists, returns its value; if not, returns
|
||||
// the given default value.
|
||||
const char* StringFromEnv(const char* flag, const char* default_val);
|
||||
|
||||
// Parses a string for a bool flag, in the form of either
|
||||
|
@ -71,9 +93,11 @@ bool ParseStringFlag(const char* str, const char* flag, std::string* value);
|
|||
bool IsFlag(const char* str, const char* flag);
|
||||
|
||||
// Returns true unless value starts with one of: '0', 'f', 'F', 'n' or 'N', or
|
||||
// some non-alphanumeric character. As a special case, also returns true if
|
||||
// value is the empty string.
|
||||
// some non-alphanumeric character. Also returns false if the value matches
|
||||
// one of 'no', 'false', 'off' (case-insensitive). As a special case, also
|
||||
// returns true if value is the empty string.
|
||||
bool IsTruthyFlagValue(const std::string& value);
|
||||
|
||||
} // end namespace benchmark
|
||||
|
||||
#endif // BENCHMARK_COMMANDLINEFLAGS_H_
|
||||
|
|
|
@ -29,20 +29,23 @@ BigOFunc* FittingCurve(BigO complexity) {
|
|||
static const double kLog2E = 1.44269504088896340736;
|
||||
switch (complexity) {
|
||||
case oN:
|
||||
return [](int64_t n) -> double { return static_cast<double>(n); };
|
||||
return [](IterationCount n) -> double { return static_cast<double>(n); };
|
||||
case oNSquared:
|
||||
return [](int64_t n) -> double { return std::pow(n, 2); };
|
||||
return [](IterationCount n) -> double { return std::pow(n, 2); };
|
||||
case oNCubed:
|
||||
return [](int64_t n) -> double { return std::pow(n, 3); };
|
||||
return [](IterationCount n) -> double { return std::pow(n, 3); };
|
||||
case oLogN:
|
||||
/* Note: can't use log2 because Android's GNU STL lacks it */
|
||||
return [](int64_t n) { return kLog2E * log(n); };
|
||||
return
|
||||
[](IterationCount n) { return kLog2E * log(static_cast<double>(n)); };
|
||||
case oNLogN:
|
||||
/* Note: can't use log2 because Android's GNU STL lacks it */
|
||||
return [](int64_t n) { return kLog2E * n * log(n); };
|
||||
return [](IterationCount n) {
|
||||
return kLog2E * n * log(static_cast<double>(n));
|
||||
};
|
||||
case o1:
|
||||
default:
|
||||
return [](int64_t) { return 1.0; };
|
||||
return [](IterationCount) { return 1.0; };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,8 +76,8 @@ std::string GetBigOString(BigO complexity) {
|
|||
// - time : Vector containing the times for the benchmark tests.
|
||||
// - fitting_curve : lambda expression (e.g. [](int64_t n) {return n; };).
|
||||
|
||||
// For a deeper explanation on the algorithm logic, look the README file at
|
||||
// http://github.com/ismaelJimenez/Minimal-Cpp-Least-Squared-Fit
|
||||
// For a deeper explanation on the algorithm logic, please refer to
|
||||
// https://en.wikipedia.org/wiki/Least_squares#Least_squares,_regression_analysis_and_statistics
|
||||
|
||||
LeastSq MinimalLeastSq(const std::vector<int64_t>& n,
|
||||
const std::vector<double>& time,
|
||||
|
@ -182,12 +185,20 @@ std::vector<BenchmarkReporter::Run> ComputeBigO(
|
|||
result_cpu = MinimalLeastSq(n, cpu_time, reports[0].complexity);
|
||||
result_real = MinimalLeastSq(n, real_time, result_cpu.complexity);
|
||||
}
|
||||
std::string benchmark_name =
|
||||
reports[0].benchmark_name.substr(0, reports[0].benchmark_name.find('/'));
|
||||
|
||||
// Drop the 'args' when reporting complexity.
|
||||
auto run_name = reports[0].run_name;
|
||||
run_name.args.clear();
|
||||
|
||||
// Get the data from the accumulator to BenchmarkReporter::Run's.
|
||||
Run big_o;
|
||||
big_o.benchmark_name = benchmark_name + "_BigO";
|
||||
big_o.run_name = run_name;
|
||||
big_o.run_type = BenchmarkReporter::Run::RT_Aggregate;
|
||||
big_o.repetitions = reports[0].repetitions;
|
||||
big_o.repetition_index = Run::no_repetition_index;
|
||||
big_o.threads = reports[0].threads;
|
||||
big_o.aggregate_name = "BigO";
|
||||
big_o.report_label = reports[0].report_label;
|
||||
big_o.iterations = 0;
|
||||
big_o.real_accumulated_time = result_real.coef;
|
||||
big_o.cpu_accumulated_time = result_cpu.coef;
|
||||
|
@ -203,10 +214,14 @@ std::vector<BenchmarkReporter::Run> ComputeBigO(
|
|||
|
||||
// Only add label to mean/stddev if it is same for all runs
|
||||
Run rms;
|
||||
big_o.report_label = reports[0].report_label;
|
||||
rms.benchmark_name = benchmark_name + "_RMS";
|
||||
rms.run_name = run_name;
|
||||
rms.run_type = BenchmarkReporter::Run::RT_Aggregate;
|
||||
rms.aggregate_name = "RMS";
|
||||
rms.report_label = big_o.report_label;
|
||||
rms.iterations = 0;
|
||||
rms.repetition_index = Run::no_repetition_index;
|
||||
rms.repetitions = reports[0].repetitions;
|
||||
rms.threads = reports[0].threads;
|
||||
rms.real_accumulated_time = result_real.rms / multiplier;
|
||||
rms.cpu_accumulated_time = result_cpu.rms / multiplier;
|
||||
rms.report_rms = true;
|
||||
|
|
|
@ -12,21 +12,21 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "complexity.h"
|
||||
#include "counter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "check.h"
|
||||
#include "colorprint.h"
|
||||
#include "commandlineflags.h"
|
||||
#include "complexity.h"
|
||||
#include "counter.h"
|
||||
#include "internal_macros.h"
|
||||
#include "string_util.h"
|
||||
#include "timers.h"
|
||||
|
@ -53,7 +53,7 @@ bool ConsoleReporter::ReportContext(const Context& context) {
|
|||
}
|
||||
|
||||
void ConsoleReporter::PrintHeader(const Run& run) {
|
||||
std::string str = FormatString("%-*s %13s %13s %10s", static_cast<int>(name_field_width_),
|
||||
std::string str = FormatString("%-*s %13s %15s %12s", static_cast<int>(name_field_width_),
|
||||
"Benchmark", "Time", "CPU", "Iterations");
|
||||
if(!run.counters.empty()) {
|
||||
if(output_options_ & OO_Tabular) {
|
||||
|
@ -64,9 +64,8 @@ void ConsoleReporter::PrintHeader(const Run& run) {
|
|||
str += " UserCounters...";
|
||||
}
|
||||
}
|
||||
str += "\n";
|
||||
std::string line = std::string(str.length(), '-');
|
||||
GetOutputStream() << line << "\n" << str << line << "\n";
|
||||
GetOutputStream() << line << "\n" << str << "\n" << line << "\n";
|
||||
}
|
||||
|
||||
void ConsoleReporter::ReportRuns(const std::vector<Run>& reports) {
|
||||
|
@ -98,6 +97,21 @@ static void IgnoreColorPrint(std::ostream& out, LogColor, const char* fmt,
|
|||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
static std::string FormatTime(double time) {
|
||||
// Align decimal places...
|
||||
if (time < 1.0) {
|
||||
return FormatString("%10.3f", time);
|
||||
}
|
||||
if (time < 10.0) {
|
||||
return FormatString("%10.2f", time);
|
||||
}
|
||||
if (time < 100.0) {
|
||||
return FormatString("%10.1f", time);
|
||||
}
|
||||
return FormatString("%10.0f", time);
|
||||
}
|
||||
|
||||
void ConsoleReporter::PrintRunData(const Run& result) {
|
||||
typedef void(PrinterFn)(std::ostream&, LogColor, const char*, ...);
|
||||
auto& Out = GetOutputStream();
|
||||
|
@ -106,7 +120,7 @@ void ConsoleReporter::PrintRunData(const Run& result) {
|
|||
auto name_color =
|
||||
(result.report_big_o || result.report_rms) ? COLOR_BLUE : COLOR_GREEN;
|
||||
printer(Out, name_color, "%-*s ", name_field_width_,
|
||||
result.benchmark_name.c_str());
|
||||
result.benchmark_name().c_str());
|
||||
|
||||
if (result.error_occurred) {
|
||||
printer(Out, COLOR_RED, "ERROR OCCURRED: \'%s\'",
|
||||
|
@ -114,33 +128,24 @@ void ConsoleReporter::PrintRunData(const Run& result) {
|
|||
printer(Out, COLOR_DEFAULT, "\n");
|
||||
return;
|
||||
}
|
||||
// Format bytes per second
|
||||
std::string rate;
|
||||
if (result.bytes_per_second > 0) {
|
||||
rate = StrCat(" ", HumanReadableNumber(result.bytes_per_second), "B/s");
|
||||
}
|
||||
|
||||
// Format items per second
|
||||
std::string items;
|
||||
if (result.items_per_second > 0) {
|
||||
items =
|
||||
StrCat(" ", HumanReadableNumber(result.items_per_second), " items/s");
|
||||
}
|
||||
|
||||
const double real_time = result.GetAdjustedRealTime();
|
||||
const double cpu_time = result.GetAdjustedCPUTime();
|
||||
const std::string real_time_str = FormatTime(real_time);
|
||||
const std::string cpu_time_str = FormatTime(cpu_time);
|
||||
|
||||
|
||||
if (result.report_big_o) {
|
||||
std::string big_o = GetBigOString(result.complexity);
|
||||
printer(Out, COLOR_YELLOW, "%10.2f %s %10.2f %s ", real_time, big_o.c_str(),
|
||||
printer(Out, COLOR_YELLOW, "%10.2f %-4s %10.2f %-4s ", real_time, big_o.c_str(),
|
||||
cpu_time, big_o.c_str());
|
||||
} else if (result.report_rms) {
|
||||
printer(Out, COLOR_YELLOW, "%10.0f %% %10.0f %% ", real_time * 100,
|
||||
cpu_time * 100);
|
||||
printer(Out, COLOR_YELLOW, "%10.0f %-4s %10.0f %-4s ", real_time * 100, "%",
|
||||
cpu_time * 100, "%");
|
||||
} else {
|
||||
const char* timeLabel = GetTimeUnitString(result.time_unit);
|
||||
printer(Out, COLOR_YELLOW, "%10.0f %s %10.0f %s ", real_time, timeLabel,
|
||||
cpu_time, timeLabel);
|
||||
printer(Out, COLOR_YELLOW, "%s %-4s %s %-4s ", real_time_str.c_str(), timeLabel,
|
||||
cpu_time_str.c_str(), timeLabel);
|
||||
}
|
||||
|
||||
if (!result.report_big_o && !result.report_rms) {
|
||||
|
@ -150,28 +155,18 @@ void ConsoleReporter::PrintRunData(const Run& result) {
|
|||
for (auto& c : result.counters) {
|
||||
const std::size_t cNameLen = std::max(std::string::size_type(10),
|
||||
c.first.length());
|
||||
auto const& s = HumanReadableNumber(c.second.value, 1000);
|
||||
auto const& s = HumanReadableNumber(c.second.value, c.second.oneK);
|
||||
const char* unit = "";
|
||||
if (c.second.flags & Counter::kIsRate)
|
||||
unit = (c.second.flags & Counter::kInvert) ? "s" : "/s";
|
||||
if (output_options_ & OO_Tabular) {
|
||||
if (c.second.flags & Counter::kIsRate) {
|
||||
printer(Out, COLOR_DEFAULT, " %*s/s", cNameLen - 2, s.c_str());
|
||||
} else {
|
||||
printer(Out, COLOR_DEFAULT, " %*s", cNameLen, s.c_str());
|
||||
}
|
||||
} else {
|
||||
const char* unit = (c.second.flags & Counter::kIsRate) ? "/s" : "";
|
||||
printer(Out, COLOR_DEFAULT, " %s=%s%s", c.first.c_str(), s.c_str(),
|
||||
printer(Out, COLOR_DEFAULT, " %*s%s", cNameLen - strlen(unit), s.c_str(),
|
||||
unit);
|
||||
} else {
|
||||
printer(Out, COLOR_DEFAULT, " %s=%s%s", c.first.c_str(), s.c_str(), unit);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rate.empty()) {
|
||||
printer(Out, COLOR_DEFAULT, " %*s", 13, rate.c_str());
|
||||
}
|
||||
|
||||
if (!items.empty()) {
|
||||
printer(Out, COLOR_DEFAULT, " %*s", 18, items.c_str());
|
||||
}
|
||||
|
||||
if (!result.report_label.empty()) {
|
||||
printer(Out, COLOR_DEFAULT, " %s", result.report_label.c_str());
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
namespace benchmark {
|
||||
namespace internal {
|
||||
|
||||
double Finish(Counter const& c, double cpu_time, double num_threads) {
|
||||
double Finish(Counter const& c, IterationCount iterations, double cpu_time,
|
||||
double num_threads) {
|
||||
double v = c.value;
|
||||
if (c.flags & Counter::kIsRate) {
|
||||
v /= cpu_time;
|
||||
|
@ -25,12 +26,23 @@ double Finish(Counter const& c, double cpu_time, double num_threads) {
|
|||
if (c.flags & Counter::kAvgThreads) {
|
||||
v /= num_threads;
|
||||
}
|
||||
if (c.flags & Counter::kIsIterationInvariant) {
|
||||
v *= iterations;
|
||||
}
|
||||
if (c.flags & Counter::kAvgIterations) {
|
||||
v /= iterations;
|
||||
}
|
||||
|
||||
if (c.flags & Counter::kInvert) { // Invert is *always* last.
|
||||
v = 1.0 / v;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void Finish(UserCounters* l, double cpu_time, double num_threads) {
|
||||
void Finish(UserCounters* l, IterationCount iterations, double cpu_time,
|
||||
double num_threads) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,15 +12,21 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef BENCHMARK_COUNTER_H_
|
||||
#define BENCHMARK_COUNTER_H_
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
// these counter-related functions are hidden to reduce API surface.
|
||||
namespace internal {
|
||||
void Finish(UserCounters* l, double time, double num_threads);
|
||||
void Finish(UserCounters* l, IterationCount iterations, double time,
|
||||
double num_threads);
|
||||
void Increment(UserCounters* l, UserCounters const& r);
|
||||
bool SameNames(UserCounters const& l, UserCounters const& r);
|
||||
} // end namespace internal
|
||||
|
||||
} // end namespace benchmark
|
||||
|
||||
#endif // BENCHMARK_COUNTER_H_
|
||||
|
|
|
@ -37,6 +37,18 @@ std::vector<std::string> elements = {
|
|||
"error_occurred", "error_message"};
|
||||
} // namespace
|
||||
|
||||
std::string CsvEscape(const std::string & s) {
|
||||
std::string tmp;
|
||||
tmp.reserve(s.size() + 2);
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '"' : tmp += "\"\""; break;
|
||||
default : tmp += c; break;
|
||||
}
|
||||
}
|
||||
return '"' + tmp + '"';
|
||||
}
|
||||
|
||||
bool CSVReporter::ReportContext(const Context& context) {
|
||||
PrintBasicContext(&GetErrorStream(), context);
|
||||
return true;
|
||||
|
@ -49,6 +61,8 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
|
|||
// save the names of all the user counters
|
||||
for (const auto& run : reports) {
|
||||
for (const auto& cnt : run.counters) {
|
||||
if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second")
|
||||
continue;
|
||||
user_counter_names_.insert(cnt.first);
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +83,8 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
|
|||
// check that all the current counters are saved in the name set
|
||||
for (const auto& run : reports) {
|
||||
for (const auto& cnt : run.counters) {
|
||||
if (cnt.first == "bytes_per_second" || cnt.first == "items_per_second")
|
||||
continue;
|
||||
CHECK(user_counter_names_.find(cnt.first) != user_counter_names_.end())
|
||||
<< "All counters must be present in each run. "
|
||||
<< "Counter named \"" << cnt.first
|
||||
|
@ -85,18 +101,11 @@ void CSVReporter::ReportRuns(const std::vector<Run>& reports) {
|
|||
|
||||
void CSVReporter::PrintRunData(const Run& run) {
|
||||
std::ostream& Out = GetOutputStream();
|
||||
|
||||
// Field with embedded double-quote characters must be doubled and the field
|
||||
// delimited with double-quotes.
|
||||
std::string name = run.benchmark_name;
|
||||
ReplaceAll(&name, "\"", "\"\"");
|
||||
Out << '"' << name << "\",";
|
||||
Out << CsvEscape(run.benchmark_name()) << ",";
|
||||
if (run.error_occurred) {
|
||||
Out << std::string(elements.size() - 3, ',');
|
||||
Out << "true,";
|
||||
std::string msg = run.error_message;
|
||||
ReplaceAll(&msg, "\"", "\"\"");
|
||||
Out << '"' << msg << "\"\n";
|
||||
Out << CsvEscape(run.error_message) << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -117,20 +126,16 @@ void CSVReporter::PrintRunData(const Run& run) {
|
|||
}
|
||||
Out << ",";
|
||||
|
||||
if (run.bytes_per_second > 0.0) {
|
||||
Out << run.bytes_per_second;
|
||||
if (run.counters.find("bytes_per_second") != run.counters.end()) {
|
||||
Out << run.counters.at("bytes_per_second");
|
||||
}
|
||||
Out << ",";
|
||||
if (run.items_per_second > 0.0) {
|
||||
Out << run.items_per_second;
|
||||
if (run.counters.find("items_per_second") != run.counters.end()) {
|
||||
Out << run.counters.at("items_per_second");
|
||||
}
|
||||
Out << ",";
|
||||
if (!run.report_label.empty()) {
|
||||
// Field with embedded double-quote characters must be doubled and the field
|
||||
// delimited with double-quotes.
|
||||
std::string label = run.report_label;
|
||||
ReplaceAll(&label, "\"", "\"\"");
|
||||
Out << "\"" << label << "\"";
|
||||
Out << CsvEscape(run.report_label);
|
||||
}
|
||||
Out << ",,"; // for error_occurred and error_message
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ extern "C" uint64_t __rdtsc();
|
|||
#pragma intrinsic(__rdtsc)
|
||||
#endif
|
||||
|
||||
#ifndef BENCHMARK_OS_WINDOWS
|
||||
#if !defined(BENCHMARK_OS_WINDOWS) || defined(BENCHMARK_OS_MINGW)
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
@ -84,13 +84,21 @@ inline BENCHMARK_ALWAYS_INLINE int64_t Now() {
|
|||
return (high << 32) | low;
|
||||
#elif defined(__powerpc__) || defined(__ppc__)
|
||||
// This returns a time-base, which is not always precisely a cycle-count.
|
||||
int64_t tbl, tbu0, tbu1;
|
||||
asm("mftbu %0" : "=r"(tbu0));
|
||||
asm("mftb %0" : "=r"(tbl));
|
||||
asm("mftbu %0" : "=r"(tbu1));
|
||||
tbl &= -static_cast<int64_t>(tbu0 == tbu1);
|
||||
// high 32 bits in tbu1; low 32 bits in tbl (tbu0 is garbage)
|
||||
return (tbu1 << 32) | tbl;
|
||||
#if defined(__powerpc64__) || defined(__ppc64__)
|
||||
int64_t tb;
|
||||
asm volatile("mfspr %0, 268" : "=r"(tb));
|
||||
return tb;
|
||||
#else
|
||||
uint32_t tbl, tbu0, tbu1;
|
||||
asm volatile(
|
||||
"mftbu %0\n"
|
||||
"mftbl %1\n"
|
||||
"mftbu %2"
|
||||
: "=r"(tbu0), "=r"(tbl), "=r"(tbu1));
|
||||
tbl &= -static_cast<int32_t>(tbu0 == tbu1);
|
||||
// high 32 bits in tbu1; low 32 bits in tbl (tbu0 is no longer needed)
|
||||
return (static_cast<uint64_t>(tbu1) << 32) | tbl;
|
||||
#endif
|
||||
#elif defined(__sparc__)
|
||||
int64_t tick;
|
||||
asm(".byte 0x83, 0x41, 0x00, 0x00");
|
||||
|
@ -164,6 +172,27 @@ inline BENCHMARK_ALWAYS_INLINE int64_t Now() {
|
|||
uint64_t tsc;
|
||||
asm("stck %0" : "=Q"(tsc) : : "cc");
|
||||
return tsc;
|
||||
#elif defined(__riscv) // RISC-V
|
||||
// Use RDCYCLE (and RDCYCLEH on riscv32)
|
||||
#if __riscv_xlen == 32
|
||||
uint32_t cycles_lo, cycles_hi0, cycles_hi1;
|
||||
// This asm also includes the PowerPC overflow handling strategy, as above.
|
||||
// Implemented in assembly because Clang insisted on branching.
|
||||
asm volatile(
|
||||
"rdcycleh %0\n"
|
||||
"rdcycle %1\n"
|
||||
"rdcycleh %2\n"
|
||||
"sub %0, %0, %2\n"
|
||||
"seqz %0, %0\n"
|
||||
"sub %0, zero, %0\n"
|
||||
"and %1, %1, %0\n"
|
||||
: "=r"(cycles_hi0), "=r"(cycles_lo), "=r"(cycles_hi1));
|
||||
return (static_cast<uint64_t>(cycles_hi1) << 32) | cycles_lo;
|
||||
#else
|
||||
uint64_t cycles;
|
||||
asm volatile("rdcycle %0" : "=r"(cycles));
|
||||
return cycles;
|
||||
#endif
|
||||
#else
|
||||
// The soft failover to a generic implementation is automatic only for ARM.
|
||||
// For other platforms the developer is expected to make an attempt to create
|
||||
|
|
|
@ -11,9 +11,6 @@
|
|||
#ifndef __has_feature
|
||||
#define __has_feature(x) 0
|
||||
#endif
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#if !defined(COMPILER_CLANG)
|
||||
|
@ -43,6 +40,9 @@
|
|||
#define BENCHMARK_OS_CYGWIN 1
|
||||
#elif defined(_WIN32)
|
||||
#define BENCHMARK_OS_WINDOWS 1
|
||||
#if defined(__MINGW32__)
|
||||
#define BENCHMARK_OS_MINGW 1
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#define BENCHMARK_OS_APPLE 1
|
||||
#include "TargetConditionals.h"
|
||||
|
@ -70,6 +70,8 @@
|
|||
#define BENCHMARK_OS_FUCHSIA 1
|
||||
#elif defined (__SVR4) && defined (__sun)
|
||||
#define BENCHMARK_OS_SOLARIS 1
|
||||
#elif defined(__QNX__)
|
||||
#define BENCHMARK_OS_QNX 1
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) && defined(__GLIBCXX__)
|
||||
|
@ -87,14 +89,6 @@
|
|||
#define BENCHMARK_MAYBE_UNUSED
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_GCC) || __has_builtin(__builtin_unreachable)
|
||||
#define BENCHMARK_UNREACHABLE() __builtin_unreachable()
|
||||
#elif defined(COMPILER_MSVC)
|
||||
#define BENCHMARK_UNREACHABLE() __assume(false)
|
||||
#else
|
||||
#define BENCHMARK_UNREACHABLE() ((void)0)
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
|
||||
#endif // BENCHMARK_INTERNAL_MACROS_H_
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "complexity.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iomanip> // for setprecision
|
||||
#include <iostream>
|
||||
|
@ -31,36 +32,67 @@ namespace benchmark {
|
|||
|
||||
namespace {
|
||||
|
||||
std::string StrEscape(const std::string & s) {
|
||||
std::string tmp;
|
||||
tmp.reserve(s.size());
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '\b': tmp += "\\b"; break;
|
||||
case '\f': tmp += "\\f"; break;
|
||||
case '\n': tmp += "\\n"; break;
|
||||
case '\r': tmp += "\\r"; break;
|
||||
case '\t': tmp += "\\t"; break;
|
||||
case '\\': tmp += "\\\\"; break;
|
||||
case '"' : tmp += "\\\""; break;
|
||||
default : tmp += c; break;
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, std::string const& value) {
|
||||
return StrFormat("\"%s\": \"%s\"", key.c_str(), value.c_str());
|
||||
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, const char* value) {
|
||||
return StrFormat("\"%s\": \"%s\"", key.c_str(), value);
|
||||
return StrFormat("\"%s\": \"%s\"", StrEscape(key).c_str(), StrEscape(value).c_str());
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, bool value) {
|
||||
return StrFormat("\"%s\": %s", key.c_str(), value ? "true" : "false");
|
||||
return StrFormat("\"%s\": %s", StrEscape(key).c_str(), value ? "true" : "false");
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, int64_t value) {
|
||||
std::stringstream ss;
|
||||
ss << '"' << key << "\": " << value;
|
||||
ss << '"' << StrEscape(key) << "\": " << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, IterationCount value) {
|
||||
std::stringstream ss;
|
||||
ss << '"' << StrEscape(key) << "\": " << value;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string FormatKV(std::string const& key, double value) {
|
||||
std::stringstream ss;
|
||||
ss << '"' << key << "\": ";
|
||||
ss << '"' << StrEscape(key) << "\": ";
|
||||
|
||||
const auto max_digits10 = std::numeric_limits<decltype(value)>::max_digits10;
|
||||
const auto max_fractional_digits10 = max_digits10 - 1;
|
||||
|
||||
ss << std::scientific << std::setprecision(max_fractional_digits10) << value;
|
||||
if (std::isnan(value))
|
||||
ss << (value < 0 ? "-" : "") << "NaN";
|
||||
else if (std::isinf(value))
|
||||
ss << (value < 0 ? "-" : "") << "Infinity";
|
||||
else {
|
||||
const auto max_digits10 =
|
||||
std::numeric_limits<decltype(value)>::max_digits10;
|
||||
const auto max_fractional_digits10 = max_digits10 - 1;
|
||||
ss << std::scientific << std::setprecision(max_fractional_digits10)
|
||||
<< value;
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int64_t RoundDouble(double v) { return static_cast<int64_t>(v + 0.5); }
|
||||
int64_t RoundDouble(double v) { return std::lround(v); }
|
||||
|
||||
} // end namespace
|
||||
|
||||
|
@ -77,6 +109,8 @@ bool JSONReporter::ReportContext(const Context& context) {
|
|||
std::string walltime_value = LocalDateTimeString();
|
||||
out << indent << FormatKV("date", walltime_value) << ",\n";
|
||||
|
||||
out << indent << FormatKV("host_name", context.sys_info.name) << ",\n";
|
||||
|
||||
if (Context::executable_name) {
|
||||
out << indent << FormatKV("executable", Context::executable_name) << ",\n";
|
||||
}
|
||||
|
@ -101,7 +135,7 @@ bool JSONReporter::ReportContext(const Context& context) {
|
|||
out << cache_indent << FormatKV("level", static_cast<int64_t>(CI.level))
|
||||
<< ",\n";
|
||||
out << cache_indent
|
||||
<< FormatKV("size", static_cast<int64_t>(CI.size) * 1000u) << ",\n";
|
||||
<< FormatKV("size", static_cast<int64_t>(CI.size)) << ",\n";
|
||||
out << cache_indent
|
||||
<< FormatKV("num_sharing", static_cast<int64_t>(CI.num_sharing))
|
||||
<< "\n";
|
||||
|
@ -111,6 +145,12 @@ bool JSONReporter::ReportContext(const Context& context) {
|
|||
}
|
||||
indent = std::string(4, ' ');
|
||||
out << indent << "],\n";
|
||||
out << indent << "\"load_avg\": [";
|
||||
for (auto it = info.load_avg.begin(); it != info.load_avg.end();) {
|
||||
out << *it++;
|
||||
if (it != info.load_avg.end()) out << ",";
|
||||
}
|
||||
out << "],\n";
|
||||
|
||||
#if defined(NDEBUG)
|
||||
const char build_type[] = "release";
|
||||
|
@ -154,7 +194,26 @@ void JSONReporter::Finalize() {
|
|||
void JSONReporter::PrintRunData(Run const& run) {
|
||||
std::string indent(6, ' ');
|
||||
std::ostream& out = GetOutputStream();
|
||||
out << indent << FormatKV("name", run.benchmark_name) << ",\n";
|
||||
out << indent << FormatKV("name", run.benchmark_name()) << ",\n";
|
||||
out << indent << FormatKV("run_name", run.run_name.str()) << ",\n";
|
||||
out << indent << FormatKV("run_type", [&run]() -> const char* {
|
||||
switch (run.run_type) {
|
||||
case BenchmarkReporter::Run::RT_Iteration:
|
||||
return "iteration";
|
||||
case BenchmarkReporter::Run::RT_Aggregate:
|
||||
return "aggregate";
|
||||
}
|
||||
BENCHMARK_UNREACHABLE();
|
||||
}()) << ",\n";
|
||||
out << indent << FormatKV("repetitions", run.repetitions) << ",\n";
|
||||
if (run.run_type != BenchmarkReporter::Run::RT_Aggregate) {
|
||||
out << indent << FormatKV("repetition_index", run.repetition_index)
|
||||
<< ",\n";
|
||||
}
|
||||
out << indent << FormatKV("threads", run.threads) << ",\n";
|
||||
if (run.run_type == BenchmarkReporter::Run::RT_Aggregate) {
|
||||
out << indent << FormatKV("aggregate_name", run.aggregate_name) << ",\n";
|
||||
}
|
||||
if (run.error_occurred) {
|
||||
out << indent << FormatKV("error_occurred", run.error_occurred) << ",\n";
|
||||
out << indent << FormatKV("error_message", run.error_message) << ",\n";
|
||||
|
@ -175,17 +234,16 @@ void JSONReporter::PrintRunData(Run const& run) {
|
|||
} else if (run.report_rms) {
|
||||
out << indent << FormatKV("rms", run.GetAdjustedCPUTime());
|
||||
}
|
||||
if (run.bytes_per_second > 0.0) {
|
||||
out << ",\n"
|
||||
<< indent << FormatKV("bytes_per_second", run.bytes_per_second);
|
||||
}
|
||||
if (run.items_per_second > 0.0) {
|
||||
out << ",\n"
|
||||
<< indent << FormatKV("items_per_second", run.items_per_second);
|
||||
}
|
||||
|
||||
for (auto& c : run.counters) {
|
||||
out << ",\n" << indent << FormatKV(c.first, c.second);
|
||||
}
|
||||
|
||||
if (run.has_memory_result) {
|
||||
out << ",\n" << indent << FormatKV("allocs_per_iter", run.allocs_per_iter);
|
||||
out << ",\n" << indent << FormatKV("max_bytes_used", run.max_bytes_used);
|
||||
}
|
||||
|
||||
if (!run.report_label.empty()) {
|
||||
out << ",\n" << indent << FormatKV("label", run.report_label);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ typedef std::condition_variable Condition;
|
|||
// NOTE: Wrappers for std::mutex and std::unique_lock are provided so that
|
||||
// we can annotate them with thread safety attributes and use the
|
||||
// -Wthread-safety warning with clang. The standard library types cannot be
|
||||
// used directly because they do not provided the required annotations.
|
||||
// used directly because they do not provide the required annotations.
|
||||
class CAPABILITY("mutex") Mutex {
|
||||
public:
|
||||
Mutex() {}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "check.h"
|
||||
#include "string_util.h"
|
||||
|
||||
namespace benchmark {
|
||||
|
||||
|
@ -48,12 +49,20 @@ void BenchmarkReporter::PrintBasicContext(std::ostream *out,
|
|||
Out << "CPU Caches:\n";
|
||||
for (auto &CInfo : info.caches) {
|
||||
Out << " L" << CInfo.level << " " << CInfo.type << " "
|
||||
<< (CInfo.size / 1000) << "K";
|
||||
<< (CInfo.size / 1024) << " KiB";
|
||||
if (CInfo.num_sharing != 0)
|
||||
Out << " (x" << (info.num_cpus / CInfo.num_sharing) << ")";
|
||||
Out << "\n";
|
||||
}
|
||||
}
|
||||
if (!info.load_avg.empty()) {
|
||||
Out << "Load Average: ";
|
||||
for (auto It = info.load_avg.begin(); It != info.load_avg.end();) {
|
||||
Out << StrFormat("%.2f", *It++);
|
||||
if (It != info.load_avg.end()) Out << ", ";
|
||||
}
|
||||
Out << "\n";
|
||||
}
|
||||
|
||||
if (info.scaling_enabled) {
|
||||
Out << "***WARNING*** CPU scaling is enabled, the benchmark "
|
||||
|
@ -70,7 +79,16 @@ void BenchmarkReporter::PrintBasicContext(std::ostream *out,
|
|||
// No initializer because it's already initialized to NULL.
|
||||
const char *BenchmarkReporter::Context::executable_name;
|
||||
|
||||
BenchmarkReporter::Context::Context() : cpu_info(CPUInfo::Get()) {}
|
||||
BenchmarkReporter::Context::Context()
|
||||
: cpu_info(CPUInfo::Get()), sys_info(SystemInfo::Get()) {}
|
||||
|
||||
std::string BenchmarkReporter::Run::benchmark_name() const {
|
||||
std::string name = run_name.str();
|
||||
if (run_type == RT_Aggregate) {
|
||||
name += "_" + aggregate_name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
double BenchmarkReporter::Run::GetAdjustedRealTime() const {
|
||||
double new_time = real_accumulated_time * GetTimeUnitMultiplier(time_unit);
|
||||
|
|
|
@ -91,17 +91,13 @@ std::vector<BenchmarkReporter::Run> ComputeStats(
|
|||
// Accumulators.
|
||||
std::vector<double> real_accumulated_time_stat;
|
||||
std::vector<double> cpu_accumulated_time_stat;
|
||||
std::vector<double> bytes_per_second_stat;
|
||||
std::vector<double> items_per_second_stat;
|
||||
|
||||
real_accumulated_time_stat.reserve(reports.size());
|
||||
cpu_accumulated_time_stat.reserve(reports.size());
|
||||
bytes_per_second_stat.reserve(reports.size());
|
||||
items_per_second_stat.reserve(reports.size());
|
||||
|
||||
// All repetitions should be run with the same number of iterations so we
|
||||
// can take this information from the first benchmark.
|
||||
int64_t const run_iterations = reports.front().iterations;
|
||||
const IterationCount run_iterations = reports.front().iterations;
|
||||
// create stats for user counters
|
||||
struct CounterStat {
|
||||
Counter c;
|
||||
|
@ -123,13 +119,11 @@ std::vector<BenchmarkReporter::Run> ComputeStats(
|
|||
|
||||
// Populate the accumulators.
|
||||
for (Run const& run : reports) {
|
||||
CHECK_EQ(reports[0].benchmark_name, run.benchmark_name);
|
||||
CHECK_EQ(reports[0].benchmark_name(), run.benchmark_name());
|
||||
CHECK_EQ(run_iterations, run.iterations);
|
||||
if (run.error_occurred) continue;
|
||||
real_accumulated_time_stat.emplace_back(run.real_accumulated_time);
|
||||
cpu_accumulated_time_stat.emplace_back(run.cpu_accumulated_time);
|
||||
items_per_second_stat.emplace_back(run.items_per_second);
|
||||
bytes_per_second_stat.emplace_back(run.bytes_per_second);
|
||||
// user counters
|
||||
for (auto const& cnt : run.counters) {
|
||||
auto it = counter_stats.find(cnt.first);
|
||||
|
@ -147,24 +141,46 @@ std::vector<BenchmarkReporter::Run> ComputeStats(
|
|||
}
|
||||
}
|
||||
|
||||
const double iteration_rescale_factor =
|
||||
double(reports.size()) / double(run_iterations);
|
||||
|
||||
for (const auto& Stat : *reports[0].statistics) {
|
||||
// Get the data from the accumulator to BenchmarkReporter::Run's.
|
||||
Run data;
|
||||
data.benchmark_name = reports[0].benchmark_name + "_" + Stat.name_;
|
||||
data.run_name = reports[0].run_name;
|
||||
data.run_type = BenchmarkReporter::Run::RT_Aggregate;
|
||||
data.threads = reports[0].threads;
|
||||
data.repetitions = reports[0].repetitions;
|
||||
data.repetition_index = Run::no_repetition_index;
|
||||
data.aggregate_name = Stat.name_;
|
||||
data.report_label = report_label;
|
||||
data.iterations = run_iterations;
|
||||
|
||||
// It is incorrect to say that an aggregate is computed over
|
||||
// run's iterations, because those iterations already got averaged.
|
||||
// Similarly, if there are N repetitions with 1 iterations each,
|
||||
// an aggregate will be computed over N measurements, not 1.
|
||||
// Thus it is best to simply use the count of separate reports.
|
||||
data.iterations = reports.size();
|
||||
|
||||
data.real_accumulated_time = Stat.compute_(real_accumulated_time_stat);
|
||||
data.cpu_accumulated_time = Stat.compute_(cpu_accumulated_time_stat);
|
||||
data.bytes_per_second = Stat.compute_(bytes_per_second_stat);
|
||||
data.items_per_second = Stat.compute_(items_per_second_stat);
|
||||
|
||||
// We will divide these times by data.iterations when reporting, but the
|
||||
// data.iterations is not nessesairly the scale of these measurements,
|
||||
// because in each repetition, these timers are sum over all the iterations.
|
||||
// And if we want to say that the stats are over N repetitions and not
|
||||
// M iterations, we need to multiply these by (N/M).
|
||||
data.real_accumulated_time *= iteration_rescale_factor;
|
||||
data.cpu_accumulated_time *= iteration_rescale_factor;
|
||||
|
||||
data.time_unit = reports[0].time_unit;
|
||||
|
||||
// user counters
|
||||
for (auto const& kv : counter_stats) {
|
||||
// Do *NOT* rescale the custom counters. They are already properly scaled.
|
||||
const auto uc_stat = Stat.compute_(kv.second.s);
|
||||
auto c = Counter(uc_stat, counter_stats[kv.first].c.flags);
|
||||
auto c = Counter(uc_stat, counter_stats[kv.first].c.flags,
|
||||
counter_stats[kv.first].c.oneK);
|
||||
data.counters[kv.first] = c;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "string_util.h"
|
||||
|
||||
#include <array>
|
||||
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
|
||||
#include <cerrno>
|
||||
#endif
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
@ -160,15 +163,6 @@ std::string StrFormat(const char* format, ...) {
|
|||
return tmp;
|
||||
}
|
||||
|
||||
void ReplaceAll(std::string* str, const std::string& from,
|
||||
const std::string& to) {
|
||||
std::size_t start = 0;
|
||||
while ((start = str->find(from, start)) != std::string::npos) {
|
||||
str->replace(start, from.length(), to);
|
||||
start += to.length();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
|
||||
/*
|
||||
* GNU STL in Android NDK lacks support for some C++11 functions, including
|
||||
|
|
|
@ -12,7 +12,13 @@ void AppendHumanReadable(int n, std::string* str);
|
|||
|
||||
std::string HumanReadableNumber(double n, double one_k = 1024.0);
|
||||
|
||||
std::string StrFormat(const char* format, ...);
|
||||
#if defined(__MINGW32__)
|
||||
__attribute__((format(__MINGW_PRINTF_FORMAT, 1, 2)))
|
||||
#elif defined(__GNUC__)
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
#endif
|
||||
std::string
|
||||
StrFormat(const char* format, ...);
|
||||
|
||||
inline std::ostream& StrCatImp(std::ostream& out) BENCHMARK_NOEXCEPT {
|
||||
return out;
|
||||
|
@ -31,9 +37,6 @@ inline std::string StrCat(Args&&... args) {
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
void ReplaceAll(std::string* str, const std::string& from,
|
||||
const std::string& to);
|
||||
|
||||
#ifdef BENCHMARK_STL_ANDROID_GNUSTL
|
||||
/*
|
||||
* GNU STL in Android NDK lacks support for some C++11 functions, including
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#undef StrCat // Don't let StrCat in string_util.h be renamed to lstrcatA
|
||||
#include <versionhelpers.h>
|
||||
#include <windows.h>
|
||||
#include <codecvt>
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#ifndef BENCHMARK_OS_FUCHSIA
|
||||
|
@ -36,6 +37,9 @@
|
|||
#if defined(BENCHMARK_OS_SOLARIS)
|
||||
#include <kstat.h>
|
||||
#endif
|
||||
#if defined(BENCHMARK_OS_QNX)
|
||||
#include <sys/syspage.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
@ -52,6 +56,7 @@
|
|||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <locale>
|
||||
|
||||
#include "check.h"
|
||||
#include "cycleclock.h"
|
||||
|
@ -207,6 +212,9 @@ bool ReadFromFile(std::string const& fname, ArgT* arg) {
|
|||
bool CpuScalingEnabled(int num_cpus) {
|
||||
// We don't have a valid CPU count, so don't even bother.
|
||||
if (num_cpus <= 0) return false;
|
||||
#ifdef BENCHMARK_OS_QNX
|
||||
return false;
|
||||
#endif
|
||||
#ifndef BENCHMARK_OS_WINDOWS
|
||||
// On Linux, the CPUfreq subsystem exposes CPU information as files on the
|
||||
// local file system. If reading the exported files fails, then we may not be
|
||||
|
@ -262,7 +270,7 @@ std::vector<CPUInfo::CacheInfo> GetCacheSizesFromKVFS() {
|
|||
else if (f && suffix != "K")
|
||||
PrintErrorAndDie("Invalid cache size format: Expected bytes ", suffix);
|
||||
else if (suffix == "K")
|
||||
info.size *= 1000;
|
||||
info.size *= 1024;
|
||||
}
|
||||
if (!ReadFromFile(StrCat(FPath, "type"), &info.type))
|
||||
PrintErrorAndDie("Failed to read from file ", FPath, "type");
|
||||
|
@ -288,7 +296,7 @@ std::vector<CPUInfo::CacheInfo> GetCacheSizesMacOSX() {
|
|||
std::string name;
|
||||
std::string type;
|
||||
int level;
|
||||
size_t num_sharing;
|
||||
uint64_t num_sharing;
|
||||
} Cases[] = {{"hw.l1dcachesize", "Data", 1, CacheCounts[1]},
|
||||
{"hw.l1icachesize", "Instruction", 1, CacheCounts[1]},
|
||||
{"hw.l2cachesize", "Unified", 2, CacheCounts[2]},
|
||||
|
@ -354,6 +362,40 @@ std::vector<CPUInfo::CacheInfo> GetCacheSizesWindows() {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
#elif BENCHMARK_OS_QNX
|
||||
std::vector<CPUInfo::CacheInfo> GetCacheSizesQNX() {
|
||||
std::vector<CPUInfo::CacheInfo> res;
|
||||
struct cacheattr_entry *cache = SYSPAGE_ENTRY(cacheattr);
|
||||
uint32_t const elsize = SYSPAGE_ELEMENT_SIZE(cacheattr);
|
||||
int num = SYSPAGE_ENTRY_SIZE(cacheattr) / elsize ;
|
||||
for(int i = 0; i < num; ++i ) {
|
||||
CPUInfo::CacheInfo info;
|
||||
switch (cache->flags){
|
||||
case CACHE_FLAG_INSTR :
|
||||
info.type = "Instruction";
|
||||
info.level = 1;
|
||||
break;
|
||||
case CACHE_FLAG_DATA :
|
||||
info.type = "Data";
|
||||
info.level = 1;
|
||||
break;
|
||||
case CACHE_FLAG_UNIFIED :
|
||||
info.type = "Unified";
|
||||
info.level = 2;
|
||||
case CACHE_FLAG_SHARED :
|
||||
info.type = "Shared";
|
||||
info.level = 3;
|
||||
default :
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
info.size = cache->line_size * cache->num_lines;
|
||||
info.num_sharing = 0;
|
||||
res.push_back(std::move(info));
|
||||
cache = SYSPAGE_ARRAY_ADJ_OFFSET(cacheattr, cache, elsize);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<CPUInfo::CacheInfo> GetCacheSizes() {
|
||||
|
@ -361,11 +403,53 @@ std::vector<CPUInfo::CacheInfo> GetCacheSizes() {
|
|||
return GetCacheSizesMacOSX();
|
||||
#elif defined(BENCHMARK_OS_WINDOWS)
|
||||
return GetCacheSizesWindows();
|
||||
#elif defined(BENCHMARK_OS_QNX)
|
||||
return GetCacheSizesQNX();
|
||||
#else
|
||||
return GetCacheSizesFromKVFS();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string GetSystemName() {
|
||||
#if defined(BENCHMARK_OS_WINDOWS)
|
||||
std::string str;
|
||||
const unsigned COUNT = MAX_COMPUTERNAME_LENGTH+1;
|
||||
TCHAR hostname[COUNT] = {'\0'};
|
||||
DWORD DWCOUNT = COUNT;
|
||||
if (!GetComputerName(hostname, &DWCOUNT))
|
||||
return std::string("");
|
||||
#ifndef UNICODE
|
||||
str = std::string(hostname, DWCOUNT);
|
||||
#else
|
||||
//Using wstring_convert, Is deprecated in C++17
|
||||
using convert_type = std::codecvt_utf8<wchar_t>;
|
||||
std::wstring_convert<convert_type, wchar_t> converter;
|
||||
std::wstring wStr(hostname, DWCOUNT);
|
||||
str = converter.to_bytes(wStr);
|
||||
#endif
|
||||
return str;
|
||||
#else // defined(BENCHMARK_OS_WINDOWS)
|
||||
#ifndef HOST_NAME_MAX
|
||||
#ifdef BENCHMARK_HAS_SYSCTL // BSD/Mac Doesnt have HOST_NAME_MAX defined
|
||||
#define HOST_NAME_MAX 64
|
||||
#elif defined(BENCHMARK_OS_NACL)
|
||||
#define HOST_NAME_MAX 64
|
||||
#elif defined(BENCHMARK_OS_QNX)
|
||||
#define HOST_NAME_MAX 154
|
||||
#elif defined(BENCHMARK_OS_RTEMS)
|
||||
#define HOST_NAME_MAX 256
|
||||
#else
|
||||
#warning "HOST_NAME_MAX not defined. using 64"
|
||||
#define HOST_NAME_MAX 64
|
||||
#endif
|
||||
#endif // def HOST_NAME_MAX
|
||||
char hostname[HOST_NAME_MAX];
|
||||
int retVal = gethostname(hostname, HOST_NAME_MAX);
|
||||
if (retVal != 0) return std::string("");
|
||||
return std::string(hostname);
|
||||
#endif // Catch-all POSIX block.
|
||||
}
|
||||
|
||||
int GetNumCPUs() {
|
||||
#ifdef BENCHMARK_HAS_SYSCTL
|
||||
int NumCPU = -1;
|
||||
|
@ -390,6 +474,8 @@ int GetNumCPUs() {
|
|||
strerror(errno));
|
||||
}
|
||||
return NumCPU;
|
||||
#elif defined(BENCHMARK_OS_QNX)
|
||||
return static_cast<int>(_syspage_ptr->num_cpu);
|
||||
#else
|
||||
int NumCPUs = 0;
|
||||
int MaxID = -1;
|
||||
|
@ -404,7 +490,13 @@ int GetNumCPUs() {
|
|||
if (ln.empty()) continue;
|
||||
size_t SplitIdx = ln.find(':');
|
||||
std::string value;
|
||||
#if defined(__s390__)
|
||||
// s390 has another format in /proc/cpuinfo
|
||||
// it needs to be parsed differently
|
||||
if (SplitIdx != std::string::npos) value = ln.substr(Key.size()+1,SplitIdx-Key.size()-1);
|
||||
#else
|
||||
if (SplitIdx != std::string::npos) value = ln.substr(SplitIdx + 1);
|
||||
#endif
|
||||
if (ln.size() >= Key.size() && ln.compare(0, Key.size(), Key) == 0) {
|
||||
NumCPUs++;
|
||||
if (!value.empty()) {
|
||||
|
@ -563,6 +655,9 @@ double GetCPUCyclesPerSecond() {
|
|||
double clock_hz = knp->value.ui64;
|
||||
kstat_close(kc);
|
||||
return clock_hz;
|
||||
#elif defined (BENCHMARK_OS_QNX)
|
||||
return static_cast<double>((int64_t)(SYSPAGE_ENTRY(cpuinfo)->speed) *
|
||||
(int64_t)(1000 * 1000));
|
||||
#endif
|
||||
// If we've fallen through, attempt to roughly estimate the CPU clock rate.
|
||||
const int estimate_time_ms = 1000;
|
||||
|
@ -571,6 +666,24 @@ double GetCPUCyclesPerSecond() {
|
|||
return static_cast<double>(cycleclock::Now() - start_ticks);
|
||||
}
|
||||
|
||||
std::vector<double> GetLoadAvg() {
|
||||
#if (defined BENCHMARK_OS_FREEBSD || defined(BENCHMARK_OS_LINUX) || \
|
||||
defined BENCHMARK_OS_MACOSX || defined BENCHMARK_OS_NETBSD || \
|
||||
defined BENCHMARK_OS_OPENBSD) && !defined(__ANDROID__)
|
||||
constexpr int kMaxSamples = 3;
|
||||
std::vector<double> res(kMaxSamples, 0.0);
|
||||
const int nelem = getloadavg(res.data(), kMaxSamples);
|
||||
if (nelem < 1) {
|
||||
res.clear();
|
||||
} else {
|
||||
res.resize(nelem);
|
||||
}
|
||||
return res;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
const CPUInfo& CPUInfo::Get() {
|
||||
|
@ -582,6 +695,14 @@ CPUInfo::CPUInfo()
|
|||
: num_cpus(GetNumCPUs()),
|
||||
cycles_per_second(GetCPUCyclesPerSecond()),
|
||||
caches(GetCacheSizes()),
|
||||
scaling_enabled(CpuScalingEnabled(num_cpus)) {}
|
||||
scaling_enabled(CpuScalingEnabled(num_cpus)),
|
||||
load_avg(GetLoadAvg()) {}
|
||||
|
||||
|
||||
const SystemInfo& SystemInfo::Get() {
|
||||
static const SystemInfo* info = new SystemInfo();
|
||||
return *info;
|
||||
}
|
||||
|
||||
SystemInfo::SystemInfo() : name(GetSystemName()) {}
|
||||
} // end namespace benchmark
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace internal {
|
|||
|
||||
class ThreadManager {
|
||||
public:
|
||||
ThreadManager(int num_threads)
|
||||
explicit ThreadManager(int num_threads)
|
||||
: alive_threads_(num_threads), start_stop_barrier_(num_threads) {}
|
||||
|
||||
Mutex& GetBenchmarkMutex() const RETURN_CAPABILITY(benchmark_mutex_) {
|
||||
|
@ -38,12 +38,10 @@ class ThreadManager {
|
|||
|
||||
public:
|
||||
struct Result {
|
||||
int64_t iterations = 0;
|
||||
IterationCount iterations = 0;
|
||||
double real_time_used = 0;
|
||||
double cpu_time_used = 0;
|
||||
double manual_time_used = 0;
|
||||
int64_t bytes_processed = 0;
|
||||
int64_t items_processed = 0;
|
||||
int64_t complexity_n = 0;
|
||||
std::string report_label_;
|
||||
std::string error_message_;
|
||||
|
|
|
@ -8,14 +8,22 @@ namespace benchmark {
|
|||
namespace internal {
|
||||
|
||||
class ThreadTimer {
|
||||
explicit ThreadTimer(bool measure_process_cpu_time_)
|
||||
: measure_process_cpu_time(measure_process_cpu_time_) {}
|
||||
|
||||
public:
|
||||
ThreadTimer() = default;
|
||||
static ThreadTimer Create() {
|
||||
return ThreadTimer(/*measure_process_cpu_time_=*/false);
|
||||
}
|
||||
static ThreadTimer CreateProcessCpuTime() {
|
||||
return ThreadTimer(/*measure_process_cpu_time_=*/true);
|
||||
}
|
||||
|
||||
// Called by each thread
|
||||
void StartTimer() {
|
||||
running_ = true;
|
||||
start_real_time_ = ChronoClockNow();
|
||||
start_cpu_time_ = ThreadCPUUsage();
|
||||
start_cpu_time_ = ReadCpuTimerOfChoice();
|
||||
}
|
||||
|
||||
// Called by each thread
|
||||
|
@ -25,7 +33,8 @@ class ThreadTimer {
|
|||
real_time_used_ += ChronoClockNow() - start_real_time_;
|
||||
// Floating point error can result in the subtraction producing a negative
|
||||
// time. Guard against that.
|
||||
cpu_time_used_ += std::max<double>(ThreadCPUUsage() - start_cpu_time_, 0);
|
||||
cpu_time_used_ +=
|
||||
std::max<double>(ReadCpuTimerOfChoice() - start_cpu_time_, 0);
|
||||
}
|
||||
|
||||
// Called by each thread
|
||||
|
@ -34,24 +43,32 @@ class ThreadTimer {
|
|||
bool running() const { return running_; }
|
||||
|
||||
// REQUIRES: timer is not running
|
||||
double real_time_used() {
|
||||
double real_time_used() const {
|
||||
CHECK(!running_);
|
||||
return real_time_used_;
|
||||
}
|
||||
|
||||
// REQUIRES: timer is not running
|
||||
double cpu_time_used() {
|
||||
double cpu_time_used() const {
|
||||
CHECK(!running_);
|
||||
return cpu_time_used_;
|
||||
}
|
||||
|
||||
// REQUIRES: timer is not running
|
||||
double manual_time_used() {
|
||||
double manual_time_used() const {
|
||||
CHECK(!running_);
|
||||
return manual_time_used_;
|
||||
}
|
||||
|
||||
private:
|
||||
double ReadCpuTimerOfChoice() const {
|
||||
if (measure_process_cpu_time) return ProcessCPUUsage();
|
||||
return ThreadCPUUsage();
|
||||
}
|
||||
|
||||
// should the thread, or the process, time be measured?
|
||||
const bool measure_process_cpu_time;
|
||||
|
||||
bool running_ = false; // Is the timer running
|
||||
double start_real_time_ = 0; // If running_
|
||||
double start_cpu_time_ = 0; // If running_
|
||||
|
|
|
@ -178,40 +178,68 @@ double ThreadCPUUsage() {
|
|||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string DateTimeString(bool local) {
|
||||
std::string LocalDateTimeString() {
|
||||
// Write the local time in RFC3339 format yyyy-mm-ddTHH:MM:SS+/-HH:MM.
|
||||
typedef std::chrono::system_clock Clock;
|
||||
std::time_t now = Clock::to_time_t(Clock::now());
|
||||
const std::size_t kStorageSize = 128;
|
||||
char storage[kStorageSize];
|
||||
std::size_t written;
|
||||
const std::size_t kTzOffsetLen = 6;
|
||||
const std::size_t kTimestampLen = 19;
|
||||
|
||||
std::size_t tz_len;
|
||||
std::size_t timestamp_len;
|
||||
long int offset_minutes;
|
||||
char tz_offset_sign = '+';
|
||||
// Long enough buffers to avoid format-overflow warnings
|
||||
char tz_offset[128];
|
||||
char storage[128];
|
||||
|
||||
if (local) {
|
||||
#if defined(BENCHMARK_OS_WINDOWS)
|
||||
written =
|
||||
std::strftime(storage, sizeof(storage), "%x %X", ::localtime(&now));
|
||||
std::tm *timeinfo_p = ::localtime(&now);
|
||||
#else
|
||||
std::tm timeinfo;
|
||||
::localtime_r(&now, &timeinfo);
|
||||
written = std::strftime(storage, sizeof(storage), "%F %T", &timeinfo);
|
||||
std::tm timeinfo;
|
||||
std::tm *timeinfo_p = &timeinfo;
|
||||
::localtime_r(&now, &timeinfo);
|
||||
#endif
|
||||
|
||||
tz_len = std::strftime(tz_offset, sizeof(tz_offset), "%z", timeinfo_p);
|
||||
|
||||
if (tz_len < kTzOffsetLen && tz_len > 1) {
|
||||
// Timezone offset was written. strftime writes offset as +HHMM or -HHMM,
|
||||
// RFC3339 specifies an offset as +HH:MM or -HH:MM. To convert, we parse
|
||||
// the offset as an integer, then reprint it to a string.
|
||||
|
||||
offset_minutes = ::strtol(tz_offset, NULL, 10);
|
||||
if (offset_minutes < 0) {
|
||||
offset_minutes *= -1;
|
||||
tz_offset_sign = '-';
|
||||
}
|
||||
|
||||
tz_len = ::snprintf(tz_offset, sizeof(tz_offset), "%c%02li:%02li",
|
||||
tz_offset_sign, offset_minutes / 100, offset_minutes % 100);
|
||||
CHECK(tz_len == kTzOffsetLen);
|
||||
((void)tz_len); // Prevent unused variable warning in optimized build.
|
||||
} else {
|
||||
// Unknown offset. RFC3339 specifies that unknown local offsets should be
|
||||
// written as UTC time with -00:00 timezone.
|
||||
#if defined(BENCHMARK_OS_WINDOWS)
|
||||
written = std::strftime(storage, sizeof(storage), "%x %X", ::gmtime(&now));
|
||||
// Potential race condition if another thread calls localtime or gmtime.
|
||||
timeinfo_p = ::gmtime(&now);
|
||||
#else
|
||||
std::tm timeinfo;
|
||||
::gmtime_r(&now, &timeinfo);
|
||||
written = std::strftime(storage, sizeof(storage), "%F %T", &timeinfo);
|
||||
#endif
|
||||
|
||||
strncpy(tz_offset, "-00:00", kTzOffsetLen + 1);
|
||||
}
|
||||
CHECK(written < kStorageSize);
|
||||
((void)written); // prevent unused variable in optimized mode.
|
||||
|
||||
timestamp_len = std::strftime(storage, sizeof(storage), "%Y-%m-%dT%H:%M:%S",
|
||||
timeinfo_p);
|
||||
CHECK(timestamp_len == kTimestampLen);
|
||||
// Prevent unused variable warning in optimized build.
|
||||
((void)timestamp_len);
|
||||
((void)kTimestampLen);
|
||||
|
||||
std::strncat(storage, tz_offset, kTzOffsetLen + 1);
|
||||
return std::string(storage);
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
std::string LocalDateTimeString() { return DateTimeString(true); }
|
||||
|
||||
} // end namespace benchmark
|
||||
|
|
Loading…
Reference in New Issue