内存存储可以在 OoOE 处理器中真正重新排序吗?

Can memory store be reordered really, in an OoOE processor?

本文关键字:新排序 排序 存储 OoOE 处理器 内存      更新时间:2023-10-16

我们知道 OoOE 处理器可以对两条指令进行重新排序。例如,在不同的线程之间共享两个全局变量。

int data;
bool ready;

编写器线程生成data并打开标志ready以允许读者使用该数据。

data = 6;
ready = true;

现在,在 OoOE 处理器上,可以对这两个指令进行重新排序(指令提取、执行(。但是结果的最终提交/写回呢?即,商店是否井井有条?

根据我所了解到的,这完全取决于处理器的内存模型。 例如,x86/64 具有强大的内存模型,不允许对存储进行重新排序。相反,ARM 通常有一个弱模型,可以在其中进行存储重新排序(以及其他几个重新排序(。

此外,直觉告诉我我是对的,因为否则我们就不需要像典型的多线程程序中使用的那样在这两个指令之间设置存储屏障。

但是,这是我们的维基百科所说的:

..在上面的概述中,OoOE处理器避免了停滞 发生在顺序处理器的步骤 (2( 中,当指令 由于缺少数据,尚未完全准备好进行处理。

OoOE处理器用其他指令及时填充这些"插槽" 准备就绪,然后在最后对结果重新排序以使其显示 指令已正常处理。

我很困惑。是说结果必须按顺序写回来吗?真的,在OoOE处理器中,可以存储到dataready重新订购吗?

对于某些处理器类型,简单的答案是肯定的。

在 CPU 之前,您的代码面临着一个较早的问题,即编译器重新排序。

data = 6;
ready = true;

编译器可以自由地重新排列这些语句,因为据它所知,它们不会相互影响(它不是线程感知的(。

现在下降到处理器级别:

1( 无序处理器可以按不同的顺序处理这些指令,包括反转存储的顺序。

2( 即使 CPU 按顺序执行它们,它们内存控制器也可能不按顺序执行它们,因为它可能需要刷新或引入新的缓存行或执行地址转换才能写入它们。

3( 即使没有发生这种情况,系统中的另一个 CPU 也可能不会以相同的顺序看到它们。 为了观察它们,它可能需要从写入它们的核心引入修改后的缓存行。 如果一个缓存行被保留为另一个内核,或者多个内核争用该行,并且它自己的乱序执行将先于另一个读取一个缓存行,则它可能无法早于另一个缓存行。

4(最后,在其他内核上的推测执行可能会读取写入内核设置ready之前的data值,当它开始读取ready时,它已经设置了,但data也被修改了。

这些问题都通过记忆障碍来解决。 具有弱顺序内存的平台必须利用内存屏障来确保线程同步的内存一致性。

体系结构的一致性模型(或内存模型(确定可以重新排序的内存操作。 这个想法始终是从代码中实现最佳性能,同时保留程序员期望的语义。 这就是维基百科的观点,内存操作按顺序出现在程序员面前,即使它们可能已被重新排序。 当代码是单线程时,重新排序通常是安全的,因为处理器可以轻松检测到潜在的违规行为。

在 x86 上,常见的模型是不与其他写入重新排序的写入。 然而,处理器正在使用乱序执行 (OoOE(,因此指令不断重新排序。 通常,处理器具有几个额外的硬件结构来支持 OoOE,例如重新排序缓冲区和加载存储队列。 重新排序缓冲区确保所有指令看起来都按顺序执行,以便中断和异常破坏程序中的特定点。 加载存储队列的功能类似,因为它可以根据内存模型恢复内存操作的顺序。 加载存储队列还可以消除地址的歧义,以便处理器可以识别何时对相同或不同的地址执行操作。

回到OoOE,处理器在每个周期中执行10到100条指令。 负载和存储正在计算它们的地址等。 处理器可以预取用于访问的缓存行(可能包括缓存一致性(,但在安全(根据内存模型(之前,它实际上无法访问该行进行读取或写入。

插入存储屏障、内存围栏等会告诉编译器和处理器有关重新排序内存操作的进一步限制。 编译器是实现内存模型的一部分,因为一些语言(如Java(具有特定的内存模型,而其他语言(如C(则遵循"内存访问应显示为按顺序执行"的要求。

总之,是的,数据和就绪可以在 OoOE 中重新排序。 但这取决于记忆模型,它们是否真的是。 因此,如果您需要特定的顺序,请使用屏障等提供适当的指示,以便编译器、处理器等不会选择不同的顺序以获得更高的性能。

在现代处理器上,存储操作本身是异步的(可以想象它向 L1 缓存提交更改并继续执行,缓存系统进一步以异步方式传播(。因此,两个对象位于不同缓存块上的更改可以从其他CPU的角度实现OoO。

此外,即使是存储这些数据的指令,也可以执行OoO。例如,当两个对象"同时"存储,但一个对象的总线被其他 CPU 或总线主控保留/锁定时,因此其他对象可能会更早提交。

因此,要正确地跨线程共享数据,您需要某种内存屏障或利用最新 CPU (如 TSX(中的事务内存功能。

我认为

您误解了"似乎指令已正常处理"。 这意味着如果我有:

add r1 + 7 -> r2
move r3 -> r1

并且这些顺序通过无序执行有效地颠倒了,参与 add 操作的值仍将是移动之前存在的 R1 值。 等。 CPU将缓存寄存器值和/或延迟寄存器存储,以确保顺序指令流的"含义"不会改变。

这并没有说明从另一个处理器可见的商店顺序。