/* * Copyright 2013-2024 Real Logic Limited. * * 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 * * https://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 _BENCHLET_HPP #define _BENCHLET_HPP #define __STDC_LIMIT_MACROS 1 #include #include #include #include #if defined(Darwin) # include # include #elif defined(__linux__) # include #elif defined(WIN32) || defined(_WIN32) #else # error "Must define Darwin or __linux__ or WIN32" #endif /* platform includes */ #include #include #define DEFAULT_ITERATIONS 1 #define DEFAULT_ITERATIONS_STRING "1" #define DEFAULT_BATCHES 1 #define DEFAULT_BATCHES_STRING "1" #define DEFAULT_RETAIN_STATS false class Benchmark { public: enum ConfigVariable { ITERATIONS, BATCHES, RETAIN_STATS }; struct Config { ConfigVariable key; const char *value; }; virtual void setUp() {}; virtual void tearDown() {}; virtual uint64_t run(int iterations) = 0; void name(const char *name) { name_ = name; }; const char *name() const { return name_; }; void runName(const char *runName) { runName_ = runName; }; const char *runName() const { return runName_; }; void iterations(const unsigned int i) { iterations_ = i; }; unsigned int iterations() const { return iterations_; }; void batches(const unsigned int i) { batches_ = i; }; unsigned int batches() const { return batches_; }; void config(const struct Config *cfg, unsigned int numConfigs) { config_ = cfg; numConfigs_ = numConfigs; }; const struct Config *config() const { return config_; }; unsigned int numConfigs() const { return numConfigs_; }; void stats(uint64_t *stats) { stats_ = stats; }; uint64_t *stats() { return stats_; }; void retainStats(bool retain) { retainStats_ = retain; }; bool retainStats() const { return retainStats_; }; private: const char *name_ = nullptr; const char *runName_ = nullptr; unsigned int iterations_ = 0; unsigned int batches_ = 0; const struct Config *config_ = nullptr; unsigned int numConfigs_ = 0; uint64_t *stats_ = nullptr; bool retainStats_ = false; // save start time, etc. }; class BenchmarkRunner { public: static Benchmark *registerBenchmark(const char *name, const char *runName, Benchmark *impl, struct Benchmark::Config *cfg, int numCfgs) { impl->name(name); impl->runName(runName); impl->config(cfg, numCfgs); impl->iterations(DEFAULT_ITERATIONS); impl->batches(DEFAULT_BATCHES); impl->retainStats(DEFAULT_RETAIN_STATS); for (int i = 0, max = numCfgs; i < max; i++) { if (cfg[i].key == Benchmark::ITERATIONS) { impl->iterations(atoi(cfg[i].value)); } else if (cfg[i].key == Benchmark::BATCHES) { impl->batches(atoi(cfg[i].value)); } else if (cfg[i].key == Benchmark::RETAIN_STATS) { if (strcmp(cfg[i].value, "true") == 0) { impl->retainStats(true); } else if (strcmp(cfg[i].value, "false") == 0) { impl->retainStats(false); } } } table().push_back(impl); std::cout << "Registering " << name << " run \"" << runName << "\" total iterations " << impl->iterations() * impl->batches() << std::endl; return impl; }; static void run() { for (std::vector::iterator it = table().begin(); it != table().end(); ++it) { Benchmark *benchmark = *it; uint64_t elapsedNanos; double nanospop, opspsec; uint64_t *stats = new uint64_t[benchmark->batches()]; uint64_t total = 0; std::cout << "Running benchmark " << benchmark->name() << "." << benchmark->runName() << ". "; std::cout << benchmark->iterations() << " iterations X " << benchmark->batches() << " batches. " << std::endl; benchmark->setUp(); for (int i = 0, max_i = benchmark->batches(); i < max_i; i++) { elapsedNanos = benchmark->run(benchmark->iterations()); nanospop = (double)elapsedNanos / (double)benchmark->iterations(); opspsec = 1000000000.0 / nanospop; stats[i] = elapsedNanos; total += elapsedNanos; std::cout << " Elapsed " << elapsedNanos << " nanoseconds. "; std::cout << opspsec/1000.0 << " Kops/sec. "; std::cout << nanospop << " nanos/op. "; std::cout << std::endl; } double elapsedPerBatch = (double)total / (double)benchmark->batches(); double elapsedPerIteration = elapsedPerBatch / (double)benchmark->iterations(); double throughputKopsps = 1000000.0 / elapsedPerIteration; std::cout << " Avg elapsed/batch " << elapsedPerBatch << " nanoseconds" << std::endl; std::cout << " Throughput " << throughputKopsps << " Kops/sec." << std::endl; std::cout << " Avg nanos/op " << elapsedPerIteration << " nanos/op" << std::endl; benchmark->stats(stats); benchmark->tearDown(); total = 0; if (!benchmark->retainStats()) { delete[] stats; } } }; static std::vector &table() { static std::vector table; return table; }; #if defined(Darwin) static uint64_t currentTimestamp(void) { return mach_absolute_time(); }; // inspired from https://developer.apple.com/library/mac/qa/qa1398/_index.html static uint64_t elapsedNanoseconds(uint64_t start_timestamp, uint64_t end_timestamp) { static mach_timebase_info_data_t timebaseInfo; if (0 == timebaseInfo.denom) { (void)mach_timebase_info(&timebaseInfo); } return (end_timestamp - start_timestamp) * timebaseInfo.numer / timebaseInfo.denom; }; #elif defined(__linux__) static uint64_t currentTimestamp() { struct timespec ts = {}; clock_gettime(CLOCK_REALTIME, &ts); return ts.tv_sec * 1000000000 + ts.tv_nsec; }; static uint64_t elapsedNanoseconds(uint64_t start_timestamp, uint64_t end_timestamp) { return end_timestamp - start_timestamp; }; #elif defined(WIN32) || defined(_WIN32) static uint64_t currentTimestamp(void); static uint64_t elapsedNanoseconds(uint64_t start_timestamp, uint64_t end_timestamp) { return end_timestamp - start_timestamp; } #endif /* platform high resolution time */ }; template uint64_t runBenchmark(C *obj, int iterations) { uint64_t start = BenchmarkRunner::currentTimestamp(); for (int i = 0; i < iterations; i++) { obj->benchmarkBody(); } uint64_t end = BenchmarkRunner::currentTimestamp(); return BenchmarkRunner::elapsedNanoseconds(start, end); } #define BENCHMARK_CLASS_NAME(x,y) x##y #define BENCHMARK_CONFIG(c,r,i) \ class BENCHMARK_CLASS_NAME(c,r) : public c { \ public: \ BENCHMARK_CLASS_NAME(c,r)() {}; \ virtual uint64_t run(int iterations) { return runBenchmark(this, iterations); }; \ void benchmarkBody(); \ private: \ static Benchmark *instance_; \ }; \ Benchmark *BENCHMARK_CLASS_NAME(c,r)::instance_ = BenchmarkRunner::registerBenchmark(#c, #r, new BENCHMARK_CLASS_NAME(c,r)(), i, sizeof(i)/sizeof(Benchmark::Config)); \ void BENCHMARK_CLASS_NAME(c,r)::benchmarkBody() #define BENCHMARK_ITERATIONS(c,r,i) \ struct Benchmark::Config c ## r ## _cfg[] = {{Benchmark::ITERATIONS, #i},{Benchmark::BATCHES, DEFAULT_BATCHES_STRING}}; \ BENCHMARK_CONFIG(c,r, c ## r ## _cfg) #define BENCHMARK(c,r) \ struct Benchmark::Config c ## r ## _cfg[] = {{Benchmark::ITERATIONS, DEFAULT_ITERATIONS_STRING},{Benchmark::BATCHES, DEFAULT_BATCHES_STRING}}; \ BENCHMARK_CONFIG(c,r, c ## r ## _cfg) #endif /* _BENCHLET_HPP */