使用rdtsc计算系统时间
Calculate system time using rdtsc
假设我 CPU 中的所有核心都具有相同的频率,从技术上讲,我可以每毫秒左右为每个核心同步系统时间和时间戳计数器对。然后基于当前核心我是 运行,我可以获取当前 rdtsc
值并使用 tick delta 除以核心频率我能够估计自上次同步以来经过的时间系统时间和时间戳计数器对,并在没有当前线程系统调用开销的情况下推断出当前系统时间(假设不需要锁来检索上述数据)。
这在理论上很好用,但在实践中我发现有时我得到的滴答声比我预期的要多,也就是说,如果我的核心频率是 1GHz 并且我在 1 毫秒前获取了系统时间和时间戳计数器对,我希望看到一个增量在大约 10^6 个刻度的刻度中,但实际上我发现它可以在 10^6 和 10^7 之间的任何位置。
我不确定哪里出了问题,任何人都可以分享他对如何使用 rdtsc
计算系统时间的想法吗?我的主要 objective 是为了避免每次我想知道系统时间时都需要执行系统调用,并且能够在用户 space 中执行计算,这将给我一个很好的估计(目前我定义了一个很好的估计结果,它与实际系统时间相差 10 微秒。
不要这样做 - 直接使用 RDTSC
机器指令 - (因为你的 OS 调度程序可以在任意时刻重新安排其他线程或进程,或者减慢时钟)。使用您的图书馆或 OS 提供的功能。
My main objective is to avoid the need to perform system call every time I want to know the system time
在 Linux 上阅读 time(7) then use clock_gettime(2) which is really quick (and does not involve any slow system call) thanks to vdso(7)。
在符合 C++11 的实现中,只需使用标准 <chrono>
header. And standard C has clock(3)(提供微秒精度)。两者都将使用 Linux 足够好的时间测量功能(因此 间接 vdso
)
我上次测量 clock_gettime
每次调用通常花费不到 4 纳秒。
这个想法不无道理,但它不适合 user-mode 应用程序,正如 所建议的那样,有更好的选择。
英特尔本身建议使用 TSC 作为 wall-clock:
The invariant TSC will run at a constant rate in all ACPI P-, C-. and T-states.
This is the architectural behaviour
moving forward. On processors with invariant TSC support, the OS may use the TSC for wall clock timer services
(instead of ACPI or HPET timers). TSC reads are much more efficient and do not incur the overhead associated with
a ring transition or access to a platform resource.
但是,必须小心。
TSC 并不总是不变的
在旧处理器中,TSC 在每个 内部 时钟周期递增,它不是 wall-clock。
引用英特尔
For Pentium M processors (family [06H], models [09H, 0DH]); for Pentium 4 processors, Intel Xeon processors
(family [0FH], models [00H, 01H, or 02H]); and for P6 family processors: the time-stamp counter increments
with every internal processor clock cycle.
The internal processor clock cycle is determined by the current core-clock to bus-clock ratio. Intel®
SpeedStep® technology transitions may also impact the processor clock.
如果您只有一个变体 TSC,则跟踪时间的测量结果不可靠。
不过,不变的 TSC 还是有希望的。
TSC 未按照品牌字符串建议的频率增加
仍然引用英特尔
the time-stamp counter increments at a constant rate. That rate may be set by the
maximum core-clock to bus-clock ratio of the processor or may be set by the maximum resolved frequency at
which the processor is booted. The maximum resolved frequency may differ from the processor base
frequency.
On certain processors, the TSC frequency may not be the same
as the frequency in the brand string.
你不能简单的拿处理器盒子上写的频率。
见下文。
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_INFO
中的常数B 是 100Mhz 或 133+1/3 Mhz,具体取决于处理器。
正如 所指出的,从 Skylake 开始,ART crystal 频率不再是总线频率。
可以找到由英特尔维护的实际值 in the turbostat.c file。
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 内核对即使在 non-deep 睡眠状态下也会重置 TSC 有一些评论。
上下文切换会使测量中毒
你对此无能为力。
这实际上会阻止您使用 TSC time-keeping。
假设我 CPU 中的所有核心都具有相同的频率,从技术上讲,我可以每毫秒左右为每个核心同步系统时间和时间戳计数器对。然后基于当前核心我是 运行,我可以获取当前 rdtsc
值并使用 tick delta 除以核心频率我能够估计自上次同步以来经过的时间系统时间和时间戳计数器对,并在没有当前线程系统调用开销的情况下推断出当前系统时间(假设不需要锁来检索上述数据)。
这在理论上很好用,但在实践中我发现有时我得到的滴答声比我预期的要多,也就是说,如果我的核心频率是 1GHz 并且我在 1 毫秒前获取了系统时间和时间戳计数器对,我希望看到一个增量在大约 10^6 个刻度的刻度中,但实际上我发现它可以在 10^6 和 10^7 之间的任何位置。
我不确定哪里出了问题,任何人都可以分享他对如何使用 rdtsc
计算系统时间的想法吗?我的主要 objective 是为了避免每次我想知道系统时间时都需要执行系统调用,并且能够在用户 space 中执行计算,这将给我一个很好的估计(目前我定义了一个很好的估计结果,它与实际系统时间相差 10 微秒。
不要这样做 - 直接使用 RDTSC
机器指令 - (因为你的 OS 调度程序可以在任意时刻重新安排其他线程或进程,或者减慢时钟)。使用您的图书馆或 OS 提供的功能。
My main objective is to avoid the need to perform system call every time I want to know the system time
在 Linux 上阅读 time(7) then use clock_gettime(2) which is really quick (and does not involve any slow system call) thanks to vdso(7)。
在符合 C++11 的实现中,只需使用标准 <chrono>
header. And standard C has clock(3)(提供微秒精度)。两者都将使用 Linux 足够好的时间测量功能(因此 间接 vdso
)
我上次测量 clock_gettime
每次调用通常花费不到 4 纳秒。
这个想法不无道理,但它不适合 user-mode 应用程序,正如
英特尔本身建议使用 TSC 作为 wall-clock:
The invariant TSC will run at a constant rate in all ACPI P-, C-. and T-states.
This is the architectural behaviour moving forward. On processors with invariant TSC support, the OS may use the TSC for wall clock timer services (instead of ACPI or HPET timers). TSC reads are much more efficient and do not incur the overhead associated with a ring transition or access to a platform resource.
但是,必须小心。
TSC 并不总是不变的
在旧处理器中,TSC 在每个 内部 时钟周期递增,它不是 wall-clock。
引用英特尔
For Pentium M processors (family [06H], models [09H, 0DH]); for Pentium 4 processors, Intel Xeon processors (family [0FH], models [00H, 01H, or 02H]); and for P6 family processors: the time-stamp counter increments with every internal processor clock cycle.
The internal processor clock cycle is determined by the current core-clock to bus-clock ratio. Intel® SpeedStep® technology transitions may also impact the processor clock.
如果您只有一个变体 TSC,则跟踪时间的测量结果不可靠。 不过,不变的 TSC 还是有希望的。
TSC 未按照品牌字符串建议的频率增加
仍然引用英特尔
the time-stamp counter increments at a constant rate. That rate may be set by the maximum core-clock to bus-clock ratio of the processor or may be set by the maximum resolved frequency at which the processor is booted. The maximum resolved frequency may differ from the processor base frequency.
On certain processors, the TSC frequency may not be the same as the frequency in the brand string.
你不能简单的拿处理器盒子上写的频率。
见下文。
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_INFO
中的常数B 是 100Mhz 或 133+1/3 Mhz,具体取决于处理器。
正如
可以找到由英特尔维护的实际值 in the turbostat.c file。
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 内核对即使在 non-deep 睡眠状态下也会重置 TSC 有一些评论。
上下文切换会使测量中毒
你对此无能为力。
这实际上会阻止您使用 TSC time-keeping。