(rdtsc + lfence + rdtsc) 和 (rdtsc + rdtscp) 在测量执行时间方面有什么区别吗?

Is there any difference in between (rdtsc + lfence + rdtsc) and (rdtsc + rdtscp) in measuring execution time?

据我所知,处理器在运行时顺序方面与 rdtsc 和 rdtscp 指令的主要区别在于执行是否等到所有先前的指令都在本地执行。

换句话说,就是lfence + rdtsc = rdtscp,因为在rdtsc指令之前的lfence使得后面的rdtsc在所有前面的指令在本地完成后执行。

但是,我看到一些示例代码在测量开始时使用 rdtsc,在测量结束时使用 rdtscp。使用两个 rdtsc 和 rdtsc + rdtscp 之间有什么区别吗?

    lfence
    rdtsc
    lfence
    ...
    ...
    ...
    lfence
    rdtsc
    lfence
    lfence
    rdtsc
    lfence
    ...
    ...
    ...
    rdtscp
    lfence

TL;DR

rdtscplfence/rdtsc 在 Intel 处理器上具有完全相同的上游序列化属性。在具有调度序列化 lfence 的 AMD 处理器上,两个序列也具有相同的上游序列化属性。对于后面的指令,可以调度lfence/rdtsc序列中的rdtsc与后面的指令同时执行。如果您还想精确计时这些后面的指令,则可能不需要这种行为。这通常不是问题,因为保留站调度程序会优先调度较旧的 uops,只要不存在结构性风险即可。在 lfence 退休后,rdtsc uops 将是 RS 中最老的,可能没有结构性危险,因此它们将被立即派遣(可能与一些后来的 uops 一起)。您也可以在 rdtsc 之后放置一个 lfence

英特尔手册 V2 对 rdtscp 的描述如下(强调我的):

The RDTSCP instruction is not a serializing instruction, but it does wait until all previous instructions have executed and all previous loads are globally visible. But it does not wait for previous stores to be globally visible, and subsequent instructions may begin execution before the read operation is performed.

这里的"read operation"部分是指读取时间戳计数器。这表明 rdtscp 内部工作方式类似于 lfence 后跟 rdtsc + 阅读 IA32_TSC_AUX。也就是说,首先执行lfence,然后执行对寄存器的两次读取(可能同时执行)。

在大多数支持这些指令的 Intel 和 AMD 处理器上,lfence/rdtsc 的微指令数略大于 rdtscpAgner's tables中提到的lfence uops的数量是针对lfence指令连续执行的情况,这使得lfence被解码为比单个 lfence 实际解码为(5 或 6 微指令)的微指令数量更少(1 或 2)。通常,lfence 与其他背靠背 lfence 一起使用。这就是 lfence/rdtscrdtscp 包含更多微指令的原因。 Agner 的表格还显示,在某些处理器上,rdtscrdtscp 具有相同的微指令数,我不确定这是否正确。 rdtscprdtsc 有一个或多个微指令更有意义。也就是说,延迟可能比 uops 数量的差异更重要,因为这是直接影响测量开销的因素。

在便携性方面,rdtscrdtscp老; rdtsc 首先在奔腾处理器上得到支持,而第一个支持 rdtscp 的处理器是在 2005-2006 年发布的(参见:What is the gcc cpu-type that includes support for RDTSCP?)。但目前使用的大多数 Intel 和 AMD 处理器都支持 rdtscp。比较两个序列的另一个维度是 rdtscprdtsc.

多污染一个寄存器(即 ECX

总而言之,如果您不关心阅读 IA32_TSC_AUX MSR,则没有特别重要的理由让您选择其中之一。我会使用 rdtscp 并在不支持它的处理器上回退到 lfence/rdtsc(或 lfence/rdtsc/lfence)。如果您想要最大的计时精度,请使用 .

中讨论的方法

一样,您仍然需要在最后一个 rdtsc(p) 之后添加一个 lfence,因为它没有排序 w.r.t。后续说明:

lfence                    lfence
rdtsc      -- ALLOWED --> B
B                         rdtsc

rdtscp     -- ALLOWED --> B
B                         rdtscp

这也是addressed in the manuals


关于rdtscp的使用,我认为它是一个紧凑的lfence + rdtsc似乎是正确的。
手册对这两条指令使用不同的术语(例如 "completed locally" 与 "globally visible" 用于加载),但所描述的行为似乎是相同的。
在这个答案的其余部分,我假设是这样。

但是 rdtscp 是一条指令,而 lfence + rdtscp 是两条指令,使 lfence 成为分析代码的一部分。
尽管lfence在后端执行资源方面应该是轻量级的(它只是一个标记)它仍然占用前端资源(两个uops?)和ROB中的一个插槽。
rdtscp由于可以读取IA32_TSC_AUX,所以被解码成更多的uops,所以在节省前端(部分)资源的同时,占用了更多的后端。
如果 TSC 的读取首先(或同时)与处理器 ID 一起完成,那么这个额外的 uops 仅与后续代码相关。
这可能是为什么它在基准测试的最后而不是在开始时使用的原因(额外的 uops 会影响代码)。 这足以 bias/complicate 一些微架构基准。

你无法避免 lfence afterrdtsc(p) 但你可以避免 before with rdtscp.
这对于第一个 rdtsc 似乎是不必要的,因为前面的 lfence 无论如何都没有被分析。


最后使用 rdtscp 的另一个原因是(根据英特尔的说法)它是为了检测到不同 CPU 的迁移(这就是为什么它也自动加载 IA32_TSC_AUX), 所以在配置代码的末尾,您可能需要检查该代码是否尚未安排到另一个 CPU。

User mode software can use RDTSCP to detect if CPU migration has occurred between successive reads of the TSC.

当然,这需要之前阅读 IA32_TSC_AUX(以便进行比较),因此在分析代码之前应该有一个 rdpidrdtscp
如果可以不使用 ecx,第一个 rdtsc 也可以是 rdtscp(但见上文),否则(而不是在分析代码中存储处理器 ID), rdpid 可以首先使用(因此,在分析代码周围有一个 rdtsc + rdtscp 对)。

这对 ABA problem 是开放的,所以我认为英特尔在这方面没有优势(除非我们限制自己的代码足够短,最多可以重新安排一次)。

编辑 正如 PeterCordes 指出的那样,从 已用时间 度量的角度来看,迁移 A->B->A 不是问题,因为参考时钟是相同的。


关于为什么 rdtsc(p) 没有完全序列化的更多信息:为什么 RDTSC 不是序列化指令? .