gcc-c ++是不是优化当前x86-64处理器的原子操作

时间:2017-07-20 09:08:13

标签: c++ assembly concurrency x86-64 atomic

鉴于以下测试计划:

CreteInstance

编译:

#include <atomic>
#include <iostream>

int64_t process_one() {
        int64_t a;
        //Should be atomic on my haswell
        int64_t assign = 42;
        a = assign;
        return a;
}

int64_t process_two() {
        std::atomic<int64_t> a;
        int64_t assign = 42;
        a = assign;
        return a;
}

int main() {
        auto res_one = process_one();
        auto res_two = process_two();
        std::cout << res_one << std::endl;
        std::cout << res_two << std::endl;
}

代码为这两个函数生成了以下asm:

g++ --std=c++17 -O3 -march=native main.cpp

就个人而言,我不会说很多汇编语言但是(我可能会在这里弄错)似乎process_two编译为包含所有process_one&,然后是一些。

然而,据我所知,&#39;现代&#39; x86-64处理器(例如Haswell,我编译它)将自动进行分配而不需要任何额外的操作(在这种情况下,我认为额外的操作是process_two中的00000000004007c0 <_Z11process_onev>: 4007c0: b8 2a 00 00 00 mov $0x2a,%eax 4007c5: c3 retq 4007c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4007cd: 00 00 00 00000000004007d0 <_Z11process_twov>: 4007d0: 48 c7 44 24 f8 2a 00 movq $0x2a,-0x8(%rsp) 4007d7: 00 00 4007d9: 0f ae f0 mfence 4007dc: 48 8b 44 24 f8 mov -0x8(%rsp),%rax 4007e1: c3 retq 4007e2: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4007e9: 00 00 00 4007ec: 0f 1f 40 00 nopl 0x0(%rax) 指令。)

那么为什么gcc只是优化第二个过程中的代码来完成与过程一样的情况呢?鉴于我编译的标志。

是否存在原子存储的行为与正常变量的赋值不同的情况,因为它们都是8个字节。

2 个答案:

答案 0 :(得分:10)

原因是默认使用std::atomic也意味着内存顺序

std::memory_order order = std::memory_order_seq_cst

为了实现这种一致性,编译器必须告诉处理器不要重新排序指令。它通过使用mfence指令来实现。

更改您的

    a = assign;

    a.store(assign, std::memory_order_relaxed);

,您的输出将从

更改
process_two():
        mov     QWORD PTR [rsp-8], 42
        mfence
        mov     rax, QWORD PTR [rsp-8]
        ret

process_two():
        mov     QWORD PTR [rsp-8], 42
        mov     rax, QWORD PTR [rsp-8]
        ret

就像你预期的那样。

答案 1 :(得分:2)

这只是错过了优化。例如,clang does just fine with it - 两个函数编译为单个gcc

现在,您必须深入研究gcc内部,但似乎clang尚未实现围绕原子变量的许多常见和法律优化,包括合并连续读写。事实上,iccgccclang似乎没有优化任何东西,除了icc通过基本上删除处理局部原子(包括传递值)它们的原子性质,在某些情况下很有用,例如通用代码。有时two_reads似乎会产生特别糟糕的代码 - 请参阅rax here,例如:它似乎只想使用mov作为地址作为累加器,导致一系列state指令随机移动。

围绕原子优化的一些更复杂的问题是discussed here,我希望编译器会随着时间的推移而变得更好。