在OoOE处理器中,内存存储能否真正重新排序?

时间:2014-08-15 15:31:01

标签: c++ c processor

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

int data;
bool ready;

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

data = 6;
ready = true;

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

据我所知,这完全取决于处理器的内存模型。例如,x86 / 64具有强大的内存模型,不允许重新排序商店。相反,ARM通常有一个弱模型,可以发生商店重新排序(以及其他几个重新排序)。

此外,直觉告诉我,我是对的,否则我们不会在典型的多线程程序中使用这两条指令之间存在商店障碍。

但是,这是wikipedia所说的:

  

..在上面的大纲中,OoOE处理器避免了失速   当指令是在有序处理器的步骤(2)中发生   由于缺少数据而没有完全准备好处理。

     

OoOE处理器填补这些"插槽"及时与其他指示   准备就绪,然后在结尾处重新排序结果以使其显示   指令按正常方式处理。

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

4 个答案:

答案 0 :(得分:3)

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

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

因此,要在线程之间正确共享数据,您需要某种内存屏障或使用transactional memory等最新CPU中的TSX功能。

答案 1 :(得分:3)

我认为你错误解释"看起来说明是正常处理的。"这意味着如果我有:

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

并且这些顺序通过无序执行有效地逆转,参与 add 操作的值仍然是 r1 的值。在移动之前出现。等等.CPU将缓存寄存器值和/或延迟寄存器存储,以确保"含义" 顺序指令流的更改。

这对于从其他处理器可见的商店顺序一无所知。

答案 2 :(得分:3)

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

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

回到OoOE,处理器在每个周期执行10到100个指令。加载和存储正在计算它们的地址等。处理器可以预取用于访问的高速缓存行(其可以包括高速缓存一致性),但是在它是安全的之前它实际上不能读取或写入该行(根据存储器模型)这样做。

插入存储障碍,内存屏障等会告诉编译器和处理器有关重新排序内存操作的进一步限制。编译器是实现内存模型的一部分,因为像java这样的某些语言具有特定的内存模型,而像C这样的其他语言遵循“内存访问应该看起来好像是按顺序执行”。

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

答案 3 :(得分:3)

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

在CPU之前,您的代码面临早期问题,编译器重新排序。

data = 6;
ready = true;

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

现在降到处理器级别:

1)无序处理器可以按不同的顺序处理这些指令,包括颠倒商店的顺序。

2)即使CPU按顺序执行它们,它们也不会按顺序执行它们,因为它可能需要刷新或引入新的缓存行或在写入之前进行地址转换。

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

4)最后,其他核心上的推测性执行可能会在写入核心设置data之前读取ready的值,并且当它到达读取ready时,它已经设置但data也被修改了。

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