Ale*_*lex 15 c++ x86 multithreading gcc c++11
以下是在x86/x86_64中实现顺序一致性的四种方法:
正如它在这里写的: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
问题:
唯一的重新排序x86(对于正常的内存访问)是它可能重新排序商店后面的负载.
SFENCE保证围栏之前的所有商店在围栏之后的所有商店之前完成.LFENCE保证围栏之前的所有荷载在围栏之后的所有荷载之前完成.对于正常的内存访问,默认情况下已提供单个SFENCE或LFENCE操作的排序保证.基本上,LFENCE和SFENCE本身仅对x86的较弱内存访问模式有用.
LFENCE,SFENCE和LFENCE + SFENCE都不会阻止重载前后的存储.MFENCE确实如此.
相关参考资料是Intel x86架构手册.
std::atomic<int>::store映射到编译器内在__atomic_store_n.(此处和其他原子操作内在函数在此处记录:用于内存模型识别原子操作的内置函数.)_n后缀使其类型为泛型; 后端实际上实现了特定大小的变体,以字节为单位. int在x86上,AFAIK总是32位长,这意味着我们正在寻找的定义__atomic_store_4. 此版本GCC的内部手册说该__atomic_store操作对应于命名的机器描述模式; 对应于4字节整数的模式是"SI"(这里记录的),所以我们正在寻找一个叫做"atomic_storemodeatomic_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中错过的优化错误.例如,您使用编译器的方式不是错误.
请考虑以下代码:
#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架构上都是有序的和原子的,因为:
不需要栅栏就可以将常量值存储到原子变量中.因为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内部不需要围栅.
SFENCE + LFENCE 不是 StoreLoad障碍(MFENCE),因此问题的前提是不正确的。(另请参见我对同一用户的同一问题的另一个版本的回答,为什么(或不是?)SFENCE + LFENCE等同于MFENCE?)
LFENCE + SFENCE不包含任何阻止存储在以后加载之前被缓冲的内容。MFENCE 确实防止了这种情况。
Preshing的博客文章更详细地解释了StoreLoad障碍如何特殊的情况,并提供了图表,并提供了一个实际的工作代码示例,演示了没有MFENCE的重新排序。任何对内存排序感到困惑的人都应该从该博客开始。
x86具有强大的内存模型,其中每个常规存储都具有发布语义,每个常规负载都具有获取语义。 这篇文章有详细信息。
LFENCE和SFENCE 仅适用于movnt加载/存储,它们的顺序很弱,并且绕过了缓存。
万一这些链接消失了,我的答案中还有另一个类似问题的更多信息。
| 归档时间: |
|
| 查看次数: |
2991 次 |
| 最近记录: |