rdtscp的“半栅栏”行为是怎么回事?

时间:2018-09-04 03:53:08

标签: performance assembly x86 microbenchmark rdtsc

多年来,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位。

0 个答案:

没有答案