内存重新排序:可以使用早期存储将负载重新排序到不同但包含的位置吗?

时间:2012-08-05 13:54:53

标签: x86 intel memory-model memory-barriers

在英特尔的处理器手册中:link在8.2.3.4节中说明,负载可以与早期商店重新排序到不同的位置,但不能与之前的商店重新排序到同一位置。

所以我理解以下两个操作可以重新排序:

x = 1;
y = z;

以下两项操作无法重新排序:

x = 1;
y = x;

但是当商店和负载位于不同的位置时会发生什么,但负载完全包含商店,例如:

typedef union {
  uint64_t shared_var;
  uint32_t individual_var[2];
} my_union_t;

my_union_t var;
var.shared_var = 0;

var.individual_var[1] = 1;
int y = var.shared_var;

在这种情况下,'y'是否可以为0?

编辑(@Hans Passant)为了进一步解释这种情况,我试图看看我是否可以使用这种技术在不使用锁定指令的情况下在线程之间设计一种准同步。

一个更具体的问题是,给定一个全局变量:

my_union_t var;
var.shared_var = 0;

两个线程执行以下代码:

主题1:

var.individual_var[0] = 1;
int y = __builtin_popcountl(var.shared_var);

主题2:

var.individual_var[1] = 1;
int y = __builtin_popcountl(var.shared_var);

两个线程都可以为1吗?

注意:__ builtin_popcountl是内置gcc内在函数,用于计算变量中设置的位数。

2 个答案:

答案 0 :(得分:1)

您最后也是最重要的问题 1

  

两个线程执行以下代码:

     

主题1:

var.individual_var[0] = 1;
int y = __builtin_popcountl(var.shared_var);
     

主题2:

var.individual_var[1] = 1;
int y = __builtin_popcountl(var.shared_var);
     

可以' y'两个线程都是1?

是的,它可以,但是没有测试芯片实际上会这样做是不明显的,因为SDM中没有涵盖重叠读取。

这种情况基本上是8.2.3.4(存储缓冲)和8.2.3.5(存储转发)情况的组合。部分结果可能来自当前的本地商店,结果的其余部分必须来自全球可见的商店(即来自"内存")。

CPU可以为两个线程提供结果1吗? - 一些当前的Intel CPU将满足来自存储缓冲区的部分负载,以及来自L1的其余负载,但由于两个的商店尚未全球化可见(仍然位于存储缓冲区中),您可以在线程1上获得var.iv[0] == 1 && var.iv[1] == 0,在线程2上获得var.iv[0] == 0 && var.iv[1] == 1

用户Alex实际上为此编写了测试代码并在此very relevant answer中进行了演示。所以不,这里没有神奇的技巧:你不能在所有的CPU上建立你自己的无锁同步。

顺便说一句:这个可能在某些CPU上工作!在存在部分存储转发的情况下,某些模型可以采用简单存储并等待直到存储提交到L1,然后从L1读取整个值。在这种情况下,你的伎俩会起作用......但它最终不会给你带来太大的收获。你必须等待整个商店缓冲区耗尽,这仍然是内存栅栏的主要成本!因此,您可以获得内存栅栏效果,最重要的是内存栅栏大小的档位。

1 早期单线程的答案"所以可以' y'在这种情况下是0?" 情况显然是"没有" - CPU将保持按顺序执行的错觉,所以如果你写东西并立即读回来,你总会看到写(没有其他线程写同一位置),无论写和读重叠如何

答案 1 :(得分:0)

CPU不知道或不关心您是否已将内存位置别名。因此,您的第一个问题的答案是“否”。

第二个示例中的写入不同步,因此,是的,线程可以拥有自己的数据副本。

你没有问过的问题的答案(“我应该实现并使用自定义同步原语吗?”)是“否”。