多年来,x86 CPU支持rdtsc
指令,该指令读取当前CPU的“时间戳计数器”。该计数器的确切定义已随时间变化,但是在最近的CPU上,它是相对于壁钟时间以固定频率递增的计数器,因此,它对于构建快速,准确的时钟或测量时间非常有用。只需一小段代码即可。
关于rdtsc
指令的一个重要事实并未与周围的代码以任何特殊方式进行排序。像大多数指令一样,它可以相对于与之没有依赖关系的其他指令自由地重新排序。这实际上是“正常”的操作,对于大多数指令来说,这只是使CPU速度提高的一种几乎看不见的方式(这只是一种漫长的说法out-of-order execution)。
对于rdtsc
来说很重要,因为这意味着您可能没有在计时您希望计时的代码。例如,给定以下序列 1 :
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
。像rdtsc
一样,它返回时间戳计数器的读数,而这个家伙还做更多的事情:它通过时间戳读取原子地读取特定于内核的MSR值。在大多数操作系统上,它包含一个核心ID值。我认为这个想法是,在每个内核可能具有不同TSC偏移量的CPU上,可以使用此值来适当地将返回值调整为实时。
太好了。
rdtscp
引入的另一件事是无序执行方面的半围栏:
从manual:
RDTSCP指令不是序列化指令,但它确实 等到所有先前的指令已执行并且所有先前的 加载是全局可见的。1但它不等待先前的存储 全局可见,随后的说明可能会开始 在执行读取操作之前执行。
因此,这就好比将lfence
放在rdtscp
之前,而不是之后。这种半防护行为的意义何在?如果您想要通用的时间戳并且不关心指令的顺序,那么您想要的就是不受约束的行为。如果要将其用于计时短代码段,则半栅栏行为仅对第二(最终)读数有用,而对初始读数则无用,因为栅栏位于“错误”的一面(实际上,您希望两侧都有栅栏,但最里面的栅栏可能是最重要的。
这种半围墙有什么作用?
1 在这种情况下,我忽略了计数器的高32位。