TL;DR
rdtscp 和 lfence/rdtsc 在 Intel 处理器上具有完全相同的上游序列化属性。在具有调度序列化lfence 的 AMD 处理器上,两个序列也具有相同的上游序列化属性。对于后面的指令,lfence/rdtsc 序列中的rdtsc 可以与后面的指令同时被调度执行。如果您还想精确地为这些后面的指令计时,那么这种行为可能是不可取的。这通常不是问题,因为只要不存在结构性危害,保留站调度程序就会优先考虑较旧的微指令进行调度。在lfence 退休后,rdtsc 微指令将是 RS 中最古老的,可能没有结构性危害,因此它们将立即被派出(可能与一些后来的微指令一起)。您也可以在rdtsc 之后添加lfence。
英特尔手册 V2 对rdtscp (强调我的)有以下说明:
RDTSCP 指令不是序列化指令,但它确实
等到所有先前的指令都已执行并且所有先前的指令
负载是全局可见的。但它不等待以前的商店
为了全局可见,后续指令可能会在执行读取操作之前开始执行。
这里的“读取操作”部分是指读取时间戳计数器。这表明rdtscp 在内部像lfence 一样工作,然后是rdtsc + 阅读IA32_TSC_AUX。也就是说,首先执行lfence,然后执行从寄存器中读取的两次(可能同时执行)。
在大多数支持这些指令的 Intel 和 AMD 处理器上,lfence/rdtsc 的微指令数量比rdtscp 略多。 Agner's tables 中提到的lfence uops 的数量是针对lfence 指令背靠背执行的情况,这使得lfence 看起来被解码为较少数量的 uops(1 或 2 ) 而不是单个lfence 实际解码为(5 或 6 微秒)。通常,lfence 不使用其他背靠背lfences。这就是lfence/rdtsc 比rdtscp 包含更多微指令的原因。 Agner 的表格还显示,在某些处理器上,rdtsc 和 rdtscp 具有相同数量的微指令,我不确定这是否正确。 rdtscp 比 rdtsc 拥有一个或多个微指令更有意义。也就是说,延迟可能比微指令数量的差异更重要,因为这会直接影响测量开销。
在可移植性方面,rdtsc 比rdtscp 更老; rdtsc 最初在 Pentium 处理器上得到支持,而支持 rdtscp 的第一批处理器于 2005-2006 年发布(参见:What is the gcc cpu-type that includes support for RDTSCP?)。但目前使用的大多数 Intel 和 AMD 处理器都支持rdtscp。比较两个序列的另一个维度是,rdtscp 比rdtsc 多污染一个寄存器(即ECX)。
总而言之,如果您不关心阅读IA32_TSC_AUX MSR,那么您应该选择其中一个并没有什么特别大的理由。我会在不支持它的处理器上使用rdtscp 并回退到lfence/rdtsc(或lfence/rdtsc/lfence)。如果您想要最大的计时精度,请使用Memory latency measurement with time stamp counter 中讨论的方法。
作为Andreas Abel pointed out,您仍然需要在最后一个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似乎是正确的。
手册对两条指令使用不同的术语(例如,“在本地完成”与“全局可见”的负载),但描述的行为似乎是相同的。
我在这个答案的其余部分假设是这样。
但是rdtscp 是一条指令,而lfence + rdtscp 是两条,这使得lfence 成为配置代码的一部分。
尽管lfence 在后端执行资源方面应该是轻量级的(它只是一个标记),但它仍然占用前端资源(两个微指令?)和 ROB 中的一个插槽。
rdtscp 被解码为由于它能够读取IA32_TSC_AUX,所以它的uop数量更多,因此在节省前端(部分)资源的同时,它占用了更多的后端。
如果 TSC 的读取首先(或同时)使用处理器 ID 完成,则此额外的微指令仅与后续代码相关。
这可能是为什么它在最后而不是在基准测试开始时使用的原因(额外的微指令会影响代码)。
这足以使一些微架构基准产生偏差/复杂化。
您无法避免 lfence after 和 rdtsc(p),但您可以通过 rdtscp 避免 before。
这对于第一个rdtsc 来说似乎是不必要的,因为前面的lfence 无论如何都没有被分析。
最后使用rdtscp 的另一个原因是它(根据英特尔)旨在检测到不同 CPU 的迁移(这就是为什么它也以原子方式加载IA32_TSC_AUX),所以在配置文件的末尾您可能需要检查代码是否未安排到另一个 CPU。
用户模式软件可以使用 RDTSCP 来检测在连续读取 TSC 之间是否发生了 CPU 迁移。
当然,这需要在之前阅读过IA32_TSC_AUX(以便进行比较),因此在分析代码之前应该有一个rdpid 或rdtscp。
如果可以不使用ecx,则第一个rdtsc 也可以是rdtscp(但见上文),否则(而不是在分析代码中存储处理器ID),可以使用rdpid首先(因此,在分析代码周围有一个 rdtsc + rdtscp 对)。
这对ABA problem 开放,所以我认为英特尔在这方面没有优势(除非我们限制自己的代码足够短,最多可以重新安排一次)。
编辑
正如 PeterCordes 指出的那样,从 经过时间 度量的角度来看,迁移 A->B->A 不是问题,因为参考时钟是相同的。
有关rdtsc(p) 未完全序列化原因的更多信息:Why isn't RDTSC a serializing instruction?
。