内存屏障是CPU执行的指令,还是仅仅是一个标记?

Chr*_*her 19 x86 assembly cpu-architecture memory-fences memory-barriers

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

这是使用中的内存屏障的示例:

instruction 1
instruction 2
instruction 3
mfence
instruction 4
instruction 5
instruction 6
Run Code Online (Sandbox Code Playgroud)

现在我的问题是:mfence指令只是一个标记,告诉CPU执行指令的顺序是什么?或者它是CPU实际执行的指令,就像它执行其他指令(例如:) mov.

Joh*_*ica 23

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

您可以在英特尔指令集参考mfence的特定页面中清楚地看到这一点.

MFENCE
对MFENCE指令之前发出的所有内存加载和存储到内存指令执行序列化操作.此序列化操作保证在遵循MFENCE指令的任何加载或存储指令之前,按程序顺序在MFENCE指令之前的每个加载和存储指令都变为全局可见.

MFENCE指令按照所有加载和存储指令,其他MFENCE指令,任何LFENCE和SFENCE指令以及任何序列化指令(例如CPUID指令)进行排序.MFENCE不会序列化指令流.弱序存储器类型可用于通过诸如无序问题,推测读取,写入组合和写入折叠等技术来实现更高的处理器性能.数据消费者识别或知道数据微弱排序的程度因应用程序而异,并且该数据的生产者可能不知道.MFENCE指令提供了一种性能有效的方法,可确保生成弱顺序结果的例程和使用该数据的例程之间的加载和存储顺序.

处理器可以从使用WB,WC和WT存储器类型的系统存储器区域中推测性地获取和缓存数据.这种推测性提取可以在任何时间发生,并且与指令执行无关.因此,对于执行MFENCE指令没有命令; 在执行MFENCE指令之前,期间或之后,可以推测性地将数据引入高速缓存.

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


Had*_*ais 16

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

inst1
store1
inst2
load1
inst3
mfence
inst4
store2
load2
inst5
Run Code Online (Sandbox Code Playgroud)

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

英特尔在1999年提交的专利描述了如何mfence运作.由于这是一项非常古老的专利,实施可能已经改变,或者在不同的处理器中可能会有所不同.我在这里总结专利.mfence被解码为三个uops.不幸的是,目前还不清楚这些uops用于什么.然后从保留站分配的条目被分配用于保存uop并且还从加载和存储缓冲区分配.这意味着加载缓冲区可以保存真实加载请求或围栏(基本上是虚假加载请求)的条目.类似地,存储缓冲区可以保存真实存储请求和围栏的条目.该mfence微操作时,不会调度,直到所有更早加载或存储微指令(在各自的缓冲区)已经退休.当发生这种情况时,mfenceuop本身作为内存请求被发送到L1缓存控制器.控制器检查所有先前的请求是否已完成.在这种情况下,它将被简单地视为NOP,并且uop将从缓冲区中解除涂层.否则,缓存控制器拒绝mfenceuop.

  • Skylake将`mfence`解码为4个uop(融合和未融合),在p2/p3和p4(AGU和存储数据端口)上运行.http://agner.org/optimize/.至少在纸面上,`mfence`不必停止以后的存储*执行*(并将存储数据放入存储缓冲区),它只是阻止这些存储在任何事情之前变为全局可见(提交到L1d缓存)围栏之后.但是,在所有早期的加载/存储全局可见之前,`mfence`确实需要一种机制来阻止执行.如果英特尔的实施更具限制性,那么这只是一个设计选择. (4认同)
  • @PeterCordes顺便说一下,`memory-fences`和`memory-barrier`标签不应该是同义词吗? (2认同)

tin*_*ino 5

mfence是一条指令。

要在 Linux 上获取它:

1/ 编写文件mfence.c

#include <stdio.h>

int main(){
    printf("Disass me\n");
    asm volatile ("mfence" ::: "memory");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

2/编译

gcc mfence.c mfence

3/ 拆解

objdump -d mfence | grep -A 10 "<main>:"

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 
Run Code Online (Sandbox Code Playgroud)

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

这是一条 cpu 指令(如mov):处理器需要在获取之前的指令之前对其进行解码,否则它无法猜测它的对齐方式。

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

最后,它只是一条老式指令,在管道中的执行点,它将在执行下一条指令之前进一步同步管道中的内存访问。


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