【问题标题】:Is a memory barrier an instruction that the CPU executes, or is it just a marker?内存屏障是 CPU 执行的指令,还是只是一个标记?
【发布时间】:2017-07-31 13:47:43
【问题描述】:

我试图准确地理解什么是内存屏障。 根据我目前所知,内存屏障(例如:mfence)用于防止指令从前到后和从后到前的重新排序。

这是一个正在使用的内存屏障的示例:

instruction 1
instruction 2
instruction 3
mfence
instruction 4
instruction 5
instruction 6

现在我的问题是:mfence 指令是否只是一个标记,告诉 CPU 以什么顺序执行指令?还是 CPU 像执行其他指令一样实际执行的指令(例如:mov)。

【问题讨论】:

  • 这是CPU执行的指令,没有其他指令。
  • 请注意,像std::atomic_signal_fence() 或GNU C asm("":::"memory") 这样的编译器 内存屏障纯粹是源代码中的标记,并且编译为零指令。它们的存在是为了在编译时阻止重新排序,并且在目标架构具有比源语言更强的内存模型时特别有用(例如 C++ -> x86 asm)。 preshing.com/20120625/memory-ordering-at-compile-time 解释更多。
  • 我想知道你对这笔赏金的期望。你得到的答案很明确。如果您还有其他问题,请务必实际说明!没有人能猜出答案的哪一部分让你不满意。
  • 请注意,您在“指令”和“标记”之间建立了一种可能错误的二分法。为什么不能两者兼而有之?是的,无可否认,它是一个指令,但为什么不能它是一个主要用作标记的指令?

标签: assembly x86 cpu-architecture memory-barriers memory-fences


【解决方案1】:

CPU 在其代码中遇到的每个字节序列都是 CPU 执行的指令。没有其他类型的指令。

您可以在Intel instruction set reference 和特定页面for mfence 中清楚地看到这一点。

MFENCE
对所有从内存加载的操作执行序列化操作 和之前发出的存储到内存指令 MFENCE 指令。这种序列化操作保证了之前的每条加载和存储指令 程序顺序中的 MFENCE 指令在随后的任何加载或存储指令之前变得全局可见 MFENCE 指令。

MFENCE 指令相对于所有加载和存储指令进行排序,其他指令 MFENCE 指令、任何 LFENCE 和 SFENCE 指令,以及任何序列化指令(例如 CPUID 操作说明)。 MFENCE 不序列化指令流。 弱排序内存类型可用于通过以下技术实现更高的处理器性能 乱序问题,推测性读取,写入组合, 和写折叠。消费者的程度 数据识别或知道数据是弱排序的,因应用程序而异,并且可能不为 该数据的生产者。 MFENCE 指令提供 一种确保加载和存储的高效方式 产生弱顺序的例程之间的排序 编辑结果和使用该数据的例程。

处理器可以随意获取和缓存数据 从使用 WB、WC 和 WT 内存类型。这种推测性的取指可以随时发生,并且与指令执行无关。因此,它 没有关于执行 MFENCE 的命令 操作说明;数据可以推测性地在 MFENCE 指令执行之前、期间或之后被带入缓存。

从摘录中可以看出,MFence 指令做了很多工作,而不仅仅是某种标记。

【讨论】:

    【解决方案2】:

    我将解释mfence 对管道流动的影响。以Skylake 管道为例。考虑以下指令序列:

    inst1
    store1
    inst2
    load1
    inst3
    mfence
    inst4
    store2
    load2
    inst5
    

    指令以相同的程序顺序被解码为一系列微指令。然后所有的微指令都按顺序传递给调度程序。通常,如果没有围栏,所有 uops 都会乱序执行。但是,当调度程序接收到mfence uop 时,它需要确保mfence 下游的内存uop 没有被执行,直到所有上游内存uop 都变得全局可见(这意味着存储已经退休并且负载至少完全的)。这适用于所有内存访问,无论被访问区域的内存类型如何。这可以通过让调度程序分别不发出任何下游存储或加载微指令到存储或加载缓冲区,直到缓冲区耗尽,或者通过发出下游存储或加载微指令并标记它们,以便它们可以与缓冲区中所有现有的内存微指令。围栏上方或下方的所有非内存微指令仍然可以乱序执行。在示例中,一旦store1 退出并且load1 完成(通过接收数据并将其保存在某个内部寄存器中),mfence 指令被认为已完成执行。我认为mfence 可能会或可能不会占用后端(ROB 或 RS)中的任何资源,并且可能会被翻译成多个微指令。

    英特尔在 1999 年提交了一份patent,描述了mfence 的工作原理。由于这是一项非常古老的专利,因此实施可能已经改变,或者在不同的处理器中可能会有所不同。我将在这里总结专利。 mfence 被解码为三个微指令。不幸的是,尚不清楚这些微指令的确切用途。然后从保留站分配条目以保存微指令,并且还从加载和存储缓冲区分配条目。这意味着加载缓冲区可以保存真实加载请求或栅栏(基本上是虚假加载请求)的条目。类似地,存储缓冲区可以保存真正的存储请求和栅栏的条目。 mfence uop 直到所有较早的加载或存储 uop(在各自的缓冲区中)都已停用。发生这种情况时,mfence uop 本身会作为内存请求发送到 L1 缓存控制器。控制器检查是否所有先前的请求都已完成。在这种情况下,它将被简单地视为 NOP,并且 uop 将从缓冲区中删除。否则,缓存控制器拒绝mfence uop。

    【讨论】:

    • Skylake 将mfence 解码为在 p2/p3 和 p4(AGU 和存储数据端口)上运行的 4 个微指令(融合和非融合)。 agner.org/optimize。至少在纸面上,mfence 不必停止以后的存储执行(并将存储数据放入存储缓冲区),它只是阻止这些存储变得全局可见(提交到 L1d缓存)在围栏之后的任何东西之前。不过,mfence 确实需要一种机制来阻止加载在所有早期加载/存储全局可见之前执行。如果英特尔的实施更具限制性,那么这只是一种设计选择。
    • @PeterCordes 你知道这 4 个微指令的用途吗?只是好奇。
    • @PeterCordes 顺便问一下,memory-fencesmemory-barriers 标签不应该是同义词吗?
    • 更新 mfence uops 与 Agner Fog 的表:他可能在 SKL079 的微码修复使 mfence 更昂贵之前进行了测试。查看Are loads and stores the only instructions that gets reordered? 的底部,我最近在偶然发现mfence 为何如此昂贵的解释后更新了它。也许实现蛮力停止一切解决方案是不同的微指令,但它曾经是 4 个未融合域微指令,仅阻止内存重新排序。
    • @PeterCordes IIRC,memory-order 标签应该是关于 ISA 内存模型的,memory-model 标签应该是关于语言内存模型的。但似乎memory-order 不知何故成为memory-barriers 的同义词,但它不应该是。 memory-barriers 标签只能是 memory-fences 的同义词。 memory-order 标签可能是 memory-ordering 的同义词。
    【解决方案3】:

    mfence 是一个指令。

    在 Linux 上获取它:

    1/写一个文件mfence.c

    #include <stdio.h>
    
    int main(){
        printf("Disass me\n");
        asm volatile ("mfence" ::: "memory");
        return 0;
    }
    

    2/ 编译

    gcc mfence.c mfence

    3/ 拆卸

    objdump -d mfence | grep -A 10 "&lt;main&gt;:"

    000000000000063a <main>:
     63a:   55                      push   %rbp
     63b:   48 89 e5                mov    %rsp,%rbp
     63e:   48 8d 3d 9f 00 00 00    lea    0x9f(%rip),%rdi        # 6e4 <_IO_stdin_used+0x4>
     645:   e8 c6 fe ff ff          callq  510 <puts@plt>
     64a:   0f ae f0                mfence 
     64d:   b8 00 00 00 00          mov    $0x0,%eax
     652:   5d                      pop    %rbp
     653:   c3                      retq   
     654:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
     65b:   00 00 00 
    

    4/ 观察第 64a 行 mfence 是(3 位)指令 (0f ae f0)

    这是一条 cpu 指令(如 mov):处理器需要先解码之前的指令,否则它无法猜测它是对齐的。

    例如,0f ae f0 可能出现在地址中,因此 cpu 无法将其用作制造商。

    最后,它只是一条老派指令,在其流水线中的执行点,它会在执行下一条指令之前同步流水线中的内存访问。


    注意:在 Windows 上使用宏 _ReadWriteBarrier in 来生成 mfence

    【讨论】:

      【解决方案4】:

      您的问题有错误的假设。 MFENCE 不会阻止指令的重新排序(参见突出显示的引用)。例如,如果有 1000 条指令流只对寄存器进行操作,而 MFENCE 指令放在中间,那么它不会影响 CPU 如何重新排序这些指令。

      MFENCE 指令相对于所有加载和存储指令、其他 MFENCE 指令、任何 LFENCE 和 SFENCE 指令以及任何序列化指令(例如 CPUID 指令)进行排序。 MFENCE 不序列化指令流。

      相反,MFENCE 指令阻止对高速缓存和主内存的加载和存储重新排序。

      【讨论】:

      • 我认为 x86 只能合并 adjacent 存储,因为它必须按顺序将它们提交到 L1d 缓存。 (x86 的内存模型不允许 StoreStore 重新排序)。但我想这可以作为一个例子,而不是阻止以后的加载(但不是以后的 ALU 指令),直到 MFENCE 成为全局可见之前的最后一次存储之后。
      • @PeterCordes,你是对的。我将删除最后一段
      • Hrm,这是这个答案的有趣部分。我们有一些证据表明 Skylake 确实将相邻存储合并到同一个缓存行中,但很难衡量,因为存储端口吞吐量仅为每个时钟 1 个。这个答案没有详细解释停止内存重新排序是什么意思,或者根本没有提到存储缓冲区,这是一些人在这里误解的关键(Does a memory barrier acts both as a marker and as an instruction?Does an x86 CPU reorder instructions?
      猜你喜欢
      • 2018-10-24
      • 1970-01-01
      • 2018-10-23
      • 2016-03-29
      • 2021-01-06
      • 2021-10-22
      • 1970-01-01
      • 1970-01-01
      • 2017-08-01
      相关资源
      最近更新 更多