C ++ 11原子x86内存排序

时间:2012-08-06 21:14:17

标签: c++ c++11 atomic

在C ++ 0x中的一个原子变量文档中,当描述内存顺序时,它提到:

  

发布 - 获取订购

     

在强烈排序的系统(x86,SPARC,IBM大型机)上,发布 - 获取订购是   自动。仅为此同步模式不会发出其他CPU指令   某些编译器优化会受到影响......

首先是真的,x86遵循严格的内存排序? 似乎效率非常低,总是强加于此。意味着每次写入和读取都有围栏?

另外,如果我在x86系统上有一个对齐的int,原子变量是否可以用于任何目的?

4 个答案:

答案 0 :(得分:11)

是的,x86确实有严格的内存排序,请参阅Intel manuals的第3A卷第8.2章。较老的x86处理器(如386)提供了真正严格的排序(称为强排序)语义,而更现代的x86处理器在少数情况下略微放松了条件,但您无需担心。例如,当写入是高速缓存命中时(因此读取的地址不同),Pentium和486允许读取高速缓存未命中超过缓冲写入。

是的,效率低下。有时,高性能软件仅针对具有更宽松内存排序要求的其他架构编写。

是的,原子变量仍然在x86上有用。它们与编译器具有特殊的语义,使得典型的read-modify-write操作以原子方式发生。如果你有两个线程同时递增一个原子变量(我的意思是C ++ 11中类型为std::atomic<T>的变量),你可以放心,该值将大2;没有std::atomic,你可能会得到错误的值,因为一个线程在执行增量时缓存了寄存器中的当前值,即使存储到内存在x86上是原子的。

答案 1 :(得分:4)

确实,在x86上,所有商店都有发布,所有加载都具有获取语义。

这不会也不应该影响您编写C ++的方式:要编写并发,无竞争的代码,您必须使用std::atomic构造或锁。

架构细节的含义是,在x86上,只要您最多要求获取/发布顺序,就会对原子字大小类型的操作生成很少或没有额外代码。 (顺序一致性将发出mfence指令。但是,您仍然必须使用C ++原子类型,并且不能省略它们以便拥有正确,格式正确的程序。原子变量的一个重要特性是它们阻止编译器重新排序,这对于程序的正确性至关重要。

(Pre-C ++ 11,您将不得不使用编译器提供的扩展,例如GCC的__sync_*函数套件,这将使编译器正常运行。如果您真的想使用裸变量,你至少必须自己插入编译器障碍。)

答案 2 :(得分:1)

有一个不错的重新排序操作的表,可以发生,并且(例如)x86很少有。其他架构(众所周知的Alpha)几乎可以做任何事情。

对于内存模型由标准定义,x86等本质上是合规的。

关于原子变量的问题答案略有不同。对变量的任何修改都涉及竞争条件,这样当多个线程更新同一个变量时,更新可能会丢失。定义原子变量使得它们是原子操作的正确类型,从而消除了这种竞争条件。所以他们的目的之一不是订购。

答案 3 :(得分:0)

请注意,释放/获取语义不一定意味着每条指令后都有一个mfence。在x86上保持enter image description here,可以在@Adam Rosenfield引用的手册中看到,或者快速查看Wikipedia。然而,x86具有存储的释放语义并且可以获取负载的语义。

来自Kerrek SB的答案:

  

架构细节的含义是,在x86上,只要您最多要求获取/发布顺序,就会对原子字大小类型的操作生成很少或没有额外代码。 (但顺序一致性会发出mfence指令。)

请注意,顺序一致性是默认值! (例如参见cppreference).

这意味着......

#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;

void producer()
{
    std::string* p  = new std::string("Hello");
    ptr = p;
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr))
        ;
    assert(*p2 == "Hello"); // never fails
}

(x86上的g ++ -std = c ++ 11 -S -O3)

...实际上会导致在生成器函数中发出mfence以解释x86上的上述放松(enter image description here)。

然而......

#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;

void producer()
{
    std::string* p  = new std::string("Hello");
    ptr.store(p, std::memory_order_release);
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fails
}

(x86上的g ++ -std = c ++ 11 -S -O3)

...不会插入mfence,因为x86具有存储的释放语义并且可以获取加载的语义。