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

X86 内存重新排序:是否可以使用早期存储将加载重新排序到不同但包含的位置?,x86,intel,memory-model,memory-barriers,X86,Intel,Memory Model,Memory Barriers,《英特尔处理器手册》第8.2.3.4节中指出,可以使用早期存储将加载重新排序到不同的位置,但不能使用早期存储将加载重新排序到相同的位置 因此,我了解以下两个操作可以重新排序: x = 1; y = z; x = 1; y = x; 以下两项操作不能重新排序: x = 1; y = z; x = 1; y = x; 但是,当存储和加载针对不同的位置,但加载完全包含存储时会发生什么情况,例如: typedef union { uint64_t shared_var; uint32_t

《英特尔处理器手册》第8.2.3.4节中指出,可以使用早期存储将加载重新排序到不同的位置,但不能使用早期存储将加载重新排序到相同的位置

因此,我了解以下两个操作可以重新排序:

x = 1;
y = z;
x = 1;
y = x;
以下两项操作不能重新排序:

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);
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);
var.individual_var[1] = 1;
int y = __builtin_popcountl(var.shared_var);
两个线程的“y”是否都可以为1


注意:uuu builtin\u popcountl是用于计算变量中设置的位数的内置gcc固有值。

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

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


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

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

和两个执行以下代码的线程:

线程1:

var.individual_var[0] = 1;
int y = __builtin_popcountl(var.shared_var);
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);
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
?是-某些当前英特尔CPU将满足存储缓冲区的部分负载,以及L1的其余负载,但由于这两个存储区都尚未全局可见(仍位于存储缓冲区中),因此在线程1和
var.iv[0]==0&&var.iv[1]==1&&var.iv[1]==0
==1在线程2上

用户Alex已经为此编写了测试代码,并在本文中进行了演示。所以不,这里没有魔术:你不能像这样在所有CPU上构建你自己的无锁同步

顺便说一下:这可能在某些CPU上工作!在存在部分存储转发的情况下,一些模型可能会去掉easy,只需等待存储提交到L1,然后从L1读取整个值。在这种情况下,您的技巧将起作用。。。但它最终并没有给你带来多少好处。您必须等待整个存储缓冲区耗尽,这是内存围栏的主要成本!所以你得到了内存栅栏效应,最多是一个内存栅栏大小的暂停



1对于前面的单线程“在这种情况下‘y’可以是0吗?”案例,答案显然是“否”-CPU将保持顺序执行的假象,因此如果您写入某些内容并立即将其读回,您将始终看到写入(没有其他线程写入同一位置),无论写入和读取如何重叠。

排序仅在多个内核访问内存位置时起作用。从你的代码片段中看不到,发布的代码永远不会失败。让多个线程在没有同步的情况下读写同一个内存位置是没有实际用途的。考虑C中的一致性和排序问题会使问题变得复杂,因为编译器本身可能会进行重新排序和优化。例如,您的双线程示例将被破坏,因为您没有告诉编译器联合是不稳定的。