我正在constant_tsc
和nonstop_tsc
的CPU上运行此测试
$ 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速率是常数。
- 这是正在漂移的船上时钟吗?它肯定不会以这种速度漂移吧?
- 这种漂移的原因是什么?
- 我能做些什么来保持它们同步吗(除了在步骤2中频繁地重新计算
_start_tsc
和_start_clock_time
之外)?
3条答案
按热度按时间ki0zmccv1#
OP中出现漂移的原因(至少在我的机器上)是TSC每ns的滴答数偏离其原始值
_ticks_per_ns
。以下结果来自此机器:cat /proc/cpuinfo
显示constant_tsc
与nonstop_tsc
标志.viewRates.cc 运行www.example.com来查看计算机上当前的TSC每ns计数:
rdtscp.h:
请参阅:英特尔的ia-32-ia-64-benchmark-code-execution-paper
viewRates.cc:
linearExtrapolator.cc 可以运行重新创建OP的实验:
linearExtrapolator.cc:
下面是运行
viewRates
后紧接着运行linearExtrapolator
的输出:viewRates
输出显示,每ns的TSC滴答数随时间快速减少,与上图中的其中一个急剧下降相对应。linearExtrapolator
输出与OP中一样,显示clock_gettime()
报告的经过ns以及通过使用在开始时间获得的_ticks_per_ns
== 2.8069831264将经过的TSC滴答转换为经过的ns而获得的经过的ns。elapsed ticks
、ns_diff
,我使用10 s窗口重新运行TSC滴答/ns计算;这将打印出当前的tsc ticks per ns
比率。可以看出,从viewRates
输出中观察到的每ns减少的TSC滴答的趋势在linearExtrapolator
的整个运行期间持续。将
elapsed ticks
除以_ticks_per_ns
并减去相应的elapsed ns
得到ns_diff
,例如:(84211534141 / 2.8069831264)- 30000747550 = -20667。但这不是0,主要是由于每纳秒TSC滴答数的漂移。如果我们使用从最后10秒间隔获得的每纳秒2.80698015186滴答数的值,结果将是:(84211534141 / 2.80698015186)- 30000747550 = 11125。在最后10 s间隔内累积的额外误差-20667 - -10419 = -10248,在该间隔内使用正确的TSC每ns滴答值时几乎消失:(84211534141 - 56141027929)/ 2.80698015186 -(30000747550 - 20000496849)= 349个单位。如果linearExtrapolator在TSC ticks/ns保持恒定时运行,则精度将受到(常数)
_ticks_per_ns
已经被确定,然后取例如几个估计值的中值是值得的。如果_ticks_per_ns
偏离固定的十亿分之40,预期每10秒约400 ns的恒定漂移,因此ns_diff
将每10秒增长/收缩400。genTimeSeriesofRates.cc 可用于生成上述图的数据:genTimeSeriesofRates.cc:
2ledvvac2#
TSC和
CLOCK_MONOTONIC
之间的关系不是一成不变的。即使您根据CLOCK_MONOTONIC
“校准”TSC,校准几乎在完成后就过期了!它们无法长期保持同步的原因:
CLOCK_MONOTONIC
受NTP时钟速率调整的影响。NTP将不断检查网络时间,并巧妙地减慢或加快系统时钟以匹配网络时间。这将导致真实CLOCK_MONOTONIC
频率中出现某种振荡模式,因此您的校准将始终略有偏差。尤其是下次NTP应用速率调整时。您可以与CLOCK_MONOTONIC_RAW
进行比较以消除此影响。CLOCK_MONOTONIC
和TSC几乎可以肯定是基于 * 完全不同的底层振荡器 *。人们常说,现代操作系统使用TSC来计时,但这只是为了将小的“本地”偏移应用于某些其他底层低速运行时钟,以提供非常精确的时间(例如,“慢速时间”可以在每次定时器滴答声时更新,然后TSC用于在定时器滴答声之间进行插值)。它是较慢的底层时钟(类似于HPET或APIC时钟),它决定CLOCK_MONOTONIC
的长期行为。然而,TSC本身是一个独立的自由运行时钟,其频率来自芯片组/主板上不同位置的不同振荡器,并且会有不同的自然波动(特别是对温度变化不同响应)。在上述两种情况中,更基本的是(2):这意味着即使没有任何种类的NTP调整(或者如果您使用不受NTP调整影响的时钟),如果底层时钟基于不同的物理振荡器,您也会看到随时间的漂移。
hc2pp10m3#
这是正在漂移的船上时钟吗?它肯定不会以这种速度漂移吧?
不,它们不应该漂移
这种漂移的原因是什么?
运行操作系统的NTP服务或类似服务。它们会影响clock_gettime(CLOCK_REALTIME,...);
我能做些什么来保持它们同步吗(除了在步骤2中频繁地重新计算_start_tsc和_start_clock_time之外)?是的,你可以缓解这个问题。
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毫秒一天。这是一个伟大的精度为CPU时钟。3毫秒一天是一个1秒一年。