Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#pragma once
#include <functional> // function
#include <type_traits> // is_same
#include <future> // extra: async
#include <variant> // extra: get, holds_alternative
#include <chrono> // high_resolution_clock, high_resolution_clock::time_point, _seconds
#include <optional>
#include <string>
namespace stevefan1999 {
using JustInTimerIndependentFn = std::function<void()>;
using JustInTimeIgnore = std::function<void(JustInTimerIndependentFn)>;
using JustInTimeHighResTime = std::chrono::high_resolution_clock;
using JustInTimeHighResTimePoint = std::chrono::high_resolution_clock::time_point;
using JustInTimeBestPreciseTime = std::chrono::nanoseconds;
using JustInTimeBenchmarkSite = std::function<void()>;
using JustInTimeBenchmarkSiteWithIgnorer = std::function<void(JustInTimeIgnore)>;
using JustInTimeBenchmarkCandidate = std::variant< // function or function with a "time suspend" function
JustInTimeBenchmarkSite,
JustInTimeBenchmarkSiteWithIgnorer
>;
JustInTimeHighResTimePoint JustInTimeGetCurrentTime() {
return JustInTimeHighResTime::now();
}
struct JustInTimeBenchConfig {
friend class JustInTime;
bool async = true;
std::optional<std::string> name;
JustInTimeHighResTimePoint started, finished;
private:
std::optional<JustInTimeBenchmarkCandidate> benchSite;
JustInTimeBestPreciseTime time_ignored = JustInTimeBestPreciseTime::zero();
public:
template<class T>
constexpr auto getTimeElapsed();
};
using JustInTimeBenchmarkResult = std::vector<JustInTimeBenchConfig>;
using JustInTimeOnFinished = std::function<void(JustInTimeBenchConfig &)>;
class JustInTime {
class JustInTimeDetail {
JustInTimeBenchmarkResult m_bench;
JustInTimeOnFinished m_asyncBenchFinsihed;
JustInTimeOnFinished m_syncBenchFinsihed;
private:
bool configIsMarshalled() {
auto &pop = m_bench.back();
return pop.name && pop.benchSite;
}
public: // fluent config
JustInTimeDetail &addBench(std::string name, JustInTimeBenchmarkCandidate fn, bool async) {
if (m_bench.size() > 0 && !configIsMarshalled()) {
throw "The previous bench was not properly configurated";
}
JustInTimeBenchConfig config;
config.name = name;
config.benchSite = fn;
config.async = async;
m_bench.push_back(config);
return *this;
}
auto addBench(std::string name, JustInTimeBenchmarkCandidate fn) {
return addBench(name, fn, true);
}
auto addBench(std::string name) {
return [this, name](JustInTimeBenchmarkCandidate fn) {
return addBench(name, fn);
};
}
auto addBench(JustInTimeBenchmarkCandidate fn) {
return addBench("<unnamed bench at index " + std::to_string(m_bench.size()) + ">")(fn);
}
auto addBench() {
return addBench({}, {}, true);
}
JustInTimeDetail &async(bool b = true) {
if (m_bench.size() == 0) {
throw "no config is pushed";
}
m_bench.back().async = b;
return *this;
}
JustInTimeDetail &name(std::string _name) {
if (m_bench.size() == 0) {
throw "no config is pushed";
}
m_bench.back().name = _name;
return *this;
}
JustInTimeDetail &bench(JustInTimeBenchmarkCandidate fn) {
if (m_bench.size() == 0) {
throw "no config is pushed";
}
m_bench.back().benchSite = fn;
return *this;
}
JustInTimeDetail &onOneOfAsyncBenchFinsihed(JustInTimeOnFinished fn) {
m_asyncBenchFinsihed = fn;
return *this;
}
JustInTimeDetail &onOneOfSyncBenchFinsihed(JustInTimeOnFinished fn) {
m_syncBenchFinsihed = fn;
return *this;
}
private:
static void startBench(JustInTimeBenchConfig &config) {
auto ignore_timing = [&config](JustInTimerIndependentFn timerIndependent) {
// main implementation: calculate the running time used to call the inner function timeIndependent
// and add the function call time difference(delta) to the "time ignored" variable
auto d1 = JustInTimeGetCurrentTime();
timerIndependent();
auto d2 = JustInTimeGetCurrentTime();
config.time_ignored += d2 - d1; // add the time delta
};
auto fnSelect = config.benchSite.value();
if (std::holds_alternative<JustInTimeBenchmarkSiteWithIgnorer>(fnSelect)) {
// is with ignorer
auto benchFn = std::get<JustInTimeBenchmarkSiteWithIgnorer>(fnSelect);
config.started = JustInTimeGetCurrentTime();
benchFn(ignore_timing);
config.finished = JustInTimeGetCurrentTime();
} else {
// due to variant dichotomy it must be JustInTimeBenchmarkSite
auto benchFn = std::get<JustInTimeBenchmarkSite>(fnSelect);
config.started = JustInTimeGetCurrentTime();
benchFn();
config.finished = JustInTimeGetCurrentTime();
}
}
void startAllAsyncBench(std::vector<std::shared_future<void>> &storage) {
for (auto &it : m_bench) {
if (it.async) {
auto fut = std::async([this, &it]() { // start async task
startBench(it);
if (m_asyncBenchFinsihed) {
m_asyncBenchFinsihed(it);
}
}).share();
storage.push_back(fut);
}
}
}
void startAllSyncBench() {
for (auto &it : m_bench) {
if (!it.async) {
startBench(it);
if (m_syncBenchFinsihed) {
m_syncBenchFinsihed(it);
}
}
}
}
public:
JustInTimeDetail &startBench() {
if (m_bench.size() == 0) { // no entry is defined
return *this;
}
if (!configIsMarshalled()) {
throw "Benches were not properly configurated";
}
/*
* main implmentation: start all asynchronous benchmarks first
* then start all synchronous benchmarks
*/
std::vector<std::shared_future<void>> fut;
startAllAsyncBench(fut);
startAllSyncBench();
for (auto &it : fut) {
it.wait();
}
return *this;
}
JustInTimeBenchmarkResult getResults() {
return m_bench;
}
};
public:
static JustInTimeDetail factory() {
return JustInTimeDetail();
}
};
static auto make_just_in_time() {
return JustInTime::factory();
}
template<class T>
constexpr auto JustInTimeBenchConfig::getTimeElapsed() { // return the true time consumed by the benchmark function
auto total_function_timeframe = finished - started;
return std::chrono::duration_cast<T>(total_function_timeframe - time_ignored).count(); // implicitly ensuring type T to chrono::duration<T>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.