使用rdtsc计算系统时间



假设我的CPU中的所有内核都有相同的频率,从技术上讲,我可以每毫秒左右同步每个内核的系统时间和时间戳计数器对。然后基于我运行的当前内核,我可以取当前的rdtsc值,使用tick-delta除以核心频率,我可以估计自上次同步系统时间和时间戳计数器对以来经过的时间,并在没有来自当前线程的系统调用开销的情况下推断当前系统时间(假设不需要锁来检索上述数据)。这在理论上很有效,但在实践中,我发现有时我得到的滴答声比我预期的要多,也就是说,如果我的核心频率是1GHz,并且我在1毫秒前使用了系统时间和时间戳计数器对,我希望在滴答声中看到一个大约为10^6的增量,但实际上我发现它可以在10^6到10^7之间。我不知道出了什么问题,有人能分享他对如何使用rdtsc计算系统时间的想法吗?我的主要目标是避免每次我想知道系统时间时都需要执行系统调用,并能够在用户空间中执行计算,这将给我一个很好的估计(目前我将一个好的估计定义为与真实系统时间间隔为10微秒的结果。

这个想法并不不健全,但它不适合用户模式的应用程序,正如@Basile所建议的那样,有更好的替代方案。

英特尔自己建议将TSC用作挂钟:

不变TSC将在所有ACPI p-、C-中以恒定速率运行。和T形状态
这是体系结构行为继续前进。在具有不变TSC支持的处理器上,操作系统可以将TSC用于墙上时钟定时器服务(而不是ACPI或HPET计时器)。TSC读取效率高得多,并且不会产生与环形转换或对平台资源的访问。

但是,必须小心。

TSC并不总是不变的

在较旧的处理器中,TSC在每个内部时钟周期递增,它不是墙上的时钟
报价英特尔

对于奔腾M处理器([06H]系列、[09H、0DH]型号);适用于奔腾4处理器、英特尔至强处理器([0FH]系列,型号[00H、01H或02H]);对于P6系列处理器:时间戳计数器递增每个内部处理器时钟周期。

内部处理器时钟周期由当前核心时钟与总线时钟之比决定。英特尔®SpeedStep®技术转换也可能影响处理器时钟。

如果您只有一个变体TSC,则跟踪时间的测量不可靠。不过,不变的TSC还是有希望的。

TSC不会按照品牌字符串上建议的频率递增

仍在引用英特尔

时间戳计数器以恒定速率递增。该比率可由处理器的最大核心时钟与总线时钟之比,或者可以通过在处理器被引导。最大分辨频率可能与处理器基础不同频率
在某些处理器上,TSC频率可能不相同作为品牌串中的频率。

不能简单地取写在处理器方框上的频率
请参阅下文。

rdtsc未串行化

你需要从上到下依次排列
看看这个。

TSC基于不变时的ART(始终运行定时器)

正确的公式是

TSC_Value = (ART_Value * CPUID.15H:EBX[31:0] )/ CPUID.15H:EAX[31:0] + K

请参阅英特尔手册3第17.15.4节。

当然,您必须为ART_Value求解,因为您是从TSC_Value开始的。您可以忽略K,因为您只对delta感兴趣。根据ART_Valuedelta,一旦您知道ART的频率,您就可以获得所经过的时间。该时间表示为k*B,其中k是MSRMSR_PLATFORM_INFO中的常数,B为100Mhz或133+1/3 Mhz,具体取决于处理器。

正如@BeeOnRope所指出的,从Skylake开始,ART晶体频率不再是总线频率
由英特尔维护的实际值可以在turbostat.c文件中找到。

switch(model) 
{
case INTEL_FAM6_SKYLAKE_MOBILE: /* SKL */
case INTEL_FAM6_SKYLAKE_DESKTOP:    /* SKL */
case INTEL_FAM6_KABYLAKE_MOBILE:    /* KBL */
case INTEL_FAM6_KABYLAKE_DESKTOP:   /* KBL */
crystal_hz = 24000000;  /* 24.0 MHz */
break;
case INTEL_FAM6_SKYLAKE_X:  /* SKX */
case INTEL_FAM6_ATOM_DENVERTON: /* DNV */
crystal_hz = 25000000;  /* 25.0 MHz */
break;
case INTEL_FAM6_ATOM_GOLDMONT:  /* BXT */
crystal_hz = 19200000;  /* 19.2 MHz */
break;
default:
crystal_hz = 0; 
}

当处理器进入深度睡眠时,TSC不会增加

这在单套接字机器上应该不是问题,但Linux内核对即使在非深度睡眠状态下也会重置TSC有一些评论。

上下文切换会破坏测量

你对此无能为力。
这实际上阻止了你与TSC保持时间一致。

不要这样做-直接使用RDTSC机器指令-(因为您的操作系统调度程序可以在任意时刻重新安排其他线程或进程,或者减慢时钟)。使用库或操作系统提供的函数。

我的主要目标是避免每次我想知道系统时间时都需要执行系统调用

在Linux上,读取时间(7)然后使用clock_gettime(2),由于vdso(7),它非常快速(不涉及任何缓慢的系统调用)。

在符合C++11的实现中,只需使用标准的<chrono>头即可。标准C具有时钟(3)(给出微秒精度)。两者都将在Linux上使用足够好的时间测量功能(因此间接vdso)

上次我测量clock_gettime时,每次调用的时间通常不到4纳秒。

最新更新