我试图了解使用RDTSC/RDTSCP测量时间时使用围栏的正确方法。关于SO的几个与此相关的问题已经得到了详尽的解答。我经历了其中的一些。我也阅读了有关同一主题的这篇非常有用的文章:
http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf

但是,在另一个在线博客中,有一个在x86上使用LFENCE代替CPUID的示例。我想知道LFENCE如何防止早期的商店污染RDTSC的测量结果。
例如。

<Instr A>
LFENCE/CPUID
RDTSC
<Code to be benchmarked>
LFENCE/CPUID
RDTSC

在上述情况下,LFENCE确保所有较早的加载在完成之前完成(因为SDM说:LFENCE指令无法通过较早的读取。)。但是,较早的商店(例如Instr A是一家商店)呢?我了解为什么CPUID可以工作,因为它是一个序列化指令,而LFENCE却不能。

我发现的一种解释是在Intel SDM VOL 3A第8.3节中,以下脚注:

LFENCE确实为指令排序提供了一些保证。在本地完成所有先前的指令之前,它不会执行,并且直到LFENCE完成之前,才有后续的指令开始执行。

因此,本质上LFENCE就像MFENCE。在那种情况下,为什么我们需要两个单独的指令LFENCE和MFENCE?

我可能错过了一些东西。

提前致谢。

最佳答案

关键点是引用的句子“直到所有先前的指令在本地完成后才执行”的副词 local

我无法找到完整的“完整本地”英特尔手册集的清晰定义,我的猜测在下面进行了解释。

为了在本地完成一条指令,必须对它的输出进行计算,并且该指令可用于其依赖关系链中更下游的其他指令。
此外,该指令的任何副作用都必须在内核内部可见。

为了全局完成一条指令,其副作用必须对其他系统组件(如其他CPU)可见。

如果我们不限定“完整性”的种类,那么我们所说的“完整性”通常意味着它不在乎或隐含在上下文中。

对于在本地和全局完成的许多指令来说,都是一样的。
例如,对于负载,为了在本地完成,必须从内存或缓存中获取一些数据。
这与全局完成相同,因为如果不先从内存层次结构中读取数据,就无法将加载标记为完成。

但是对于一家商店,情况就不同了。

英特尔处理器具有存储缓冲区来处理对内存的写操作,该手册的第11章10.3节介绍了这一点:



因此,可以通过将其放入存储缓冲区来在本地完成存储,从核心的角度来看,写入就像是一直到内存一样。
在特定情况下,来自商店同一核心的负载甚至可以读回该值(这称为商店转发)。

但是,要在全局范围内完成存储,则需要从存储缓冲区中清空存储。

最后必须添加序列化指令来耗尽存储缓冲区:



完成介绍后,让我们看看lfencemfencesfence的作用:



因此lfence是较弱的序列化形式,不会消耗存储缓冲区,因为它可以在本地有效地序列化指令,因此必须先完成所有加载,然后才能完成。
sfence仅序列化存储,它基本上不允许进程执行任何其他存储,直到sfence停用。它还会耗尽存储缓冲区。
mfence而不是两者的简单组合,因为它不是经典意义上的序列化,它也是sfence,它也防止将来执行加载。

首先引入sfence,然后再引入另外的rdtsc来实现对内存顺序的更细粒度的控制,可能一文不值。

最后,我习惯于关闭两个lfence指令之间的ojit_code指令,以确保不可能对“backward”和“forward”进行重新排序。
但是,我对这种技术的健全性很有把握。

10-06 06:32