MOV x86指令是否实现了C++11 memory_order_release原子存储?

9
根据https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html,在x86(包括x86-64)上,已发布的存储实现为MOV(写入内存)。
根据http://en.cppreference.com/w/cpp/atomic/memory_ordermemory_order_release: 一个带有这个内存顺序的存储操作执行了一个释放操作:当前线程中的所有内存访问都不能被重新排序到此存储之后。这确保了当前线程中的所有写操作都对获取或相同原子变量的其他线程可见,并且将依赖关系传递到原子变量的写操作也会对使用相同原子变量的其他线程可见。
我理解当使用memory_order_release时,先前完成的所有内存存储都应该在此之前完成。
int a;
a = 10;
std::atomic<int> b;
b.store(50, std::memory_order_release); // i can be sure that 'a' is already 10, so processor can't reorder the stores to 'a' and 'b'

问题:一个没有显式内存屏障的裸 MOV 指令如何能实现这种行为?MOV 如何告诉处理器完成所有之前的存储操作?


2
你忘了提到“在x86上”。 - Cubbi
@cubbi:没错,这很重要,已完成。 - Krab
1
x86 没有单独的释放和获取屏障。 - Ben Voigt
2
那个cppreference页面的底部有一个指向x86-TSO论文的链接,其中包含比你所需更详细的信息。 - Cubbi
1
我可以确定'a'已经是10了,所以处理器不能重新排列对'a'和'b'的存储。为了明确起见,在标准中没有全局概念"'a'已经是10",因此更准确地说: “我可以确定另一个线程使用至少memory_order_acquire的内存顺序加载此处存储的50,并且还会观察到'a'为10。”认为release会使先前的写入在其他线程中神奇地可见是一个常见的陷阱 - 标准仅仅声明一个线程的写入应该在其他线程中“在合理的时间内”变得可见。 - Arne Vogel
显示剩余2条评论
2个回答

7

在运行时(由CPU完成)存在内存重排序,也存在编译时的内存重排序。请阅读Jeff Preshing的一篇关于编译时重排序的文章(以及该博客上其他许多优秀的文章)以获取更多信息。

memory_order_release 防止编译器重新排列对数据的访问,并发出任何必要的围栏或特殊指令。在x86汇编中,普通的加载和存储已经具有获取/释放语义,因此阻止编译时重排序对于acq_rel是足够的,但对于seq_cst则不足。


“_memory_order_release” 通过定义防止编译器对访问数据进行重新排序,这包括CPU的行为。 - curiousguy
1
@curiousguy:是的,之前这个回答解释得不好。但是我决定改正它,而不是给它点踩。 - Peter Cordes

5

在使用Intel编译器编译的代码中,似乎确实是这样映射的:


0000000000401100 <_Z5storeRSt6atomicIiE>:
  401100:       48 89 fa                mov    %rdi,%rdx
  401103:       b8 32 00 00 00          mov    $0x32,%eax
  401108:       89 02                   mov    %eax,(%rdx)
  40110a:       c3                      retq
  40110b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

0000000000401110 <_Z4loadRSt6atomicIiE>:
  401110:       48 89 f8                mov    %rdi,%rax
  401113:       8b 00                   mov    (%rax),%eax
  401115:       c3                      retq
  401116:       0f 1f 00                nopl   (%rax)
  401119:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

对于这段代码:

#include <atomic>
#include <stdio.h>

void store( std::atomic<int> & b ) ;

int load( std::atomic<int> & b ) ;

int main()
{
   std::atomic<int> b ;

   store( b ) ;

   printf("%d\n", load( b ) ) ;

   return 0 ;
}

void store( std::atomic<int> & b )
{
   b.store(50, std::memory_order_release ) ;
}

int load( std::atomic<int> & b )
{
   int v = b.load( std::memory_order_acquire ) ;

   return v ;
}

当前英特尔体系结构文档第三卷(系统编程指南)做了不错的解释,详见:8.2.2 P6 和更高版本处理器家族中的内存排序
  • 读取不会与其他读取重新排序。
  • 写入不会与旧读取重新排序。
  • 对内存的写入不会与其他写入重新排序,但有以下几个例外:...
完整的内存模型在那里解释。我认为英特尔和C++标准人员已经详细合作,以确保每个内存顺序操作都最佳地符合第3卷所描述的内存模型,并且在这些情况下已确定使用普通存储和加载足以满足要求。
请注意,虽然在x86-64上不需要特殊指令来执行有序存储,但这并不意味着这将普遍适用。对于PowerPC,我希望在存储时能看到类似于lwsync指令的东西,在hpux(ia64)上,编译器应该使用st4.rel指令。

在你引用的英特尔文档中提到“在单处理器系统中”,其中“处理器”可以指“核心”。我们肯定是在讨论多核系统中的重新排序问题吧?该文档继续说道:“在多处理器系统中,以下排序原则适用:”。 - user997112
除非是这部分“单个处理器使用与单处理器系统相同的排序原则。”? - user997112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接