在x86 / x86_64处理器上使用LFENCE指令是否有意义?

时间:2013-12-01 19:19:07

标签: assembly x86 x86-64 atomic memory-barriers

通常在互联网上我发现LFENCE在处理器x86中没有任何意义,即它什么都不做,所以相反MFENCE我们绝对无法使用SFENCE,因为MFENCE } = SFENCE + LFENCE = SFENCE + NOP = SFENCE

但如果LFENCE没有意义,那么为什么我们有四种方法可以在x86 / x86_64中实现顺序一致性:

  1. LOAD(没有围栏)和STORE + MFENCE
  2. LOAD(没有围栏)和LOCK XCHG
  3. MFENCE + LOADSTORE(没有围栏)
  4. LOCK XADD(0)和STORE(没有围栏)
  5. 从这里采取:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

    除了Herb Sutter在第34页底部的表演:https://skydrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&wdo=2&authkey=!AMtj_EflYn2507c

    如果LFENCE没有做任何事情,那么方法(3)将具有以下含义:SFENCE + LOAD and STORE (without fence),但在SFENCE之前做LOAD没有意义。即如果LFENCE什么都不做,方法(3)没有意义。

    在x86 / x86_64处理器中是否有任何意义上的指令LFENCE

    解答:

    1。 LFENCE在下面接受的答案中描述的案例中需要。

    2. 方法(3)不应独立查看,而应与之前的命令结合使用。例如,方法(3):

    MFENCE
    MOV reg, [addr1]  // LOAD-1
    MOV [addr2], reg  //STORE-1
    
    MFENCE
    MOV reg, [addr1]  // LOAD-2
    MOV [addr2], reg  //STORE-2
    

    我们可以按如下方式重写方法(3)的代码:

    SFENCE
    MOV reg, [addr1]  // LOAD-1
    MOV [addr2], reg  //STORE-1
    
    SFENCE
    MOV reg, [addr1]  // LOAD-2
    MOV [addr2], reg  //STORE-2
    

    此处SFENCE有助于防止重新排序STORE-1和LOAD-2。为此,STORE-1命令SFENCE刷新Store-Buffer。

3 个答案:

答案 0 :(得分:31)

底线(TL; DR):LFENCE对于内存排序确实似乎毫无用处,但它并没有使SFENCE成为MFENCE的替代品。 "算术"问题中的逻辑不适用。


以下摘自Intel's Software Developers Manual, volume 3,第8.2.2节(2014年9月版325384-052US),与我在another answer中使用的相同

  
      
  • 读取不会与其他读取重新排序。
  •   
  • 写入不会与较旧的读取重新排序。
  •   
  • 写入内存不会与其他写入重新排序,但以下情况除外:      
        
    • 使用CLFLUSH指令执行写入;
    •   
    • 使用非时间移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS和MOVNTPD)执行的流存储(写入);和
    •   
    • 字符串操作(参见第8.2.4.1节)。
    •   
  •   
  • 可以使用较旧的写入对不同位置进行重新排序,但不能将较旧的写入重新排序到同一位置。
  •   
  • 读取或写入不能使用I / O指令,锁定指令或序列化指令重新排序。
  •   
  • 读取不能通过早期的LFENCE和MFENCE指令。
  •   
  • 写入不能通过早期的LFENCE,SFENCE和MFENCE指令。
  •   
  • LFENCE指令无法通过早期读取。
  •   
  • SFENCE指令无法通过更早的写入。
  •   
  • MFENCE指令无法通过早期的读取或写入。
  •   

从这里开始:

  • MFENCE是所有内存类型上所有操作的完整内存栅栏,无论是否为非时态。
  • SFENCE仅阻止写入的重新排序(在其他术语中,它是StoreStore屏障),并且仅与非临时存储和列为例外的其他指令一起使用。
  • LFENCE可防止读取与后续读取和写入重新排序(即它结合了LoadLoad和LoadStore障碍)。但是,前两个项目符号表示LoadLoad和LoadStore障碍始终存在,没有例外。因此,仅LFENCE对内存排序无用。

为了支持最后一项声明,我查看了所有3卷英特尔手册中提到LFENCE的所有地方,并且没有发现任何会说内存需要LFENCE的地方一致性。即使是MOVNTDQA - 迄今为止唯一的非时态加载指令 - 提及MFENCE但未提及LFENCE


更新:查看Why is (or isn't?) SFENCE + LFENCE equivalent to MFENCE?上的答案,了解下方猜测的正确答案

MFENCE是否等同于"总和"其他两个围栏与否是一个棘手的问题。乍一看,在三个fence指令中,只有MFENCE提供StoreLoad屏障,即防止对先前写入的读取进行重新排序。然而,正确的答案需要知道的不仅仅是上述规则;也就是说,所有围栏指令相对于彼此进行排序是很重要的。这使得SFENCE LFENCE序列比仅仅单个效果的联合更强大:此序列还会阻止StoreLoad重新排序(因为加载无法通过LFENCE,而SFENCE无法通过LFENCE SFENCE,无法通过商店) ,因此构成一个完整的记忆围栏(但也见下面的注释(*))。但请注意,此处的顺序很重要,MFENCE ~ SFENCE LFENCE序列不具有相同的协同效应。

但是,虽然可以说LFENCE ~ NOPMFENCE ~ SFENCE,但这并不意味着SFENCE。我故意使用等价(〜)而不是等于(=)来强调算术规则在这里不适用。 LFENCE后跟LFENCE的相互影响有所不同;即使负载没有相互重新排序,也需要SFENCE来防止MFENCE重新排序负载。

(*)说CLFLUSH强于其他两个围栏的组合仍然可能是正确的。特别是,英特尔手册第2卷中的CLFLUSH指令说明" MFENCE仅按CLFLUSH指令排序。不保证可以通过任何其他屏蔽或序列化指令或其他clflush指令进行排序。"

(更新,mfence现在被定义为强排序(就像普通商店一样,所以如果你想稍后阻止加载,你只需要clflushopt),但{ {1}}排序很弱,但sfence可以围隔。)

答案 1 :(得分:7)

考虑以下场景 - 这是关键情况,推测性加载执行在理论上可能会损害顺序一致性

最初[x] = [y] = 0

CPU0:                              CPU1: 
store [x]<--1                      store [y]<--1
load  r1<--[y]                     load r2<--[x]

由于x86允许将早期存储的负载重新排序到不同的地址,因此两个负载都可以返回0。在每个商店之后单独添加一个lfence不会阻止这种情况,因为它们只能防止在同一个上下文中重新排序,但是由于商店在退役后被调度,你可以在执行和观察存储之前同时提交lfences和两个load。

另一方面, mfence 会强制商店执行,然后才允许执行加载,因此您将在至少一个上下文中看到更新的数据。

至于 sfences - 正如评论中指出的那样,从理论上来说,它不足以阻止负载重新排序在它之上,因此它仍然可能会读取陈旧数据。虽然在内存官方排序规则适用的情况下也是如此,但我相信x86 uarch的当前实现会使它稍微强一些(我猜想未来不会这么做)。根据{{​​3}}:

  

由于强大的x86排序模型,负载缓冲区被窥探   通过一致性交通。远程存储必须使所有其他副本无效   缓存行。如果加载读取缓存行,然后   由远程存储无效,必须取消加载,因为它   可能会读取无效数据。 x86内存模型不需要   窥探商店缓冲区。

因此,机器中尚未提交的任何负载都应该由来自其他核心的存储进行侦听,从而使提交点的负载的有效观察时间成为可能,而不是执行点(这确实是乱序的,可能早已执行过)。提交是按顺序完成的,因此应该在之前的指令之后观察负载 - 正如我在评论中所说的那样使得lfences几乎无用,因为没有它们可以保持一致性。 这主要是猜测,试图解释在x86中lfences毫无意义的常见概念 - 我不完全确定它起源于何处,以及是否还有其他考虑因素 - 对任何专家来说都很乐意批准/挑战这一理论。 / p>

以上所有内容仅适用于当然的WB mem类型

答案 2 :(得分:-1)

当然它很有意义!

来自英特尔数据表的

LFENCE

  

对所有内存加载指令执行序列化操作   在LFENCE指令之前发布的。这序列化   operation保证每个加载指令都在之前   程序顺序LFENCE指令在任何之前是全局可见的   LFENCE指令后面的加载指令是全局的   可见。

如果正确对齐,像MOV这样的内存写入指令是原子的。但是这条指令通常在CPU缓存中执行,此时所有其他线程都不会全局可见,因为必须首先执行内存LFENCE/SFENCE or MFENCE

典型案例:

如果线程编写器使用内存对齐MOV之类的写入指令解锁内存区域,那么没有使用LOCK前缀指令,而是执行的缓存行MOV可见在很短的时间内所有其他线程。 LFENCE 确保给线程阅读器,线程编写器的所有其他缓存行都是全局访问