内存屏障会阻止分支预测吗?
Do memory barriers prevent branch prediction?
本题不假设任何特定架构。
假设我们有一个具有高速缓存一致性、乱序执行和分支预测逻辑的多核处理器。我们还假设存储到内存中严格按照程序顺序。
我们有两个线程 运行 并行,每个都在一个单独的核心上。
以下是线程的伪代码。 data
和 flag
初始为 0。
线程 #1 代码:
data=10;
flag=1;
线程 #2 代码:
while(!flag);
print data;
通过适当的同步,线程 #2 最终会打印 1。但是,分支预测器可能会预测未进入循环,从而执行对 data
的推测性读取,其中当时包含 0 (在线程 #1 设置 data
之前)。预测是正确的,即“flag”最终设置为 1。在这种情况下,print data
指令可以退出,但它打印出不正确的值 0。
问题是内存屏障是否会以某种方式阻止 data
的推测读取,并导致 cpu 正确执行忙等待。另一种解决方案可能是让分支预测器完成它的工作,但监听另一个核心完成的写入,如果检测到对 data
的写入,我们可以使用 ROB 撤消过早读取(和它的相关指令),然后使用正确的数据重新执行。
也欢迎特定于 Arch 的答案。
不,b运行ch 预测 + 推测执行在具有内存屏障的 ISA 中很好,只要错误推测被正确杀死。
thus perform a speculative read of data
, which contains 0 at that time
当 CPU 检测到错误预测时,将丢弃来自错误推测的执行路径的指令,以及它们对体系结构寄存器的影响。
当正确的执行路径最终退出循环时,然后内存屏障将运行(再次),然后 data
的负载将 运行(再次)。事实上,他们早些时候 运行 在错误预测的 b运行ch 的阴影下没有影响。
您的伪代码汇编不是很清楚,因为它使 print data
看起来像一个单一的操作。事实上,它将涉及加载到寄存器中,然后是 call print
指令。
当 data
在正确的路径上加载 运行 时,它将不得不重做从缓存中读取值的工作,并且 缓存在核心之间是一致的。错误推测的负载是否将缓存行带入该核心的 L1d 缓存并不重要;另一个核心的商店必须先使其无效,然后该商店才能变得全局可见。
看到exit!=0
后循环退出;之后的障碍确保以后的加载还没有发生,为 exit
的加载提供 acquire
语义(假设它包括阻塞 LoadLoad 重新排序)。
在正确路径上执行的屏障确保该核心等待失效而不是使用早期加载。
编写器中的存储/释放屏障确保新的 data
值在 exit = 1
对任何内核上的任何其他线程可见之前是全局可见的。
本题不假设任何特定架构。 假设我们有一个具有高速缓存一致性、乱序执行和分支预测逻辑的多核处理器。我们还假设存储到内存中严格按照程序顺序。
我们有两个线程 运行 并行,每个都在一个单独的核心上。
以下是线程的伪代码。 data
和 flag
初始为 0。
线程 #1 代码:
data=10;
flag=1;
线程 #2 代码:
while(!flag);
print data;
通过适当的同步,线程 #2 最终会打印 1。但是,分支预测器可能会预测未进入循环,从而执行对 data
的推测性读取,其中当时包含 0 (在线程 #1 设置 data
之前)。预测是正确的,即“flag”最终设置为 1。在这种情况下,print data
指令可以退出,但它打印出不正确的值 0。
问题是内存屏障是否会以某种方式阻止 data
的推测读取,并导致 cpu 正确执行忙等待。另一种解决方案可能是让分支预测器完成它的工作,但监听另一个核心完成的写入,如果检测到对 data
的写入,我们可以使用 ROB 撤消过早读取(和它的相关指令),然后使用正确的数据重新执行。
也欢迎特定于 Arch 的答案。
不,b运行ch 预测 + 推测执行在具有内存屏障的 ISA 中很好,只要错误推测被正确杀死。
thus perform a speculative read of
data
, which contains 0 at that time
当 CPU 检测到错误预测时,将丢弃来自错误推测的执行路径的指令,以及它们对体系结构寄存器的影响。
当正确的执行路径最终退出循环时,然后内存屏障将运行(再次),然后 data
的负载将 运行(再次)。事实上,他们早些时候 运行 在错误预测的 b运行ch 的阴影下没有影响。
您的伪代码汇编不是很清楚,因为它使 print data
看起来像一个单一的操作。事实上,它将涉及加载到寄存器中,然后是 call print
指令。
当 data
在正确的路径上加载 运行 时,它将不得不重做从缓存中读取值的工作,并且 缓存在核心之间是一致的。错误推测的负载是否将缓存行带入该核心的 L1d 缓存并不重要;另一个核心的商店必须先使其无效,然后该商店才能变得全局可见。
看到exit!=0
后循环退出;之后的障碍确保以后的加载还没有发生,为 exit
的加载提供 acquire
语义(假设它包括阻塞 LoadLoad 重新排序)。
在正确路径上执行的屏障确保该核心等待失效而不是使用早期加载。
编写器中的存储/释放屏障确保新的 data
值在 exit = 1
对任何内核上的任何其他线程可见之前是全局可见的。