多年来 x86 CPU 支持rdtsc
指令,读取当前CPU的“时间戳计数器”。该计数器的确切定义随着时间的推移而发生变化,但在最近的 CPU 上,它是一个相对于挂钟时间以固定频率递增的计数器,因此它作为快速、准确的时钟的构建块或测量时间非常有用由小段代码获取。
关于这一点的一个重要事实rdtsc
指令不以任何特殊方式与周围的代码一起排序。与大多数指令一样,它可以相对于与其不存在依赖关系的其他指令自由地重新排序。这实际上是“正常的”,对于大多数指令来说,这只是一种使 CPU 更快的几乎看不见的方式(这只是一种冗长的说法无序执行).
For rdtsc
it is important because it means you might not be timing the code you expect to be timing. For example, given the following sequence1:
rdtsc
mov ecx, eax
mov rdi, [rdi]
mov rdi, [rdi]
rdtsc
你可能会期待rdtsc
测量两个指针追逐加载负载的延迟mov rdi, [rdi]
。然而,在实践中,即使这两个加载都需要查看时间(如果它们在缓存中丢失,则需要 100 秒的周期),您将获得相当小的读数rdtsc
一对。问题是第二个rdtsc
不等待加载完成,它只是无序执行,所以你没有计时你认为的时间间隔。也许两者兼而有之rdtsc
指令实际上甚至在第一次加载开始之前就执行,具体取决于如何执行rdi
是在本示例之前的代码中计算的。
到目前为止,这听起来更像是对一个没有人问过的问题的回答,而不是一个真正的问题,但我正在做到这一点。
您有两个基本用例rdtsc
:
- 作为一个快速时间戳,您通常不关心它如何与周围的代码重新排序,因为无论如何您可能没有关于应该在哪里获取时间戳的指令级概念。
-
作为精确的计时机制,例如在微基准中。在这种情况下,您通常会保护您的rdtsc
从重新订购lfence
操作说明。对于上面的示例,您可能会执行以下操作:
lfence
rdtsc
lfence
mov ecx, eax
...
lfence
rdtsc
确保定时指令(...
)不要逃出计时区域之外,并且还要确保来自时间区域内部的指令不会进入(可能问题不大,但它们可能会与您想要测量的代码竞争资源)。
多年后,英特尔瞧不起我们这些可怜的程序员,想出了一条新指令:rdtscp
. Like rdtsc
它返回时间戳计数器的读数,并且这个家伙做了更多的事情:它使用时间戳读数原子地读取特定于核心的 MSR 值。在大多数操作系统上,这包含一个核心 ID 值。我认为这个值可以用于在每个核心可能具有不同 TSC 偏移量的 CPU 上将返回值正确调整为实时。
Great.
另一件事rdtscp
介绍的是半击剑就乱序执行而言:
来自manual:
RDTSCP 指令不是序列化指令,但它确实
等待直到所有先前的指令都已执行并且所有先前的指令都已执行
负载是全局可见的。1 但它不会等待之前的存储
全局可见,后续指令可能会开始
在执行读操作之前执行。
所以这就像放一个lfence
之前rdtscp
,但不是之后。这种半剑的行为有什么意义呢?如果您想要一个通用时间戳并且不关心指令顺序,那么不受保护的行为就是您想要的。如果您想使用它来计时短代码部分,半围栏行为仅对第二次(最终)读取有用,但对初始读取无效,因为围栏位于“错误”一侧(实际上您想要两侧都有栅栏,但将栅栏放在内侧可能是最重要的)。
这样的半围栏有什么目的呢?
1 I'm ignoring the upper 32-bits of the counter in this case.