为什么GCC不使用LOAD(没有fence)和STORE + SFENCE来实现顺序一致性?

Ale*_*lex 15 c++ x86 multithreading gcc c++11

以下是在x86/x86_64中实现顺序一致性的四种方法:

  1. 加载(没有围栏)和STORE + MFENCE
  2. 加载(没有围栏)和LOCK XCHG
  3. MFENCE + LOAD和STORE(没有栅栏)
  4. LOCK XADD(0)和STORE(没有围栏)

正如它在这里写的:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

C/C++ 11操作x86实现

  • 加载Seq_Cst:MOV(来自内存)
  • Store Seq Cst:(LOCK)XCHG //替代方案:MOV(进入内存),MFENCE

注意:有一个C/C++ 11到x86的替代映射,而不是锁定(或屏蔽)Seq Cst存储锁/隔离Seq Cst加载:

  • 加载Seq_Cst:LOCK XADD(0)//替代:MFENCE,MOV(来自内存)
  • Store Seq Cst:MOV(进入内存)

GCC 4.8.2(x86_64中的GDB)对C++ 11-std :: memory_order_seq_cst使用第一种方法,即LOAD(没有fence)和STORE + MFENCE:

std::atomic<int> a;
int temp = 0;
a.store(temp, std::memory_order_seq_cst);
0x4613e8  <+0x0058>         mov    0x38(%rsp),%eax
0x4613ec  <+0x005c>         mov    %eax,0x20(%rsp)
0x4613f0  <+0x0060>         mfence
Run Code Online (Sandbox Code Playgroud)

我们知道,MFENCE = LFENCE + SFENCE.然后这段代码我们可以重写为:LOAD(without fence) and STORE+LFENCE+SFENCE

问题:

  1. 为什么我们不需要在LOAD之前使用LFENCE,并且需要在STORE之后使用LFENCE(因为LFENCE仅在LOAD之前才有意义!)?
  2. 为什么GCC不使用方法:对于std :: memory_order_seq_cst,LOAD(没有fence)和STORE + SFENCE?

bri*_*and 6

唯一的重新排序x86(对于正常的内存访问)是它可能重新排序商店后面的负载.

SFENCE保证围栏之前的所有商店在围栏之后的所有商店之前完成.LFENCE保证围栏之前的所有荷载在围栏之后的所有荷载之前完成.对于正常的内存访问,默认情况下已提供单个SFENCE或LFENCE操作的排序保证.基本上,LFENCE和SFENCE本身仅对x86的较弱内存访问模式有用.

LFENCE,SFENCE和LFENCE + SFENCE都不会阻止重载前后的存储.MFENCE确实如此.

相关参考资料是Intel x86架构手册.


zwo*_*wol 5

std::atomic<int>::store映射到编译器内在__atomic_store_n.(此处和其他原子操作内在函数在此处记录:用于内存模型识别原子操作的内置函数.)_n后缀使其类型为泛型; 后端实际上实现了特定大小的变体,以字节为单位. int在x86上,AFAIK总是32位长,这意味着我们正在寻找的定义__atomic_store_4. 此版本GCC的内部手册说该__atomic_store操作对应于命名的机器描述模式; 对应于4字节整数的模式是"SI"(这里记录的),所以我们正在寻找一个叫做"atomic_store‌modeatomic_storesi"在x86机器描述中.这将我们带到config/i386/sync.md,特别是这一点:

(define_expand "atomic_store<mode>"
  [(set (match_operand:ATOMIC 0 "memory_operand")
        (unspec:ATOMIC [(match_operand:ATOMIC 1 "register_operand")
                        (match_operand:SI 2 "const_int_operand")]
                       UNSPEC_MOVA))]
  ""
{
  enum memmodel model = (enum memmodel) (INTVAL (operands[2]) & MEMMODEL_MASK);

  if (<MODE>mode == DImode && !TARGET_64BIT)
    {
      /* For DImode on 32-bit, we can use the FPU to perform the store.  */
      /* Note that while we could perform a cmpxchg8b loop, that turns
         out to be significantly larger than this plus a barrier.  */
      emit_insn (gen_atomic_storedi_fpu
                 (operands[0], operands[1],
                  assign_386_stack_local (DImode, SLOT_TEMP)));
    }
  else
    {
      /* For seq-cst stores, when we lack MFENCE, use XCHG.  */
      if (model == MEMMODEL_SEQ_CST && !(TARGET_64BIT || TARGET_SSE2))
        {
          emit_insn (gen_atomic_exchange<mode> (gen_reg_rtx (<MODE>mode),
                                                operands[0], operands[1],
                                                operands[2]));
          DONE;
        }

      /* Otherwise use a store.  */
      emit_insn (gen_atomic_store<mode>_1 (operands[0], operands[1],
                                           operands[2]));
    }
  /* ... followed by an MFENCE, if required.  */
  if (model == MEMMODEL_SEQ_CST)
    emit_insn (gen_mem_thread_fence (operands[2]));
  DONE;
})
Run Code Online (Sandbox Code Playgroud)

在没有详细介绍的情况下,大部分内容都是一个C函数体,它将被调用以生成原子存储操作的低级" RTL "中间表示.当它是由你的示例代码调用<MODE>mode != DImode,model == MEMMODEL_SEQ_CST以及TARGET_SSE2是真实的,所以它会叫gen_atomic_store<mode>_1,然后gen_mem_thread_fence.后一个函数总是生成mfence.(此文件中有代码可以生成sfence,但我相信它仅用于显式编码_mm_sfence(from <xmmintrin.h>).)

评论表明有人认为在这种情况下需要MFENCE.我的结论是,你错误地认为不需要加载围栏,或者这是GCC中错过的优化错误.例如,您使用编译器的方式不是错误.

  • 抱歉,我无法帮助你处理这部分问题.你可能会在邮件列表中找到更好的运气`gcc-help @ gcc.gnu.org`. (2认同)

smo*_*sen 5

请考虑以下代码:

#include <atomic>
#include <cstring>

std::atomic<int> a;
char b[64];

void seq() {
  /*
    movl    $0, a(%rip)
    mfence
  */
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
}

void rel() {
  /*
    movl    $0, a(%rip)
   */
  int temp = 0;
  a.store(temp, std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)

关于原子变量"a",seq()和rel()在x86架构上都是有序的和原子的,因为:

  1. mov是一个原子指令
  2. mov是一种传统指令,英特尔承诺将旧式指令的有序存储器语义与始终使用有序存储器语义的旧处理器兼容.

不需要栅栏就可以将常量值存储到原子变量中.因为std :: memory_order_seq_cst意味着所有内存都被同步,而不仅仅是保存原子变量的内存,所以这里有围栏.

下面的set和get函数可以证明这种效果:

void set(const char *s) {
  strcpy(b, s);
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
}

const char *get() {
  int temp = 0;
  a.store(temp, std::memory_order_seq_cst);
  return b;
}
Run Code Online (Sandbox Code Playgroud)

strcpy是一个库函数,如果在运行时可用,则可能使用较新的sse指令.由于旧处理器中没有sse指令,因此不需要向后兼容性,并且未定义内存顺序.因此,一个线程中strcpy的结果可能在其他线程中不可直接显示.

上面的set和get函数使用原子值来强制执行内存同步,以便strcpy的结果在其他线程中可见.现在围栏很重要,但是在atomic :: store调用中它们的顺序并不重要,因为atomic :: store内部不需要围栅.

  • 无论如何,到处都是.查看订单`std :: memory_order_release`和`std :: memory_order_acquire`的链接:http://stackoverflow.com/a/18637536/1558037对于`memory_order_acq_rel`,你自己可以使用GCC + GDB尝试反汇编:http: //ideone.com/i8wBag或者读取:"在强烈订购的系统(**x86**,SPARC,IBM大型机)上,**发布 - 获取**订购是自动进行大多数操作.**没有额外的为此同步模式发出CPU指令**,只会影响某些编译器优化" - http://en.cppreference.com/w/cpp/atomic/memory_order (4认同)
  • 并且你的参数对于**acquire-release**语义是有效且相同的,但是接下来的另一个问题是:为什么GCC没有把栅栏放到`std :: memory_order_acq_rel`中?在这一行`std :: string*p = new std :: string("Hello");`class`std :: string`可以在**Release-Acquire排序的源代码示例中使用SSE指令**,它在GCC的收购发布中不起作用?http://en.cppreference.com/w/cpp/atomic/memory_order (2认同)
  • 写的示例是错误的,并且有数据竞争.两个seq_cst存储不同步,因此不强制get函数查看集合中strcpy的效果.如果你将seq_cst存储放入一个加载,并检查它是否看到0,那么这将是正常的. (2认同)

Pet*_*des 5

SFENCE + LFENCE 不是 StoreLoad障碍(MFENCE),因此问题的前提是不正确的。(另请参见我对同一用户的同一问题的另一个版本的回答,为什么(或不是?)SFENCE + LFENCE等同于MFENCE?


  • SFENCE可以通过(出现在之前)加载。(这只是StoreStore的障碍)。
  • LFENCE可以通过早期的商店。(负载不能在两个方向上交叉:LoadLoad barrier)。
  • 负载可以通过SFENCE(但是存储不能通过LFENCE,因此它既是LoadStore障碍,也是LoadLoad障碍)。

LFENCE + SFENCE不包含任何阻止存储在以后加载之前被缓冲的内容。MFENCE 确实防止了这种情况。

Preshing的博客文章更详细地解释了StoreLoad障碍如何特殊的情况,并提供了图表,并提供了一个实际的工作代码示例,演示了没有MFENCE的重新排序。任何对内存排序感到困惑的人都应该从该博客开始。

x86具有强大的内存模型,其中每个常规存储都具有发布语义,每个常规负载都具有获取语义。 这篇文章有详细信息

LFENCE和SFENCE 仅适用于movnt加载/存储,它们的顺序很弱,并且绕过了缓存。


万一这些链接消失了,我的答案中还有另一个类似问题的更多信息。