File cpu_timer.hpp
File List > illixr > cpu_timer.hpp
Go to the documentation of this file
#pragma once
#include "error_util.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(_WIN32) || defined(_WIN64)
# include
# include
# include
# include
# ifndef CLOCK_MONOTONIC
# define CLOCK_MONOTINIC 1
# endif
# ifndef CLOCK_THREAD_CPUTIME_ID
# define CLOCK_THREAD_CPUTIME_ID 3
# endif
typedef int clockid_t;
static int illixr_clock_gettime(clockid_t clock_id, timespec* ts) {
if (clock_id == CLOCK_MONOTINIC) {
static LARGE_INTEGER start_ticks;
static LARGE_INTEGER frequency;
static bool initialized = false;
if (!initialized) {
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&start_ticks);
initialized = true;
}
LARGE_INTEGER current_ticks;
QueryPerformanceCounter(¤t_ticks);
LONGLONG diff = current_ticks.QuadPart - start_ticks.QuadPart;
ts->tv_sec = diff / frequency.QuadPart;
ts->tv_nsec = static_cast<long>(((diff % frequency.QuadPart) * 1000000000) / frequency.QuadPart);
return 0;
} else if (clock_id == CLOCK_THREAD_CPUTIME_ID) {
FILETIME creation_time, exit_time, kernel_time, user_time;
if (!GetThreadTimes(GetCurrentThread(), &creation_time, &exit_time, &kernel_time, &user_time))
return -1;
ULARGE_INTEGER ktime, utime;
ktime.LowPart = kernel_time.dwLowDateTime;
ktime.HighPart = kernel_time.dwHighDateTime;
utime.LowPart = user_time.dwLowDateTime;
utime.HighPart = user_time.dwHighDateTime;
uint64_t total_time_100ns = ktime.QuadPart + utime.QuadPart;
// convert to ns
uint64_t total_time_ns = total_time_100ns * 100;
ts->tv_sec = static_cast<time_t>(total_time_ns / 1000000000ULL);
ts->tv_nsec = static_cast<time_t>(total_time_ns % 1000000000ULL);
return 0;
}
return -1;
}
#endif // _WIN32 || _WIN64
static inline std::chrono::nanoseconds cpp_clock_get_time(clockid_t clock_id) {
/* This ensures the compiler won't reorder this function call; Pretend like it has memory side effects. */
timespec time_spec{};
RAC_ERRNO_MSG("cpu_timer before clock_get_time");
#if defined(_WIN32) || defined(_WIN64)
_ReadWriteBarrier();
#else
asm volatile(""
: /* OutputOperands */
: /* InputOperands */
: "memory" /* Clobbers */);
#endif
#if defined(_WIN32) || defined(_WIN64)
if (illixr_clock_gettime(clock_id, &time_spec)) {
#else
if (clock_gettime(clock_id, &time_spec)) {
#endif
throw std::runtime_error{std::string{"clock_get_time returned "} + strerror(errno)};
}
RAC_ERRNO_MSG("cpu_timer after clock_get_time");
#if defined(_WIN32) || defined(_WIN64)
_ReadWriteBarrier();
#else
asm volatile(""
: /* OutputOperands */
: /* InputOperands */
: "memory" /* Clobbers */);
#endif
return std::chrono::seconds{time_spec.tv_sec} + std::chrono::nanoseconds{time_spec.tv_nsec};
}
static inline std::chrono::nanoseconds thread_cpu_time() {
RAC_ERRNO_MSG("cpu_timer before cpp_clock_get_time");
return cpp_clock_get_time(CLOCK_THREAD_CPUTIME_ID);
}
template<typename Now_func, typename Time_point = decltype(std::declval<Now_func>()()),
typename Duration = decltype(std::declval<Time_point>() - std::declval<Time_point>())>
class timer {
public:
timer(const Now_func& now, Duration& duration)
: now_{now}
, duration_{duration} {
start_ = now_();
}
~timer() {
duration_ = now_() - start_;
}
private:
const Now_func& now_;
[[maybe_unused]] Duration& duration_;
Time_point start_;
};
template<typename Duration, typename Out = decltype(std::declval<Duration>().count())>
[[maybe_unused]] typename std::enable_if<std::is_integral<Out>::value, Out>::type count_duration(Duration t) {
return std::chrono::duration_cast<std::chrono::nanoseconds, typename Duration::rep, typename Duration::period>(t).count();
}
template<typename Duration>
[[maybe_unused]] [[maybe_unused]] typename std::enable_if<std::is_integral<Duration>::value, Duration>::type
count_duration(Duration t) {
return t;
}
template<typename Now_func, typename Time_point = decltype(std::declval<Now_func>()()),
typename Duration = decltype(std::declval<Time_point>() - std::declval<Time_point>())>
class print_timer {
public:
print_timer(const std::string& name, const Now_func& now)
: print_in_destructor_{name, duration_}
, timer_{now, duration_} { }
private:
class print_in_destructor {
public:
[[maybe_unused]] print_in_destructor(std::string account_name, const Duration& duration)
: account_name_{std::move(account_name)}
, duration_{duration} { }
~print_in_destructor() {
// std::ostringstream os;
// os << "cpu_timer," << account_name_ << "," << count_duration(duration_) << "\n";
if (rand() % 100 == 0) {
#ifndef NDEBUG
spdlog::get("illixr")->info("cpu_timer.hpp is DEPRECATED. See logging.hpp.");
#endif
}
}
private:
const std::string account_name_;
[[maybe_unused]] const Duration& duration_;
};
// NOTE that the destructors get called in reverse order!
// This is important, because timer_'s destructor records the timing information
// Then, print_in_destructor_ prints it
// Then, we can destroy duration_.
Duration duration_;
const print_in_destructor print_in_destructor_;
const timer<Now_func, Time_point, Duration> timer_;
};
static std::size_t gen_serial_no() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
}
class should_profile_class {
public:
should_profile_class() {
const char* ILLIXR_STDOUT_METRICS = getenv("ILLIXR_STDOUT_METRICS"); // can't use switchboard interface here
actually_should_profile_ = ILLIXR_STDOUT_METRICS && (strcmp(ILLIXR_STDOUT_METRICS, "y") == 0);
}
bool operator()() const {
return actually_should_profile_;
}
private:
bool actually_should_profile_;
};
static should_profile_class should_profile;
class print_timer2 {
public:
explicit print_timer2(std::string name)
: name_{std::move(name)}
, serial_no_{should_profile() ? gen_serial_no() : std::size_t{0}}
, wall_time_start_{should_profile() ? std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count()
: std::size_t{0}}
, cpu_time_start_{should_profile() ? thread_cpu_time().count() : std::size_t{0}} { }
~print_timer2() {
if (should_profile()) {
auto cpu_time_stop = thread_cpu_time().count();
auto wall_time_stop = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
spdlog::get("illixr")->info("[cpu_timer] cpu_timer,{},{},{},{},{},{}", name_, serial_no_, wall_time_start_,
wall_time_stop, cpu_time_start_, cpu_time_stop);
}
}
private:
const std::string name_;
const std::size_t serial_no_;
std::size_t wall_time_start_;
std::size_t cpu_time_start_;
};
#define PRINT_CPU_TIME_FOR_THIS_BLOCK(name) \
print_timer PRINT_CPU_TIME_FOR_THIS_BLOCK{name, thread_cpu_time};
#define PRINT_WALL_TIME_FOR_THIS_BLOCK(name) \
print_timer PRINT_WALL_TIME_FOR_THIS_BLOCK{ \
name, std::chrono::high_resolution_clock::now};
#define PRINT_RECORD_FOR_THIS_BLOCK(name) print_timer2 PRINT_RECORD_FOR_THIS_BLOCK_timer{name};
template<class Function, class... Args>
[[maybe_unused]] std::thread timed_thread(const std::string& account_name, Function&& f, Args&&... args) {
// Unfortunately we make copies of f and args.
// According to StackOverflow, this is unavoidable.
// See Sam Varshavchik's comment on https://stackoverflow.com/a/62380971/1078199
return std::thread([=] {
{
PRINT_RECORD_FOR_THIS_BLOCK(account_name)
std::invoke(f, args...);
}
});
}