7

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

4

2 回答 2

14

这个想法并非不合理,但它不适合用户模式应用程序,正如@Basile 所建议的那样,有更好的选择。

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

不变的 TSC 将在所有 ACPI P-、C- 中以恒定速率运行。和 T 状态。
这是向前发展的架构行为。在具有不变 TSC 支持的处理器上,操作系统可以将 TSC 用于挂钟计时器服务(而不是 ACPI 或 HPET 计时器)。TSC 读取效率更高,并且不会产生与环转换或访问平台资源相关的开销。

但是,必须小心。

TSC 并不总是不变的

在较旧的处理器中,TSC 在每个内部时钟周期递增,它不是挂钟。
引用英特尔

对于 Pentium M 处理器(系列 [06H],型号 [09H, 0DH]);对于 Pentium 4 处理器、Intel Xeon 处理器(系列 [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,因为您只对增量感兴趣。一旦您知道 ART 的频率,您就可以从ART_Value增量中获取经过的时间。这以k * B的形式给出,其中k是 MSR 中的常数,MSR_PLATFORM_INFOB100Mhz 或 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 保持同步。

于 2017-02-12T17:27:42.567 回答
7

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

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

在 Linux 上,读取time(7)然后使用clock_gettime(2)非常快(并且不涉及任何慢速系统调用),这要归功于vdso(7)

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

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

于 2017-02-12T16:22:16.427 回答