Sur un processeur avec constant_tsc et nonstop_tsc, pourquoi mon temps dérive-t-il?

Je nonstop_tsc ce test sur un processeur avec constant_tsc et nonstop_tsc

 $ grep -m 1 ^flags /proc/cpuinfo | sed 's/ /\n/g' | egrep "constant_tsc|nonstop_tsc" constant_tsc nonstop_tsc 

Étape 1: Calculez le taux de ticks de la tsc:

Je calcule _ticks_per_ns comme la médiane sur un certain nombre d’observations. J’utilise rdtscp pour assurer une exécution en ordre.

 static const int sortingals = 13; std::array rates; for (int i = 0; i < trials; ++i) { timespec beg_ts, end_ts; uint64_t beg_tsc, end_tsc; clock_gettime(CLOCK_MONOTONIC, &beg_ts); beg_tsc = rdtscp(); uint64_t elapsed_ns; do { clock_gettime(CLOCK_MONOTONIC, &end_ts); end_tsc = rdtscp(); elapsed_ns = to_ns(end_ts - beg_ts); // calculates ns between two timespecs } while (elapsed_ns < 10 * 1e6); // busy spin for 10ms rates[i] = (double)(end_tsc - beg_tsc) / (double)elapsed_ns; } std::nth_element(rates.begin(), rates.begin() + trials/2, rates.end()); _ticks_per_ns = rates[trials/2]; 

Étape 2: Calculez l’heure de l’horloge murale et tsc

 uint64_t beg, end; timespec ts; // loop to ensure we aren't interrupted between the two tsc reads while (1) { beg = rdtscp(); clock_gettime(CLOCK_REALTIME, &ts); end = rdtscp(); if ((end - beg) <= 2000) // max ticks per clock call break; } _start_tsc = end; _start_clock_time = to_ns(ts); // converts timespec to ns since epoch 

Etape 3: Créez une fonction capable de retourner l’heure de l’horloge murale depuis le tsc

 uint64_t tsc_to_ns(uint64_t tsc) { int64_t diff = tsc - _start_tsc; return _start_clock_time + (diff / _ticks_per_ns); } 

Étape 4: Exécution en boucle, impression du temps clock_gettime partir de clock_gettime et de rdtscp

 // lock the test to a single core cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(6, &mask); sched_setaffinity(0, sizeof(cpu_set_t), &mask); while (1) { timespec utc_now; clock_gettime(CLOCK_REALTIME, &utc_now); uint64_t utc_ns = to_ns(utc_now); uint64_t tsc_ns = tsc_to_ns(rdtscp()); uint64_t ns_diff = tsc_ns - utc_ns; std::cout << "clock_gettime " << ns_to_str(utc_ns) << '\n'; std::cout << "tsc_time " << ns_to_str(tsc_ns) << " diff=" << ns_diff << "ns\n"; sleep(10); } 

Sortie:

 clock_gettime 11:55:34.824419837 tsc_time 11:55:34.824419840 diff=3ns clock_gettime 11:55:44.826260245 tsc_time 11:55:44.826260736 diff=491ns clock_gettime 11:55:54.826516358 tsc_time 11:55:54.826517248 diff=890ns clock_gettime 11:56:04.826683578 tsc_time 11:56:04.826684672 diff=1094ns clock_gettime 11:56:14.826853056 tsc_time 11:56:14.826854656 diff=1600ns clock_gettime 11:56:24.827013478 tsc_time 11:56:24.827015424 diff=1946ns 

Des questions:

Il est rapidement évident que les temps calculés de ces deux manières s’écartent rapidement.

Je suppose que avec constant_tsc et nonstop_tsc , le taux de tsc est constant.

  • Est-ce l’horloge à bord qui dérive? Sûrement, il ne dérive pas à ce rythme?

  • Quelle est la cause de cette dérive?

  • Est-ce que je peux faire quelque chose pour les garder synchronisés (autre que de recalculer très fréquemment _start_tsc et _start_clock_time à l’étape 2)?

Est-ce l’horloge à bord qui dérive? Sûrement, il ne dérive pas à ce rythme?
Non, ils ne doivent pas dériver

Quelle est la cause de cette dérive?
Service NTP ou similaire qui exécute votre système d’exploitation. Ils affectent clock_gettime (CLOCK_REALTIME, …);

Est-ce que je peux faire quelque chose pour les garder synchronisés (autre que de recalculer très fréquemment _start_tsc et _start_clock_time à l’étape 2)? Oui, vous pouvez atténuer le problème.

1 Vous pouvez essayer d’utiliser CLOCK_MONOTONIC au lieu de CLOCK_REALTIME.

2 Vous pouvez calculer la différence sous la forme d’une fonction linéaire à partir de l’heure et l’appliquer pour compenser la dérive. Mais cela ne sera pas très fiable car les services de temps n’ajustent pas l’heure comme une fonction linéaire. Mais cela vous donnera plus de précision. Périodiquement, vous pouvez effectuer un réajustement.


Certaines dérives peuvent être obtenues parce que vous calculez _ticks_per_ns pas avec précision. Vous pouvez le vérifier en exécutant votre programme plusieurs fois. Si les résultats ne sont pas reproductibles, cela signifie que vous calculez incorrectement vos _ticks_per_ns. Il est préférable d’utiliser la méthode statistique alors juste une valeur moyenne.


Veuillez également noter que vous calculez _ticks_per_ns en utilisant CLOCK_MONOTONIC, qui est lié à TSC.

Ensuite, vous utilisez CLOCK_REALTIME. Il fournit l’heure du système. Si votre système dispose d’un service NTP ou similaire, l’heure sera ajustée.

Votre différence est d’environ 2 microsecondes par minute. Il est de 0,002 * 24 * 60 = 2,9 millièmes de secondes par jour. C’est une grande précision pour l’horloge du processeur. 3 ms par jour, c’est une seconde par an.

image

La raison de la dérive observée dans l’OP, au moins sur ma machine, est que le TSC décoche par ns dérive de sa valeur initiale de _ticks_per_ns . Les résultats suivants proviennent de cette machine:

 don@HAL:~/UNIX/OS/3EZPcs/Ch06$ uname -a Linux HAL 4.4.0-81-generic #104-Ubuntu SMP Wed Jun 14 08:17:06 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux don@HAL:~/UNIX/OS/3EZPcs/Ch06$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc 

cat /proc/cpuinfo affiche les indicateurs constant_tsc et nonstop_tsc .

Exemples de ticks TSC par ns de clock_gettime () CLOCK_REALTIME vs. Time.

viewRates.cc peut être exécuté pour voir les Tick TSC actuels par ns sur une machine:

rdtscp.h:

 static inline unsigned long rdtscp_start(void) { unsigned long var; unsigned int hi, lo; __asm volatile ("cpuid\n\t" "rdtsc\n\t" : "=a" (lo), "=d" (hi) :: "%rbx", "%rcx"); var = ((unsigned long)hi << 32) | lo; return (var); } static inline unsigned long rdtscp_end(void) { unsigned long var; unsigned int hi, lo; __asm volatile ("rdtscp\n\t" "mov %%edx, %1\n\t" "mov %%eax, %0\n\t" "cpuid\n\t" : "=r" (lo), "=r" (hi) :: "%rax", "%rbx", "%rcx", "%rdx"); var = ((unsigned long)hi << 32) | lo; return (var); } /*see https://www.intel.com/content/www/us/en/embedded/training/ia-32-ia-64-benchmark-code-execution-paper.html */ 

viewRates.cc:

 #include  #include  #include  #include  #include  #include "rdtscp.h" using std::cout; using std::cerr; using std::endl; #define CLOCK CLOCK_REALTIME uint64_t to_ns(const timespec &ts); // Converts a struct timespec to ns (since epoch). void view_ticks_per_ns(int runs =10, int sleep =10); int main(int argc, char **argv) { int runs = 10, sleep = 10; if (argc != 1 && argc != 3) { cerr << "Usage: " << argv[0] << " [ RUNS SLEEP ] \n"; exit(1); } else if (argc == 3) { runs = std::atoi(argv[1]); sleep = std::atoi(argv[2]); } view_ticks_per_ns(runs, sleep); } void view_ticks_per_ns(int RUNS, int SLEEP) { // Prints out stream of RUNS tsc ticks per ns, each calculated over a SLEEP secs interval. timespec clock_start, clock_end; unsigned long tsc1, tsc2, tsc_start, tsc_end; unsigned long elapsed_ns, elapsed_ticks; double rate; // ticks per ns from each run. clock_getres(CLOCK, &clock_start); cout << "Clock resolution: " << to_ns(clock_start) << "ns\n\n"; cout << " tsc ticks " << "ns " << " tsc ticks per ns\n"; for (int i = 0; i < RUNS; ++i) { tsc1 = rdtscp_start(); clock_gettime(CLOCK, &clock_start); tsc2 = rdtscp_end(); tsc_start = (tsc1 + tsc2) / 2; sleep(SLEEP); tsc1 = rdtscp_start(); clock_gettime(CLOCK, &clock_end); tsc2 = rdtscp_end(); tsc_end = (tsc1 + tsc2) / 2; elapsed_ticks = tsc_end - tsc_start; elapsed_ns = to_ns(clock_end) - to_ns(clock_start); rate = static_cast(elapsed_ticks) / elapsed_ns; cout << elapsed_ticks << " " << elapsed_ns << " " << std::setprecision(12) << rate << endl; } } 

linearExtrapolator.cc peut être exécuté pour recréer l'expérience de l'OP:

linearExtrapolator.cc:

 #include  #include  #include  #include  #include  #include  #include "rdtscp.h" using std::cout; using std::endl; using std::array; #define CLOCK CLOCK_REALTIME uint64_t to_ns(const timespec &ts); // Converts a struct timespec to ns (since epoch). void set_ticks_per_ns(bool set_rate); // Display or set tsc ticks per ns, _ticks_per_ns. void get_start(); // Sets the 'start' time point: _start_tsc[in ticks] and _start_clock_time[in ns]. uint64_t tsc_to_ns(uint64_t tsc); // Convert tsc ticks since _start_tsc to ns (since epoch) linearly using // _ticks_per_ns with origin(0) at the 'start' point set by get_start(). uint64_t _start_tsc, _start_clock_time; // The 'start' time point as both tsc tick number, start_tsc, and as // clock_gettime ns since epoch as _start_clock_time. double _ticks_per_ns; // Calibrated in set_ticks_per_ns() int main() { set_ticks_per_ns(true); // Set _ticks_per_ns as the initial TSC ticks per ns. uint64_t tsc1, tsc2, tsc_now, tsc_ns, utc_ns; int64_t ns_diff; bool first_pass{true}; for (int i = 0; i < 10; ++i) { timespec utc_now; if (first_pass) { get_start(); //Get start time in both ns since epoch (_start_clock_time), and tsc tick number(_start_tsc) cout << "_start_clock_time: " << _start_clock_time << ", _start_tsc: " << _start_tsc << endl; utc_ns = _start_clock_time; tsc_ns = tsc_to_ns(_start_tsc); // == _start_clock_time by definition. tsc_now = _start_tsc; first_pass = false; } else { tsc1 = rdtscp_start(); clock_gettime(CLOCK, &utc_now); tsc2 = rdtscp_end(); tsc_now = (tsc1 + tsc2) / 2; tsc_ns = tsc_to_ns(tsc_now); utc_ns = to_ns(utc_now); } ns_diff = tsc_ns - (int64_t)utc_ns; cout << "elapsed ns: " << utc_ns - _start_clock_time << ", elapsed ticks: " << tsc_now - _start_tsc << ", ns_diff: " << ns_diff << '\n' << endl; set_ticks_per_ns(false); // Display current TSC ticks per ns (does not alter original _ticks_per_ns). } } void set_ticks_per_ns(bool set_rate) { constexpr int RUNS {1}, SLEEP{10}; timespec clock_start, clock_end; uint64_t tsc1, tsc2, tsc_start, tsc_end; uint64_t elapsed_ns[RUNS], elapsed_ticks[RUNS]; array rates; // ticks per ns from each run. if (set_rate) { clock_getres(CLOCK, &clock_start); cout << "Clock resolution: " << to_ns(clock_start) << "ns\n"; } for (int i = 0; i < RUNS; ++i) { tsc1 = rdtscp_start(); clock_gettime(CLOCK, &clock_start); tsc2 = rdtscp_end(); tsc_start = (tsc1 + tsc2) / 2; sleep(SLEEP); tsc1 = rdtscp_start(); clock_gettime(CLOCK, &clock_end); tsc2 = rdtscp_end(); tsc_end = (tsc1 + tsc2) / 2; elapsed_ticks[i] = tsc_end - tsc_start; elapsed_ns[i] = to_ns(clock_end) - to_ns(clock_start); rates[i] = static_cast(elapsed_ticks[i]) / elapsed_ns[i]; } cout << " tsc ticks " << "ns " << "tsc ticks per ns" << endl; for (int i = 0; i < RUNS; ++i) cout << elapsed_ticks[i] << " " << elapsed_ns[i] << " " << std::setprecision(12) << rates[i] << endl; if (set_rate) _ticks_per_ns = rates[RUNS-1]; } constexpr uint64_t BILLION {1000000000}; uint64_t to_ns(const timespec &ts) { return ts.tv_sec * BILLION + ts.tv_nsec; } void get_start() { // Get start time both in tsc ticks as _start_tsc, and in ns since epoch as _start_clock_time timespec ts; uint64_t beg, end; // loop to ensure we aren't interrupted between the two tsc reads while (1) { beg = rdtscp_start(); clock_gettime(CLOCK, &ts); end = rdtscp_end(); if ((end - beg) <= 2000) // max ticks per clock call break; } _start_tsc = (end + beg) / 2; _start_clock_time = to_ns(ts); // converts timespec to ns since epoch } uint64_t tsc_to_ns(uint64_t tsc) { // Convert tsc ticks into absolute ns: // Absolute ns is defined by this linear extrapolation from the start point where //_start_tsc[in ticks] corresponds to _start_clock_time[in ns]. uint64_t diff = tsc - _start_tsc; return _start_clock_time + static_cast(diff / _ticks_per_ns); } 

Voici un résultat d'une série de viewRates immédiatement suivie par linearExtrapolator :

 don@HAL:~/UNIX/OS/3EZPcs/Ch06$ ./viewRates Clock resolution: 1ns tsc ticks ns tsc ticks per ns 28070466526 10000176697 2.8069970538 28070500272 10000194599 2.80699540335 28070489661 10000196097 2.80699392179 28070404159 10000170879 2.80699245029 28070464811 10000197285 2.80699110338 28070445753 10000195177 2.80698978932 28070430538 10000194298 2.80698851457 28070427907 10000197673 2.80698730414 28070409903 10000195492 2.80698611597 28070398177 10000195328 2.80698498942 don@HAL:~/UNIX/OS/3EZPcs/Ch06$ ./linearExtrapolator Clock resolution: 1ns tsc ticks ns tsc ticks per ns 28070385587 10000197480 2.8069831264 _start_clock_time: 1497966724156422794, _start_tsc: 4758879747559 elapsed ns: 0, elapsed ticks: 0, ns_diff: 0 tsc ticks ns tsc ticks per ns 28070364084 10000193633 2.80698205596 elapsed ns: 10000247486, elapsed ticks: 28070516229, ns_diff: -3465 tsc ticks ns tsc ticks per ns 28070358445 10000195130 2.80698107188 elapsed ns: 20000496849, elapsed ticks: 56141027929, ns_diff: -10419 tsc ticks ns tsc ticks per ns 28070350693 10000195646 2.80698015186 elapsed ns: 30000747550, elapsed ticks: 84211534141, ns_diff: -20667 tsc ticks ns tsc ticks per ns 28070324772 10000189692 2.80697923105 elapsed ns: 40000982325, elapsed ticks: 112281986547, ns_diff: -34158 tsc ticks ns tsc ticks per ns 28070340494 10000198352 2.80697837242 elapsed ns: 50001225563, elapsed ticks: 140352454025, ns_diff: -50742 tsc ticks ns tsc ticks per ns 28070325598 10000196057 2.80697752704 elapsed ns: 60001465937, elapsed ticks: 168422905017, ns_diff: -70335 ^C 

La sortie viewRates montre que les ticks TSC par ns diminuent assez rapidement avec le temps correspondant à l’une des fortes baisses du tracé ci-dessus. La sortie linearExtrapolator montre, comme dans l'OP, la différence entre les ns écoulés rapportés par clock_gettime() et les ns écoulés obtenus en convertissant les ticks TSC écoulés en ns écoulés à l'aide de _ticks_per_ns == 2.8069831264 obtenus au début. Plutôt qu'un sleep(10); entre chaque impression de elapsed ns elapsed ticks , ns_diff , je ré-exécute le calcul des ticks TSC par ns en utilisant une fenêtre 10s; ceci imprime le rapport actuel des tsc ticks per ns . On peut voir que la tendance à la diminution des ticks de TSC par ns observée à partir de la sortie de viewRates se poursuit tout au long du cycle de linearExtrapolator .

En divisant un elapsed ticks par _ticks_per_ns et en soustrayant le elapsed ns correspondant, vous ns_diff le ns_diff , par exemple: (84211534141 / 2.8069831264) - 30000747550 = -20667. Mais ce n’est pas dû principalement à la dérive des tics TSC par ns. Si nous avions utilisé une valeur de 2.80698015186 ticks par ns obtenus à partir de l'intervalle des 10 dernières secondes, le résultat serait: (84211534141 / 2.80698015186) - 30000747550 = 11125. L'erreur supplémentaire accumulée pendant cet intervalle de 10 secondes, -20667 - -10419 = - 10248, disparaît presque lorsque le nombre de ticks TSC corrects par valeur ns est utilisé pour cet intervalle: (84211534141 - 56141027929) / 2.80698015186 - (30000747550 - 20000496849) = 349.

Si le linearExtrapolator avait été exécuté à un moment où les ticks TSC par ns avaient été constants, la précision serait limitée par la façon dont le _ticks_per_ns (constant) avait été déterminé, et alors il faudrait payer, par exemple, une médiane de plusieurs estimations. Si le _ticks_per_ns était éteint de 40 parties par milliard, une dérive constante d'environ 400ns toutes les 10 secondes serait attendue, donc ns_diff augmenterait / diminuerait de 400 toutes les 10 secondes.

genTimeSeriesofRates.cc peut être utilisé pour générer des données pour un tracé comme ci-dessus: genTimeSeriesofRates.cc:

 #include  #include  #include  #include  #include  #include  #include "rdtscp.h" using std::cout; using std::cerr; using std::endl; using std::array; double get_ticks_per_ns(long &ticks, long &ns); // Get median tsc ticks per ns, ticks and ns. long ts_to_ns(const timespec &ts); #define CLOCK CLOCK_REALTIME // clock_gettime() clock to use. #define TIMESTEP 10 #define NSTEPS 10000 #define RUNS 5 // Number of RUNS and SLEEP interval used for each sample in get_ticks_per_ns(). #define SLEEP 1 int main() { timespec ts; clock_getres(CLOCK, &ts); cerr << "CLOCK resolution: " << ts_to_ns(ts) << "ns\n"; clock_gettime(CLOCK, &ts); int start_time = ts.tv_sec; double ticks_per_ns; int running_elapsed_time = 0; //approx secs since start_time to center of the sampling done by get_ticks_per_ns() long ticks, ns; for (int timestep = 0; timestep < NSTEPS; ++timestep) { clock_gettime(CLOCK, &ts); ticks_per_ns = get_ticks_per_ns(ticks, ns); running_elapsed_time = ts.tv_sec - start_time + RUNS * SLEEP / 2; cout << running_elapsed_time << ' ' << ticks << ' ' << ns << ' ' << std::setprecision(12) << ticks_per_ns << endl; sleep(10); } } double get_ticks_per_ns(long &ticks, long &ns) { // get the median over RUNS runs of elapsed tsc ticks, CLOCK ns, and their ratio over a SLEEP secs time interval timespec clock_start, clock_end; long tsc_start, tsc_end; array elapsed_ns, elapsed_ticks; array rates; // arrays from each run from which to get medians. for (int i = 0; i < RUNS; ++i) { clock_gettime(CLOCK, &clock_start); tsc_start = rdtscp_end(); // minimizes time between clock_start and tsc_start. sleep(SLEEP); clock_gettime(CLOCK, &clock_end); tsc_end = rdtscp_end(); elapsed_ticks[i] = tsc_end - tsc_start; elapsed_ns[i] = ts_to_ns(clock_end) - ts_to_ns(clock_start); rates[i] = static_cast(elapsed_ticks[i]) / elapsed_ns[i]; } // get medians: std::nth_element(elapsed_ns.begin(), elapsed_ns.begin() + RUNS/2, elapsed_ns.end()); std::nth_element(elapsed_ticks.begin(), elapsed_ticks.begin() + RUNS/2, elapsed_ticks.end()); std::nth_element(rates.begin(), rates.begin() + RUNS/2, rates.end()); ticks = elapsed_ticks[RUNS/2]; ns = elapsed_ns[RUNS/2]; return rates[RUNS/2]; } constexpr long BILLION {1000000000}; long ts_to_ns(const timespec &ts) { return ts.tv_sec * BILLION + ts.tv_nsec; }