【问题标题】:x86-64 usage of LFENCELFENCE 的 x86-64 使用
【发布时间】:2016-09-23 23:32:25
【问题描述】:

我试图了解使用 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?

我可能遗漏了一些东西。

提前致谢。

【问题讨论】:

    标签: assembly x86-64 cpu-architecture atomic


    【解决方案1】:

    正如您正确地观察到的,这是序列化的问题。关于你的问题

    为什么我们需要两条单独的指令 LFENCE 和 MFENCE?

    在“5.6.4 -”部分的Intel SDM 中回答 SSE2 Cacheability Control and Ordering Instructions”:

    LFENCE 序列化加载操作
    MFENCE 序列化加载和存储操作

    所以LFENCE 可能被使用,因为MFENCE 对于RDTSC 来说不是必需的。

    【讨论】:

      【解决方案2】:

      关键是引用句子中的副词locally直到所有先前的指令都在本地完成后才会执行”。

      整套英特尔手册我找不到“本地完成”的明确定义,我的猜测解释如下。


      为了在本地完成,一条指令必须对其输出进行计算,并可供其依赖链中的其他指令使用。 此外,该指令的任何副作用都必须在核心内可见。

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

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


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

      对于一个商店但是情况是不同的。

      Intel 处理器有一个 Store Buffer 来处理对内存的写入,来自手册 3 的第 11.10 章:

      Intel 64 和 IA-32 处理器将每次写入(存储)到内存中临时存储在存储缓冲区中。存储缓冲区 通过允许处理器继续执行指令而无需执行指令来提高处理器性能 等到写入内存和/或缓存完成。它还允许延迟写入以更有效地使用 内存访问总线周期数。

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

      要在全局范围内完成,但是需要从 Store Buffer 中排空存储。

      最后是强制添加存储缓冲区被序列化指令耗尽:

      在以下情况下,存储缓冲区的内容总是排空到内存中:
      • (仅限 P6 和更新的处理器系列)执行序列化指令时。
      • (仅限 Pentium III 和更新的处理器系列)使用 SFENCE 指令订购存储时。
      • (仅限 Pentium 4 和更新的处理器系列)使用 MFENCE 指令订购存储时。


      介绍完毕,让我们看看lfencemfencesfence 做了什么:

      在所有先前的指令在本地完成之前,LFENCE 不会执行,并且在 LFENCE 完成之前,后面的指令不会开始执行。

      MFENCE 对在 MFENCE 指令之前发出的所有从内存加载和存储到内存指令执行序列化操作。 MFENCE 不序列化指令流。

      SFENCE 对在 SFENCE 指令之前发出的所有存储到内存指令执行序列化操作。

      所以lfence 是一种较弱的序列化形式,它不会耗尽存储缓冲区,因为它在本地有效地序列化指令,在它完成之前必须完成所有加载。

      sfence 仅序列化存储,它基本上不允许进程执行任何存储,直到sfence 退休。它还会耗尽存储缓冲区。

      mfence 不是两者的简单组合,因为它不是经典意义上的序列化,它是一个sfence,也可以防止将来执行加载。


      首先引入sfence,而后引入其他两个以实现对内存排序的更精细控制可能毫无价值。

      最后,我习惯于在两条 lfence 指令之间关闭一条 rdtsc 指令,以确保不可能重新排序“向后”和“向前”。
      但是我确信这种技术的可靠性。

      【讨论】:

      • 感谢您的精心回复。因此,如果我理解正确,LFENCE 不会耗尽存储缓冲区,但它确实使 CPU 等到所有先前的加载和存储指令在本地完成。在这种情况下,我们不能在基准代码结束时依赖它进行时间(RDTSC)测量,对吧?因为,您希望在测量时间之前确保已将写入设为全局(刷新到内存)。谢谢。
      • lfence 可用于衡量如果您不想等待商店变得全球可见。写入内存需要很多周期,如果你不仔细考虑缓存,它会给出不一致的结果。除非您想明确地测试它们,否则通常会在基准测试中写入内存。在这种情况下,使用 lfencesfence 或不覆盖所需寄存器的序列化指令。
      • 有道理。非常感谢。
      • @MargaretBloom 我相信“本地完成”只是意味着数据已从缓存中加载并返回到加载缓冲区。通常,只要 TLB / 缓存端口可用,就会允许加载继续进行。 LFENCE 可以防止这种情况,并确保在它退出之前的所有负载。 LFENCe 在加载缓冲区的头部时会消失。通常,商店可以在收到 TLB 特权后立即退休。 SFENCE 确保在 SFENCE 消失之前不会选择查询后的商店,这意味着首先选择它之前的所有商店。
      • 我心里最有可能的是,当SFENCE排在队列的最前面时,它会导致stores延迟4-5个周期,以确保之前的non-line-fill -缓冲区存储已提交,但也确保没有等待写入缓存的逻辑核心的行填充缓冲区。这是一种理论。
      猜你喜欢
      • 2013-12-17
      • 2019-04-26
      • 2015-02-20
      • 1970-01-01
      • 1970-01-01
      • 2011-11-17
      • 2015-01-20
      • 2020-07-23
      • 1970-01-01
      相关资源
      最近更新 更多