So I wrote a little C++-program that measures if there's a drift of RDTSC:
#if defined(_WIN32)
#define NOMINMAX
#include <Windows.h>
#elif defined(__unix__)
#include <time.h>
#include <pthread.h>
#endif
#include <iostream>
#include <cstdint>
#include <vector>
#include <cmath>
#include <string>
#include <charconv>
#include <thread>
#include <cstring>
#include <csignal>
#include <semaphore>
#include <limits>
#if defined(_MSC_VER)
#include <intrin.h>
#elif defined(__GNUC__)
#include <x86intrin.h>
#endif
using namespace std;
int main()
{
static binary_semaphore semStop( false );
signal( SIGINT, []( int ) { semStop.release( 1 ); } );
#if defined(_WIN32)
if( !SetThreadAffinityMask( GetCurrentThread(), 1 ) )
return EXIT_FAILURE;
#elif defined(__unix__)
cpu_set_t cpuSet;
CPU_ZERO(&cpuSet);
CPU_SET(0, &cpuSet);
if( pthread_setaffinity_np( pthread_self(), sizeof cpuSet, &cpuSet ) )
return EXIT_FAILURE;
#endif
auto getSecs = []() -> double
{
#if defined(_WIN32)
FILETIME ft;
GetSystemTimeAsFileTime( &ft );
return (int64_t)((uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime) / 1.0e7;
#elif defined(__unix__)
timespec t;
clock_gettime( CLOCK_REALTIME, &t );
return (int64_t)t.tv_sec + (int64_t)t.tv_nsec / 1.0e9;
#endif
};
vector<uint64_t> tscLog;
int64_t drift = 0;
uint64_t sumDrift = 0;
for( ; ; )
{
// ensure a fresh timeslice which we'll give
// up very soon that we won't be preempted
this_thread::yield();
auto waitNext = [&]() -> double
{
double lastTime = getSecs(), nextTime;
while( (nextTime = getSecs()) == lastTime );
return nextTime;
};
double begin = waitNext();
uint64_t tsc = __rdtsc();
if( semStop.try_acquire_for( 1s ) )
break;
double end = waitNext();
tsc = __rdtsc() - tsc;
double secs = end - begin;
uint64_t tscTicksPerSec = (int64_t)((double)(int64_t)tsc / secs);
size_t logSizeBefore = tscLog.size();
if( logSizeBefore )
{
int64_t lastDrift = tscTicksPerSec - tscLog.back();
drift += lastDrift,
sumDrift += abs( lastDrift );
}
tscLog.emplace_back( tscTicksPerSec );
cout << tscLog.size() << "s: " << tscTicksPerSec;
if( logSizeBefore )
cout << ", d: " << drift << ", ad: " << (int64_t)((int64_t)sumDrift / (double)(ptrdiff_t)logSizeBefore) << endl;
else
cout << endl;
}
auto getAvg = [&]() -> double
{
double avg = 0.0;
for( int64_t logEntry : tscLog )
avg += (double)logEntry;
return avg / (ptrdiff_t)tscLog.size();
};
auto getStdDev = [&]( double avg )
{
auto sqr = []( double d ) { return d * d; };
double sum = 0.0;
for( int64_t logEntry : tscLog )
sum += sqr( (double)logEntry - avg );
return sqrt( sum / (ptrdiff_t)tscLog.size() );
};
auto getDrift = [&]() -> int64_t
{
if( tscLog.size() <= 1 )
return numeric_limits<int64_t>::min();
int64_t drift = 0;
for( ptrdiff_t i = 0; i < (ptrdiff_t)tscLog.size() - 1; ++i )
drift += (int64_t)tscLog[i + 1] - (int64_t)tscLog[i];
return drift;
};
auto fmtDouble = []( double d ) -> string
{
char str[32];
to_chars_result tcr = to_chars( str, str + sizeof str, d, chars_format::fixed );
if( tcr.ec != errc() )
return string();
return string( str, tcr.ptr );
};
cout << tscLog.size() << "s" << endl;
double avg = getAvg();
cout << "avg: " << fmtDouble( avg ) << endl;
double dev = getStdDev( avg );
cout << "dev: " << fmtDouble( dev ) << " / " << fmtDouble( 100.0 * dev / avg ) << "%" << endl;
}
The program runs until you press Control C and shows the numer of timestamp-ticks per second, the summed up drift ("d: ") and the average drift so far (summed up absolute drift-differences, "ad: ") and calculates the average clock and the standard deviation of the clock at the end. The measurements aren't precise under Windows but very precise under Linux. On my Linux-PC, a Ryzen 7 1800X on an ASRock AB350 Pro4, the drift reported increasingly by the program is almost zero clock cycles after 40min. The drift slowly varies from a slight minus range symmetical to a plus-range (max. 4.000 clock cycles) around zero. There's for sure no clock-drift of 1-2s per day as @Alois Kraus mentioned.