原子加载和存储函数生成与非原子加载和存储相同的汇编代码

Big*_*ree 5 c assembly gcc atomic

为什么store_idx_x86()的汇编输出和store_idx()一样,load_idx_x86()和load_idx()一样?

我的理解是 __atomic_load_n() 会刷新核心的失效队列,而 __atomic_store_n() 会刷新核心的存储缓冲区。

注意——我遵守了:gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-16)

更新:我知道 x86 永远不会用其他存储重新排序存储并用其他负载加载——所以 gcc 足够聪明,只在需要时才实现 sfence 和 lfence,或者应该使用 __atomic_ 导致栅栏(假设内存模型比__ATOMIC_RELAXED)?

代码

#include <stdint.h>


inline void store_idx_x86(uint64_t* dest, uint64_t idx)
{   
    *dest = idx;    
}

inline void store_idx(uint64_t* dest, uint64_t idx)
{
    __atomic_store_n(dest, idx, __ATOMIC_RELEASE);
}

inline uint64_t load_idx_x86(uint64_t* source)
{
    return *source;

}

inline uint64_t load_idx(uint64_t* source)
{
    return __atomic_load_n(source, __ATOMIC_ACQUIRE);
}
Run Code Online (Sandbox Code Playgroud)

集会:

.file   "util.c"
    .text
    .globl  store_idx_x86
    .type   store_idx_x86, @function
store_idx_x86:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -8(%rbp), %rax
    movq    -16(%rbp), %rdx
    movq    %rdx, (%rax)
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   store_idx_x86, .-store_idx_x86
    .globl  store_idx
    .type   store_idx, @function
store_idx:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -8(%rbp), %rax
    movq    -16(%rbp), %rdx
    movq    %rdx, (%rax)
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   store_idx, .-store_idx
    .globl  load_idx_x86
    .type   load_idx_x86, @function
load_idx_x86:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    (%rax), %rax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   load_idx_x86, .-load_idx_x86
    .globl  load_idx
    .type   load_idx, @function
load_idx:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    (%rax), %rax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   load_idx, .-load_idx
    .ident  "GCC: (GNU) 4.8.2 20140120 (Red Hat 4.8.2-16)"
    .section    .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

Myl*_*ock 2

为什么 store_idx_x86() 的汇编输出与 store_idx() 相同,而 load_idx_x86() 与 load_idx() 相同?

在 x86 上,假设编译器强制对齐,它们是相同的操作。加载和存储到本机大小或更小的对齐地址保证是原子的。参考Intel手册第3A卷,8.1.1:

Pentium 处理器(以及此后更新的处理器)保证以下附加内存操作始终以原子方式执行:读取或写入在 64 位边界上对齐的四字 [...]

此外,x86 强制执行强有序内存模型,这意味着每个存储和加载分别具有隐式释放和获取语义。

最后,您提到的防护指令仅在使用英特尔的非临时 SSE 指令(此处有很好的参考)或需要创建存储加载防护(此处的文章)(实际上是mfenceorlock指令)时才需要。

旁白:我对英特尔手册中的这一说法感到好奇,所以我设计了一个测试程序。令人沮丧的是,在我的计算机(2 核 i3-4030U)上,我得到以下输出:

unaligned
4265292 / 303932066 | 1.40337%
unaligned, but in same cache line
2373 / 246957659 | 0.000960893%
aligned (8 byte)
0 / 247097496 | 0%
Run Code Online (Sandbox Code Playgroud)

这似乎违反了英特尔的说法。我会调查。同时,您应该克隆该演示程序并查看它为您提供了什么。你只需要-std=c++11 ... -pthread在linux上。