561 lines
17 KiB
C++
561 lines
17 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: AuClock.cpp
|
|
Date: 2021-6-13
|
|
Author: Reece
|
|
Note: Screw it, std::chrono has been widly shilled at C++11s answer to all these painful macros, asm linkage, and all the other bullshit that pulling clock counters entails.
|
|
Semantics can and will continue to change over time. I remember when, in 2016 or something like that, msvcs implementation of chrono kept changing in minor ways.
|
|
|
|
However, every platform that remotely pretends to support a C++ toolchain has a chrono high performance clock, and any PC-like platform that uses clang and C++ more than likely uses a vendor hacked liblibc++ stl.
|
|
The following should be portable enough. Worst case scenario, you're on a platform with a platform-specific high res clock function you could hack into here alongside portable timezone-unaware timegm/mktime functions linked somewhere else.
|
|
|
|
There is so much quirky shit one has to deal with when relying on timestamp/cycle counters (cycle to ~time pred, plus positive delta, sometimes inlined assembly), it's just not worth it to
|
|
implement a CNTVCT_EL0 / RDSC / related interface ourselves.
|
|
|
|
I'll wave the white flag and use the STL in here for.now
|
|
***/
|
|
#include <Source/RuntimeInternal.hpp>
|
|
#include "AuClock.hpp"
|
|
#include "Time.hpp"
|
|
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
|
|
// TODO (Reece): ....
|
|
|
|
// benchmarking:
|
|
// https://github.com/microsoft/STL/issues/2085
|
|
struct steady_clock_fast
|
|
{ // wraps QueryPerformanceCounter
|
|
using rep = long long;
|
|
using period = std::nano;
|
|
using duration = std::chrono::nanoseconds;
|
|
using time_point = _CHRONO time_point<steady_clock_fast>;
|
|
static constexpr bool is_steady = true;
|
|
|
|
_NODISCARD static time_point now() noexcept
|
|
{ // get current time
|
|
static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot
|
|
const long long _Ctr = _Query_perf_counter();
|
|
static_assert(period::num == 1, "This assumes period::num == 1.");
|
|
// Instead of just having "(_Ctr * period::den) / _Freq",
|
|
// the algorithm below prevents overflow when _Ctr is sufficiently large.
|
|
// It assumes that _Freq * period::den does not overflow, which is currently true for nano period.
|
|
// It is not realistic for _Ctr to accumulate to large values from zero with this assumption,
|
|
// but the initial value of _Ctr could be large.
|
|
// 10 MHz is a very common QPC frequency on modern PCs. Optimizing for
|
|
// this specific frequency can double the performance of this function by
|
|
// avoiding the expensive frequency conversion path.
|
|
if (_Freq == 10000000)
|
|
{
|
|
return time_point(duration(_Ctr * 100));
|
|
}
|
|
else
|
|
{
|
|
const long long _Whole = (_Ctr / _Freq) * period::den;
|
|
const long long _Part = (_Ctr % _Freq) * period::den / _Freq;
|
|
return time_point(duration(_Whole + _Part));
|
|
}
|
|
}
|
|
};
|
|
|
|
// ~3.0741 seconds
|
|
using high_res_clock = steady_clock_fast;
|
|
|
|
// holy fuck, we're keeping this
|
|
// ~2x improvement
|
|
|
|
#else
|
|
|
|
// ~6.07 seconds
|
|
using high_res_clock = std::chrono::high_resolution_clock;
|
|
|
|
#endif
|
|
|
|
using sys_clock = std::chrono::system_clock;
|
|
using steady_clock = std::chrono::steady_clock;
|
|
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
#define timegm _mkgmtime
|
|
#endif
|
|
|
|
static sys_clock::duration gEpoch;
|
|
static sys_clock::duration gUnixDelta;
|
|
|
|
static auto InitEpoch()
|
|
{
|
|
std::tm start{0, 15, 10, 29, 7, 101, 0, 0, 0};
|
|
auto epoch = sys_clock::from_time_t(timegm(&start)).time_since_epoch();
|
|
|
|
std::tm unixStart{};
|
|
unixStart.tm_mday = 1;
|
|
unixStart.tm_year = 70;
|
|
|
|
// dont care what the spec says, you can't trust some ms stls
|
|
// sys_clock can have its own epoch for all we care
|
|
auto nixEpoch = sys_clock::from_time_t(timegm(&unixStart)).time_since_epoch();
|
|
|
|
gUnixDelta = epoch - nixEpoch;
|
|
gEpoch = epoch;
|
|
return 0;
|
|
}
|
|
|
|
static auto ___ = InitEpoch();
|
|
|
|
template<typename T>
|
|
static inline T NormalizeEpoch(T sysEpoch)
|
|
{
|
|
return sysEpoch - gEpoch;
|
|
}
|
|
|
|
template<typename T>
|
|
static inline T DecodeEpoch(T auroraEpoch)
|
|
{
|
|
return auroraEpoch + gEpoch;
|
|
}
|
|
|
|
template<typename Clock_t, typename Duration_t>
|
|
static auto TimeFromDurationSinceEpoch(Duration_t in)
|
|
{
|
|
auto duration = std::chrono::duration_cast<typename Clock_t::duration>(in);
|
|
return std::chrono::time_point<Clock_t>(DecodeEpoch(duration));
|
|
}
|
|
|
|
template<typename Duration_t>
|
|
static time_t CalculateTimeT(AuUInt64 in)
|
|
{
|
|
return sys_clock::to_time_t(TimeFromDurationSinceEpoch<sys_clock>(Duration_t(in)));
|
|
}
|
|
|
|
namespace Aurora::Time
|
|
{
|
|
AUKN_SYM time_t SToCTime(AuInt64 time)
|
|
{
|
|
return CalculateTimeT<std::chrono::seconds>(time);
|
|
}
|
|
|
|
AUKN_SYM time_t NSToCTime(AuInt64 time)
|
|
{
|
|
return CalculateTimeT<std::chrono::nanoseconds>(time);
|
|
}
|
|
|
|
AUKN_SYM time_t MSToCTime(AuInt64 time)
|
|
{
|
|
return CalculateTimeT<std::chrono::milliseconds>(time);
|
|
}
|
|
|
|
AUKN_SYM AuInt64 CurrentClock()
|
|
{
|
|
return NormalizeEpoch(sys_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 CurrentClockMS()
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 CurrentClockNS()
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(NormalizeEpoch(sys_clock::now().time_since_epoch())).count();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 SteadyClock()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return _Query_perf_counter();
|
|
#endif
|
|
|
|
return SteadyClockNS() / (1000000000ull / SteadyClockJiffies());
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 SteadyClockMS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_MONOTONIC, &spec) == 0)
|
|
{
|
|
return AuSToMS<AuUInt64>(spec.tv_sec) + AuNSToMS<AuUInt64>(spec.tv_nsec);
|
|
}
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(high_res_clock::now().time_since_epoch()).count();
|
|
#endif
|
|
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 SteadyClockNS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_MONOTONIC, &spec) == 0)
|
|
{
|
|
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec;
|
|
}
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(high_res_clock::now().time_since_epoch()).count();
|
|
#endif
|
|
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 CTimeToMS(time_t time)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(NormalizeEpoch(sys_clock::from_time_t(time).time_since_epoch())).count();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ThreadClockNS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_THREAD_CPUTIME_ID, &spec) == 0)
|
|
{
|
|
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec;
|
|
}
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
FILETIME creation, exit, kernel, user;
|
|
if (::GetThreadTimes(GetCurrentThread(), &creation, &exit, &kernel, &user))
|
|
{
|
|
// i dont want to measure kernel and driver overhead under benchmarks, i dont think
|
|
// im going to consider kernel time = syscalls = ipc to another sandbox/process/thread until i have a reason to change this
|
|
// primary use case: microbenchmarks that dont care for external noise
|
|
ULARGE_INTEGER ullUser;
|
|
ullUser.LowPart = user.dwLowDateTime;
|
|
ullUser.HighPart = user.dwHighDateTime;
|
|
return ullUser.QuadPart * 100ull;
|
|
}
|
|
#endif
|
|
|
|
return HighResClockNS();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ThreadClockMS()
|
|
{
|
|
return AuNSToMS<AuUInt64>(ThreadClockNS());
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ThreadClock()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
FILETIME creation, exit, kernel, user;
|
|
if (::GetThreadTimes(GetCurrentThread(), &creation, &exit, &kernel, &user))
|
|
{
|
|
ULARGE_INTEGER ull;
|
|
ull.LowPart = user.dwLowDateTime;
|
|
ull.HighPart = user.dwHighDateTime;
|
|
return ull.QuadPart;
|
|
}
|
|
#endif
|
|
|
|
return ThreadClockNS() / (1000000000ull / ThreadClockJiffies());
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ThreadClockJiffies()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return 1000000000ull / 100u;
|
|
#endif
|
|
|
|
return HighResClockJiffies();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ProcessClockNS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0)
|
|
{
|
|
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec;
|
|
}
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
FILETIME creation, exit, kernel, user;
|
|
if (::GetProcessTimes(GetCurrentThread(), &creation, &exit, &kernel, &user))
|
|
{
|
|
ULARGE_INTEGER ullUser;
|
|
{
|
|
ullUser.LowPart = user.dwLowDateTime;
|
|
ullUser.HighPart = user.dwHighDateTime;
|
|
}
|
|
|
|
ULARGE_INTEGER ullKernel;
|
|
{
|
|
ullKernel.LowPart = kernel.dwLowDateTime;
|
|
ullKernel.HighPart = kernel.dwHighDateTime;
|
|
}
|
|
return (ullUser.QuadPart + ullKernel.QuadPart) * 100ull;
|
|
}
|
|
#endif
|
|
|
|
return HighResClockNS();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ProcessClockMS()
|
|
{
|
|
return AuNSToMS<AuUInt64>(ProcessClockNS());
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ProcessClock()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
FILETIME creation, exit, kernel, user;
|
|
|
|
if (::GetProcessTimes(GetCurrentThread(), &creation, &exit, &kernel, &user))
|
|
{
|
|
ULARGE_INTEGER ullUser;
|
|
{
|
|
ullUser.LowPart = user.dwLowDateTime;
|
|
ullUser.HighPart = user.dwHighDateTime;
|
|
}
|
|
|
|
ULARGE_INTEGER ullKernel;
|
|
{
|
|
ullKernel.LowPart = kernel.dwLowDateTime;
|
|
ullKernel.HighPart = kernel.dwHighDateTime;
|
|
}
|
|
return ullUser.QuadPart + ullKernel.QuadPart;
|
|
}
|
|
#endif
|
|
|
|
return ProcessClockNS() / (1000000000ull / ProcessClockJiffies());
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ProcessClockJiffies()
|
|
{
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
return 1000000000ull / 100u;
|
|
#endif
|
|
|
|
return HighResClockJiffies();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 ConvertAuroraToUnixMS(AuInt64 in)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(in) + gUnixDelta).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 ConvertAuroraToUnixNS(AuInt64 in)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::nanoseconds(in) + gUnixDelta).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 ConvertUnixToAuroraMS(AuInt64 in)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::milliseconds(in) - gUnixDelta).count();
|
|
}
|
|
|
|
AUKN_SYM AuInt64 ConvertUnixToAuroraNS(AuInt64 in)
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::nanoseconds(in) - gUnixDelta).count();
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ConvertInternalToAuroraEpochMS(AuUInt64 in)
|
|
{
|
|
static AuInt64 epochDelta = 0;
|
|
|
|
if (epochDelta == 0)
|
|
{
|
|
epochDelta = CurrentClockMS() - HighResClockMS();
|
|
}
|
|
|
|
return epochDelta + in;
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 ConvertInternalToAuroraEpochNS(AuUInt64 in)
|
|
{
|
|
static AuInt64 epochDelta = 0;
|
|
|
|
if (epochDelta == 0)
|
|
{
|
|
epochDelta = CurrentClockNS() - HighResClockNS();
|
|
}
|
|
|
|
return epochDelta + in;
|
|
}
|
|
|
|
AUKN_SYM double CPUFrequencyDeltaNS()
|
|
{
|
|
static double frequency = 0;
|
|
if (frequency != 0)
|
|
{
|
|
return frequency;
|
|
}
|
|
return frequency = (static_cast<double>(high_res_clock::period::num) / static_cast<double>(high_res_clock::period::den) * 1'000'000'000.f);
|
|
}
|
|
|
|
AUKN_SYM double CPUFrequencyDeltaMS()
|
|
{
|
|
static double frequency = 0;
|
|
if (frequency != 0)
|
|
{
|
|
return frequency;
|
|
}
|
|
return frequency = (static_cast<double>(high_res_clock::period::num) / static_cast<double>(high_res_clock::period::den) * 1'000.f);
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 SteadyClockJiffies()
|
|
{
|
|
static AuUInt64 frequency = 0;
|
|
if (frequency != 0)
|
|
{
|
|
return frequency;
|
|
}
|
|
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
return frequency = _Query_perf_frequency();
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_getres(CLOCK_MONOTONIC, &spec) == 0)
|
|
{
|
|
if (spec.tv_nsec && !spec.tv_sec)
|
|
{
|
|
return frequency = 1000000000ull / spec.tv_nsec;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return frequency = static_cast<double>(steady_clock::period::den) / static_cast<double>(steady_clock::period::num);
|
|
}
|
|
|
|
AUKN_SYM AuUInt64 HighResClockJiffies()
|
|
{
|
|
static AuUInt64 frequency = 0;
|
|
if (frequency != 0)
|
|
{
|
|
return frequency;
|
|
}
|
|
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
return frequency = _Query_perf_frequency();
|
|
#endif
|
|
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_getres(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0)
|
|
{
|
|
if (spec.tv_nsec && !spec.tv_sec)
|
|
{
|
|
return frequency = 1000000000ull / spec.tv_nsec;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return frequency = static_cast<double>(high_res_clock::period::den) / static_cast<double>(high_res_clock::period::num);
|
|
}
|
|
|
|
AUKN_SYM tm ToCivilTime(AuInt64 time, bool UTC)
|
|
{
|
|
std::tm ret {};
|
|
auto timet = MSToCTime(time);
|
|
if (UTC)
|
|
{
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
auto tm = gmtime_s(&ret, &timet);
|
|
#else
|
|
auto tm = gmtime_r(&timet, &ret);
|
|
#endif
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
SysAssert(!tm, "couldn't convert civil time");
|
|
#else
|
|
SysAssert(tm, "couldn't convert civil time");
|
|
#endif
|
|
|
|
}
|
|
else
|
|
{
|
|
#if defined(AURORA_COMPILER_MSVC)
|
|
if (localtime_s(&ret, &timet))
|
|
#else
|
|
if (!localtime_r(&timet, &ret))
|
|
#endif
|
|
{
|
|
SysPushErrorGeneric("Couldn't convert local civil time");
|
|
return ToCivilTime(time, true);
|
|
}
|
|
}
|
|
tm _;
|
|
_.CopyFrom(ret);
|
|
return _;
|
|
}
|
|
|
|
AUKN_SYM AuInt64 FromCivilTime(const tm &time, bool UTC)
|
|
{
|
|
::tm tm;
|
|
time_t timet;
|
|
|
|
time.CopyTo(tm);
|
|
|
|
if (UTC)
|
|
{
|
|
tm.tm_isdst = 0;
|
|
timet = timegm(&tm);
|
|
}
|
|
else
|
|
{
|
|
tm.tm_isdst = -1; // out of the 2 crts i've bothered to check, out of 3, this is legal
|
|
timet = mktime(&tm);
|
|
}
|
|
|
|
if ((timet == 0) || (timet == -1))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(NormalizeEpoch(std::chrono::system_clock::from_time_t(timet).time_since_epoch())).count();
|
|
}
|
|
|
|
AUKN_SYM tm NormalizeCivilTimezone(const Time::tm &time, ETimezoneShift shift)
|
|
{
|
|
if ((time.tm_isdst == 0) && (shift == ETimezoneShift::eUTC))
|
|
{
|
|
return time;
|
|
}
|
|
|
|
return ToCivilTime(FromCivilTime(time, shift == ETimezoneShift::eUTC));
|
|
}
|
|
|
|
#pragma region TO_DEPRECATE
|
|
// [soon to be] @deprecated / was used by benchmarks to measure thread time
|
|
AUKN_SYM AuUInt64 HighResClock()
|
|
{
|
|
return high_res_clock::now().time_since_epoch().count();
|
|
}
|
|
|
|
// [soon to be] @deprecated / was used by benchmarks to measure thread time
|
|
AUKN_SYM AuUInt64 HighResClockMS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0)
|
|
{
|
|
return AuSToMS<AuUInt64>(spec.tv_sec) + AuNSToMS<AuUInt64>(spec.tv_nsec);
|
|
}
|
|
#endif
|
|
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(high_res_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
// [soon to be] @deprecated / was used by benchmarks to measure thread time
|
|
AUKN_SYM AuUInt64 HighResClockNS()
|
|
{
|
|
#if defined(AURORA_IS_POSIX_DERIVED)
|
|
::timespec spec {};
|
|
if (::clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &spec) == 0)
|
|
{
|
|
return AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(spec.tv_sec)) + (AuUInt64)spec.tv_nsec;
|
|
}
|
|
#endif
|
|
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(high_res_clock::now().time_since_epoch()).count();
|
|
}
|
|
#pragma endregion TO_DEPRECATE
|
|
} |