当前位置: 首页 > 知识库问答 >
问题:

推测执行的CPU分支可以包含访问RAM的操作码吗?

吴俊风
2023-03-14

据我所知,当CPU推测性地执行一段代码时,它会在切换到推测分支之前“备份”寄存器状态,这样,如果预测结果错误(导致分支无效),寄存器状态将安全恢复,而不会损坏“状态”。

所以,我的问题是:推测执行的CPU分支可以包含访问RAM的操作码吗?

我的意思是,访问RAM不是一个“原子”操作——如果数据当前不在CPU缓存中,一个简单的操作码从内存中读取可能会导致实际的RAM访问,从CPU的角度来看,这可能是一个非常耗时的操作。

如果在推测分支中确实允许这样的访问,它是否仅适用于读操作?因为,我只能假设,如果丢弃分支并执行“回滚”,则根据写入操作的大小恢复写入操作可能会变得非常缓慢和棘手。而且,可以肯定的是,至少在某种程度上支持读/写操作,因为据我所知,某些CPU上的寄存器本身物理上位于CPU缓存上。

因此,也许更精确的表述是:推测执行的代码有哪些限制?

共有1个答案

蓝宜
2023-03-14

投机乱序(OoO)执行的主要规则是:

  1. 保持指令按程序顺序顺序运行的假象

OoO exec通常是通过在退休前将一切都视为推测来实现的。每个加载或存储都可能出现故障,每个FP指令都可能引发FP异常。分支是特殊的(与异常相比),只是因为分支预测失误并不罕见,所以有一种特殊的机制来处理分支未命中的早期检测和回滚是很有帮助的。

是的,可缓存的加载可以推测性地执行,因为它们没有副作用。

由于存储缓冲区的存在,也可以推测地执行存储指令。存储的实际执行只是将地址和数据写入存储缓冲区。(相关:英特尔硬件上存储缓冲区的大小?存储缓冲区到底是什么?比这更具技术性,更关注x86。我认为这个答案适用于大多数ISA。)

提交到L1d缓存发生在存储指令从ROB中退出后的一段时间,即当已知存储是非推测性的时,关联的存储缓冲区条目“毕业”,并有资格提交到缓存并全局可见。存储缓冲区将执行与其他核心可以看到的任何内容分离,并且还将此核心与缓存未命中存储隔离,因此即使在顺序CPU上,它也是一个非常有用的功能。

在存储缓冲区条目“毕业”之前,当回滚错误推测时,它可以与指向它的ROB条目一起被丢弃。

(这就是为什么即使是强排序的硬件内存模型也仍然允许StoreLoad重新排序https://preshing.com/20120930/weak-vs-strong-memory-models/-为了获得良好的性能,不让以后的加载等待以前的存储实际提交几乎是必不可少的。)

存储缓冲区实际上是一个循环缓冲区:由前端(在分配/重命名管道阶段)分配的条目,在将存储提交到L1d缓存时释放。(通过MESI与其他岩芯保持一致)。

像x86这样的强有序内存模型可以通过按顺序从存储缓冲区提交到L1d来实现。条目是按程序顺序分配的,因此存储缓冲区基本上可以是硬件中的循环缓冲区。如果存储缓冲区的头部用于尚未就绪的缓存线,则弱序ISA可以查看较年轻的条目。

例如,一些ISA(尤其是弱顺序的)也会合并存储缓冲区条目,以从一对32位存储中创建一个8字节的L1d提交。

读取可缓存内存区域被认为没有副作用,并且可以通过OoO exec、硬件预取或其他方式推测完成。错误猜测会“污染”缓存并通过触摸真正的执行路径不会(甚至可能触发TLB未命中的推测性页面遍历),但这是唯一的缺点1

MMIO区域(读取确实有副作用,例如使网卡或SATA控制器执行某些操作)需要标记为不可缓存,以便CPU知道不允许从该物理地址进行推测性读取。如果你弄错了,你的系统就会不稳定——我的答案涵盖了很多你所问的投机负载的相同细节。

高性能CPU有一个带有多个条目的负载缓冲区,用于跟踪飞行中的负载,包括L1d缓存中丢失的负载。(仅当指令尝试读取尚未准备就绪的加载结果寄存器时,才允许在顺序CPU中启用未命中命中和未命中下的未命中,并暂停)。

在OoO exec CPU中,当一个加载地址在另一个加载地址之前准备就绪时,它还允许OoO exec。当数据最终到达时,等待加载结果输入的指令将准备好运行(如果它们的其他输入也准备好了)。因此,加载缓冲区条目必须连接到调度器(在某些CPU中称为保留站)。

另请参阅关于RIDL漏洞和加载的“重播”,以了解Intel CPU如何在数据可能从L2到达L2命中的周期内积极尝试启动等待的UOP,从而专门处理这些UOP的更多信息。

脚注1:这一缺点与用于检测/读取微体系结构状态(缓存线热态或冷态)到体系结构状态(寄存器值)的定时侧通道相结合,使Spectre成为可能。(https://en.wikipedia.org/wiki/Spectre_(security\u漏洞)#机制)

了解熔毁对于了解Intel CPU如何选择处理错误路径上的推测负载的故障抑制的详细信息非常有用。http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/

当然,还支持读/写操作

是的,通过将它们解码为逻辑上独立的加载/ALU/存储操作,如果您谈论的是解码为指令uops的现代x86。加载的工作方式类似于正常加载,存储将ALU结果放入存储缓冲区。所有3个操作都可以由乱序后端正常调度,就像您编写单独的指令一样。

如果你指的是原子RMW,那么这不能真的是推测性的。缓存是全局可见的(共享请求可以随时出现),无法回滚(好吧,除了Intel为事务性内存所做的任何操作…)。决不能在缓存中放入错误的值。请参见“int num”的num是否可以是原子的?有关如何处理原子RMW的更多信息,尤其是在现代x86上,通过延迟对加载和存储提交之间该行的共享/无效请求的响应。

然而,这并不意味着eax序列化了整个管道:加载和存储的是唯一被重新排序的指令吗?表明其他独立指令的推测OoO exec可以发生在原子RMW周围。(与像lfence这样耗尽ROB的执行障碍相比)。

许多RISC ISA仅通过加载链接/存储条件指令提供原子RMW,而不是单个原子RMW指令。

[读/写操作…],至少在某种程度上,由于某些CPU上的寄存器本身在物理上位于CPU高速缓存上,正如我所理解的那样。

嗯?错误的前提,这种逻辑没有意义。缓存必须始终正确,因为另一个核心可能会要求您随时共享它。与此核心专用的寄存器不同。

寄存器文件是用类似SRAM的缓存构建的,但它们是独立的。有一些微控制器板上带有SRAM内存(非高速缓存),寄存器使用该空间的早期字节进行内存映射。(例如AVR)。但这些似乎都与无序执行无关;缓存内存的缓存线绝对不是用于完全不同的东西的缓存线,例如保存寄存器值。

将晶体管预算用于推测性执行的高性能CPU是否会将缓存与寄存器文件结合起来,这也是不太可能的;然后他们将竞争读/写端口。一个具有总读写端口数的大型缓存比一个小型快速寄存器文件(许多读/写端口)和一个具有两个读端口和一个写端口的小型(如32kiB)L1d缓存要昂贵得多(面积和功耗)。出于同样的原因,我们使用拆分的一级缓存,并具有多级缓存,而不是现代CPU中每个核心只有一个大型专用缓存。为什么在大多数处理器中,一级缓存的大小小于二级缓存的大小?

相关阅读/背景:

  • https://stackoverflow.com/tags/x86/info有一些很好的CPU架构链接
  • https://www.realworldtech.com/haswell-cpu/5/大卫·坎特的哈斯韦尔深潜
  • Intel硬件上存储缓冲区的大小?存储缓冲区究竟是什么
  • 什么是存储缓冲区
  • 存储缓冲区和行填充缓冲区如何相互作用
  • 无序执行与推测性执行-在退休之前,一切都是推测性的。我的回答集中在熔毁方面
  • http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/
  • 当skylake CPU预测失误分支时,会发生什么情况
  • https://en.wikipedia.org/wiki/MESI_protocol#Store_Buffer
  • https://en.wikipedia.org/wiki/Write_buffer(这不是一篇很好的文章,但提到它是为了完整性)
  • 内存重新排序如何帮助处理器和编译器?(StoreLoad重新排序允许使用存储缓冲区,这对于良好的性能至关重要。)
  • https://en.wikipedia.org/wiki/Memory_disambiguation-CPU如何处理从存储缓冲区到负载的转发,或者如果存储区实际比该负载年轻(按程序顺序较晚),CPU如何处理转发
  • https://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/-在x86处理器中存储到加载转发和内存消歧。关于存储转发的非常详细的测试结果和技术讨论,包括与存储的不同部分重叠的窄负载以及缓存线边界附近的负载。(https://agner.org/optimize/在他的microach PDF中有一些更容易理解但不太详细的信息,关于商店转发的速度是慢还是快。)
  • https://github.com/travisdowns/uarch-bench/wiki/Memory-Disambiguation-on-Skylake-当有未知地址的早期存储在运行中时,现代CPU动态预测负载的内存依赖性。(即尚未执行存储地址uop。)如果预测错误,这可能导致必须回滚
  • 全局不可见的加载指令—从部分与最近的存储重叠且部分不重叠的加载进行存储转发,这为我们提供了一个转折点,让我们了解CPU是如何工作的,以及思考内存(排序)模型是如何有意义的。请注意,C std::atomic无法创建这样做的代码,尽管C 20 std::atomic\u ref可以让您执行与对齐的8字节原子负载重叠的对齐的4字节原子存储
 类似资料:
  • 给定以下数据帧, 是否可以在单个聚合函数中计算和的和, ? 在R的dplyr中,我会用单行的来完成, 我想知道在Pandas中有什么等价的: 预期结果:

  • 在现代的奔腾处理器上,似乎不再可能给处理器分支提示。假设一个像gcc这样的带有概要引导优化的概要编译器获得了关于可能的分支行为的信息,那么它能做些什么来产生执行得更快的代码呢? 我知道的唯一选择是将不太可能的分支移动到函数的末尾。还有别的吗? 更新。 http://download.intel.com/products/processor/manual/325462.pdf2a卷2.1.1节说 “

  • 本文向大家介绍用Maven打成可执行jar,包含maven依赖,本地依赖的操作,包括了用Maven打成可执行jar,包含maven依赖,本地依赖的操作的使用技巧和注意事项,需要的朋友参考一下 因为今天一个朋友学习过程中用到了maven项目编写代码,到了最后打包阶段打的包不能运行,一时我也没想起来具体操作步骤,后来我百度学习了一下,特此记录下,以便后续自己查阅。 maven项目中不可避免的需要用到依

  • 我试图利用Ubuntu 16.04上的崩溃安全漏洞,英特尔酷睿-i5 CPU上的未修补内核4.8.0-364300M。 首先,我使用内核模块将机密数据存储在内核空间的一个地址: printk语句给我秘密数据的地址。 然后,我尝试访问此位置的数据,并在下一条指令中使用它缓存数组的元素。 当执行无序执行时,我希望CPU继续并在索引处缓存数组元素(数据*4096 DELTA)。在此之后,执行边界检查并抛

  • 问题内容: 我想知道是否可以在包含Java小应用程序的HTML页面上写一些内容。 更一般而言,这两者之间可能发生什么相互作用? 谢谢。 问题答案: 从您的Java小程序中 然后,您可以像往常一样使用javascript来操纵html页面。 声明小程序时,可能还需要包括mayscript参数,不确定是否需要了。

  • $ git branch 添加-a选项,就可以显示包括远端分支在内的分支清单。 创建分支 $ git branch <branchname> 高级篇 【教程1 操作分支】 1. 建立分支 修改分支的名称 $ git branch -m <oldbranch> <newbranch> 删除分支 $ git branch -d <branchname> 若有未合并到HEAD的提交,则不能删除分支。如果