На процессоре с constant_tsc и nonstop_tsc почему мое время смещается?

Я запускаю этот тест на процессоре с constant_tsc а также nonstop_tsc

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

Шаг 1: Рассчитаем частоту тиков tsc:

Я рассчитываю _ticks_per_ns в качестве медианы по ряду наблюдений. я использую rdtscp обеспечить выполнение заказа.

static const int trials = 13;
std::array<double, trials> 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];

Шаг 2: Рассчитайте время начала настенных часов и 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

Шаг 3: Создать функцию, которая может возвращать время настенных часов из TSC

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

Шаг 4: Запуск в цикле, печать времени на стене с clock_gettime и из 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);
}

Выход:

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

Вопросы:

Быстро становится очевидным, что время, рассчитанное этими двумя способами, быстро расходится.

Я предполагаю, что с constant_tsc а также nonstop_tsc что скорость TSC является постоянной.

  • Это бортовые часы, которые дрейфуют? Конечно, это не дрейфует с такой скоростью?

  • В чем причина этого дрейфа?

  • Могу ли я что-нибудь сделать, чтобы синхронизировать их (кроме очень частого пересчета _start_tsc а также _start_clock_time на шаге 2)

7

Решение

Причина дрейфа, видимого в OP, по крайней мере, на моей машине, заключается в том, что TSC тиков за нс дрейфует от своего первоначального значения _ticks_per_ns, Следующие результаты были с этой машины:

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 шоу constant_tsc а также nonstop_tsc флаги.

Пример тиков TSC за нс от clock_gettime () CLOCK_REALTIME против времени.

viewRates.cc может быть запущен для просмотра текущих тиков TSC за нс на машине:

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 <time.h>
#include <unistd.h>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#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<double>(elapsed_ticks) / elapsed_ns;

cout << elapsed_ticks << " " << elapsed_ns << " " << std::setprecision(12) << rate << endl;
}
}

linearExtrapolator.cc можно запустить, чтобы заново создать эксперимент OP:

linearExtrapolator.cc:

#include <time.h>
#include <unistd.h>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <array>
#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<double, RUNS> 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<double>(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<uint64_t>(diff / _ticks_per_ns);
}

Вот вывод из серии viewRates сразу же после 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

viewRates Вывод показывает, что тики TSC за нс довольно быстро уменьшаются со временем, соответствующим одному из этих крутых падений на графике выше. linearExtrapolator выходные данные показывают, как в OP, разницу между истекшими ns как сообщается clock_gettime()и прошедшие ns, полученные путем преобразования прошедших тиков TSC в прошедшие ns, используя _ticks_per_ns == 2.8069831264 получено во время запуска. Скорее чем sleep(10); между каждым отпечатком из elapsed ns, elapsed ticks, ns_diffЯ перезапускаю расчет тиков TSC за нс с помощью окна 10 с; это распечатывает текущий tsc ticks per ns соотношение. Можно видеть, что тенденция снижения TSC тиков за нс наблюдается из viewRates выход продолжается на протяжении всего цикла linearExtrapolator,

Разделение elapsed ticks от _ticks_per_ns и вычитая соответствующий elapsed ns дает ns_diffнапример: (84211534141 / 2.8069831264) — 30000747550 = -20667. Но это не 0 в основном из-за дрейфа тиков TSC за нс. Если бы мы использовали значение 2.80698015186 тиков в нс, полученное из интервала последних 10 с, результат был бы: (84211534141 / 2.80698015186) — 30000747550 = 11125. Дополнительная ошибка, накопленная за этот интервал последних 10 с, -20667 — -10419 = — 10248, почти исчезает, когда для этого интервала используется правильное значение TSC тиков на нс: (84211534141 — 56141027929) / 2.80698015186 — (30000747550 — 20000496849) = 349.

Если бы linearExtrapolator был запущен в то время, когда тики TSC на нс были постоянными, точность была бы ограничена тем, насколько хорошо (постоянная) _ticks_per_ns был бы определен, и тогда он заплатил бы, например, за медиану нескольких оценок. Если _ticks_per_ns был выключен фиксированными 40 частями на миллиард, постоянный дрейф около 400 нс каждые 10 секунд, поэтому ns_diff будет расти / уменьшаться на 400 каждые 10 секунд.

genTimeSeriesofRates.cc может использоваться для генерации данных для графика, как указано выше:
genTimeSeriesofRates.cc:

#include <time.h>
#include <unistd.h>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <array>
#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<long, RUNS> elapsed_ns, elapsed_ticks;
array<double, RUNS> 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<double>(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;
}
3

Другие решения

Это бортовые часы, которые дрейфуют? Конечно, это не дрейфует с такой скоростью?
Нет, они не должны дрейфовать

В чем причина этого дрейфа?
Служба NTP или аналогичная, которая запускает вашу ОС. Они влияют на clock_gettime (CLOCK_REALTIME, …);

Могу ли я что-нибудь сделать, чтобы сохранить их синхронизацию (кроме очень частого пересчета _start_tsc и _start_clock_time на шаге 2)?
Да, вы можете облегчить проблему.

1 Вы можете попробовать использовать CLOCK_MONOTONIC вместо CLOCK_REALTIME.

2 Вы можете рассчитать разницу как линейную функцию от времени и применить ее для компенсации дрейфа. Но это не будет очень надежно, потому что службы времени не корректируют время как линейную функцию. Но это даст вам больше точности. Периодически вы можете делать переналадку.


Некоторое отклонение вы можете получить, потому что вы рассчитываете _ticks_per_ns не точно. Вы можете проверить это, запустив свою программу несколько раз. Если результаты не могут быть воспроизведены, это означает, что вы неправильно вычислили _ticks_per_ns. Лучше использовать метод статистики, чем просто среднее значение.


Также обратите внимание, _ticks_per_ns вы рассчитываете с помощью CLOCK_MONOTONIC, который связан с TSC.

Далее вы используете CLOCK_REALTIME. Это обеспечивает системное время. Если в вашей системе есть NTP или аналогичная служба, время будет скорректировано.

Ваша разница составляет около 2 микросекунд в минуту. Это 0,002 * 24 * 60 = 2,9 миллисекунды в день. Это отличная точность для тактовой частоты процессора. 3 мс в день это 1 секунда в год.

образ

0

Отношения между ТСК и чем-то вроде CLOCK_MONOTONIC не будет точно неизменным. Даже если вы «откалибруете» TSC против CLOCK_MONOTONICкалибровка устареет почти сразу после ее завершения!

Причины, по которым они не будут синхронизированы в долгосрочной перспективе:

  1. CLOCK_MONOTONIC зависит от настроек тактовой частоты NTP. NTP будет постоянно проверять сетевое время и слегка замедлять или ускорять системные часы, чтобы соответствовать сетевому времени. Это приводит к некоторой колебательной модели в истинном CLOCK_MONOTONIC частота, и поэтому ваша калибровка всегда будет слегка отключена, особенно в следующий раз, когда NTP применяет регулировку скорости. Вы могли бы сравнить с CLOCK_MONOTONIC_RAW устранить этот эффект.
  2. CLOCK_MONOTONIC и TSC почти наверняка основаны на совершенно разные основные генераторы. Часто говорят, что современные ОС используют TSC для отсчета времени, но это только для того, чтобы применить небольшое «локальное» смещение к некоторым другим базовым медленным часам, чтобы обеспечить очень точное время (например, «медленное время» может обновлять каждый таймер, а затем TSC используется для интерполяции между таймерами). Именно медленные базовые часы (что-то вроде часов HPET или APIC) определяют долгосрочное поведение CLOCK_MONOTONIC, Сам TSC, однако, представляет собой независимые часы автономной работы, получающие свою частоту от другого генератора, расположенного в другом месте на чипсете / материнской плате, и будут отличаться естественными колебаниями (в частности, разной реакцией на изменения температуры).

Это (2) является более фундаментальным из двух выше: это означает, что даже без каких-либо настроек NTP (или если вы используете часы, которые им не подчиняются), вы увидите дрейф с течением времени, если основной часы основаны на разных физических генераторах.

0
По вопросам рекламы [email protected]