CPU 不平衡流水线问题处理

感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53249.html

乱序执行核心(OOO core)

其实乱序执行的思想很简单:当下一条指令被阻塞的时候,从后面的指令里再找一条能执行的就好了嘛。但要完成这个工作却相当复杂。首先要保证程序的最终结果与顺序执行一致,同时要识别各类数据依赖。要达到理想的效果,除了并行执行之外,还需要对指令的粒度进一步细化,以达到以无厚入有间的效果,这样就引入了“微操作”(micro-operations, μ-ops) 的概念。在流水线的 Decode 阶段,汇编指令又被进一步拆解,最终的产物就是一系列的微操作。

CPU 不平衡流水线问题解决

上图就是引入乱序处理核心之后的指令μ-ops 处理流程。不同颜色的模块对应第一张图中不同颜色的流水线处理阶段。

Fetch 阶段没有太多变化,在 Decode 阶段,可以并行对四条指令解码,解码的最终产物就是上面提到的μ-ops。后面的 Register Alias Table 和 Reorder Buffer 可以当做是乱序执行核心的预处理阶段。

对于并行执行的微操作,或者乱序执行的操作,很有可能会同时读写同一个寄存器。所以在处理器内部,原始的寄存器便被“别名”(aliased) 为内部对软件工程师不可见的寄存器,这样原本在同一个寄存器上执行的操作便可以在临时性的不同的寄存器上执行,无论读写,互不干扰 (注意:这里要求两个操作没有数据依赖)。而对应的微操作的操作数也变为了临时性的别名寄存器,相当于一种空间换时间的策略,并且同时对微指令进行了一次基于别名寄存器的转译。

之后微操作进入 Reorder Buffer。至此,微指令已经准备就绪。它们会被放入 Reservation Station(RS) 并被并行执行。从图中可以看到相当多的执行单元 (Port X)。每一个执行单元都执行一个特定的任务,比如读取 (Load),写入 (Store),整数计算(ALU, SEE)等等。而每一条相关的微指令都可以在它所需要的数据准备好之后执行。这样耗时较长的指令和有数据依赖关系的指令,虽然单从其自身的角度看,并没有任何变化,但它们所带来的阻塞的开销,被后续指令的并行及乱序(提前)执行所分摊,化整为零,带来整体吞吐的提升。

乱序执行核心的神奇之处就在于,它能够最大限度地提升这套机制的效率,并且在外界看来,指令是在顺序执行。这里面的详细细节不在本文的讨论范畴。但乱序执行核心是如此成功,以至于引入该机制的 CPU 即便是在大工作负载的情况下乱序执行核心仍会在大部分时间处于空闲的状态,远未饱和。因此,又引入了另外一个前端 (Front-end, 包括 Fetch 和 Decode) 给该核心输送μ-ops,在系统看来,便可以抽象为两个处理核心,这也就是超线程 (Hyper-thread)N 个物理核心,2N 个逻辑核心的由来。

乱序执行也并不一定 100% 达到顺序执行代码的效果。有些时候确实需要程序员引入内存屏障来确保执行的先后顺序。

但复杂的事物总会引入新的问题,这次矛盾转移到了 Fetch 阶段。如何在面对分支的时候选取正确的路?如果指令选取错误,整条流水线需要首先等待剩余指令执行完毕,清空之后再重新从正确的位置开始。流水线的层次越深,造成的伤害越大。后续的文章,将会介绍一些在编程层面优化的方法。感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53249.html

乱序执行核心(OOO core)

其实乱序执行的思想很简单:当下一条指令被阻塞的时候,从后面的指令里再找一条能执行的就好了嘛。但要完成这个工作却相当复杂。首先要保证程序的最终结果与顺序执行一致,同时要识别各类数据依赖。要达到理想的效果,除了并行执行之外,还需要对指令的粒度进一步细化,以达到以无厚入有间的效果,这样就引入了“微操作”(micro-operations, μ-ops) 的概念。在流水线的 Decode 阶段,汇编指令又被进一步拆解,最终的产物就是一系列的微操作。

CPU 不平衡流水线问题解决

上图就是引入乱序处理核心之后的指令μ-ops 处理流程。不同颜色的模块对应第一张图中不同颜色的流水线处理阶段。

Fetch 阶段没有太多变化,在 Decode 阶段,可以并行对四条指令解码,解码的最终产物就是上面提到的μ-ops。后面的 Register Alias Table 和 Reorder Buffer 可以当做是乱序执行核心的预处理阶段。

对于并行执行的微操作,或者乱序执行的操作,很有可能会同时读写同一个寄存器。所以在处理器内部,原始的寄存器便被“别名”(aliased) 为内部对软件工程师不可见的寄存器,这样原本在同一个寄存器上执行的操作便可以在临时性的不同的寄存器上执行,无论读写,互不干扰 (注意:这里要求两个操作没有数据依赖)。而对应的微操作的操作数也变为了临时性的别名寄存器,相当于一种空间换时间的策略,并且同时对微指令进行了一次基于别名寄存器的转译。

之后微操作进入 Reorder Buffer。至此,微指令已经准备就绪。它们会被放入 Reservation Station(RS) 并被并行执行。从图中可以看到相当多的执行单元 (Port X)。每一个执行单元都执行一个特定的任务,比如读取 (Load),写入 (Store),整数计算(ALU, SEE)等等。而每一条相关的微指令都可以在它所需要的数据准备好之后执行。这样耗时较长的指令和有数据依赖关系的指令,虽然单从其自身的角度看,并没有任何变化,但它们所带来的阻塞的开销,被后续指令的并行及乱序(提前)执行所分摊,化整为零,带来整体吞吐的提升。

乱序执行核心的神奇之处就在于,它能够最大限度地提升这套机制的效率,并且在外界看来,指令是在顺序执行。这里面的详细细节不在本文的讨论范畴。但乱序执行核心是如此成功,以至于引入该机制的 CPU 即便是在大工作负载的情况下乱序执行核心仍会在大部分时间处于空闲的状态,远未饱和。因此,又引入了另外一个前端 (Front-end, 包括 Fetch 和 Decode) 给该核心输送μ-ops,在系统看来,便可以抽象为两个处理核心,这也就是超线程 (Hyper-thread)N 个物理核心,2N 个逻辑核心的由来。

乱序执行也并不一定 100% 达到顺序执行代码的效果。有些时候确实需要程序员引入内存屏障来确保执行的先后顺序。

但复杂的事物总会引入新的问题,这次矛盾转移到了 Fetch 阶段。如何在面对分支的时候选取正确的路?如果指令选取错误,整条流水线需要首先等待剩余指令执行完毕,清空之后再重新从正确的位置开始。流水线的层次越深,造成的伤害越大。后续的文章,将会介绍一些在编程层面优化的方法。感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53249.html

乱序执行核心(OOO core)

其实乱序执行的思想很简单:当下一条指令被阻塞的时候,从后面的指令里再找一条能执行的就好了嘛。但要完成这个工作却相当复杂。首先要保证程序的最终结果与顺序执行一致,同时要识别各类数据依赖。要达到理想的效果,除了并行执行之外,还需要对指令的粒度进一步细化,以达到以无厚入有间的效果,这样就引入了“微操作”(micro-operations, μ-ops) 的概念。在流水线的 Decode 阶段,汇编指令又被进一步拆解,最终的产物就是一系列的微操作。

CPU 不平衡流水线问题解决

上图就是引入乱序处理核心之后的指令μ-ops 处理流程。不同颜色的模块对应第一张图中不同颜色的流水线处理阶段。

Fetch 阶段没有太多变化,在 Decode 阶段,可以并行对四条指令解码,解码的最终产物就是上面提到的μ-ops。后面的 Register Alias Table 和 Reorder Buffer 可以当做是乱序执行核心的预处理阶段。

对于并行执行的微操作,或者乱序执行的操作,很有可能会同时读写同一个寄存器。所以在处理器内部,原始的寄存器便被“别名”(aliased) 为内部对软件工程师不可见的寄存器,这样原本在同一个寄存器上执行的操作便可以在临时性的不同的寄存器上执行,无论读写,互不干扰 (注意:这里要求两个操作没有数据依赖)。而对应的微操作的操作数也变为了临时性的别名寄存器,相当于一种空间换时间的策略,并且同时对微指令进行了一次基于别名寄存器的转译。

之后微操作进入 Reorder Buffer。至此,微指令已经准备就绪。它们会被放入 Reservation Station(RS) 并被并行执行。从图中可以看到相当多的执行单元 (Port X)。每一个执行单元都执行一个特定的任务,比如读取 (Load),写入 (Store),整数计算(ALU, SEE)等等。而每一条相关的微指令都可以在它所需要的数据准备好之后执行。这样耗时较长的指令和有数据依赖关系的指令,虽然单从其自身的角度看,并没有任何变化,但它们所带来的阻塞的开销,被后续指令的并行及乱序(提前)执行所分摊,化整为零,带来整体吞吐的提升。

乱序执行核心的神奇之处就在于,它能够最大限度地提升这套机制的效率,并且在外界看来,指令是在顺序执行。这里面的详细细节不在本文的讨论范畴。但乱序执行核心是如此成功,以至于引入该机制的 CPU 即便是在大工作负载的情况下乱序执行核心仍会在大部分时间处于空闲的状态,远未饱和。因此,又引入了另外一个前端 (Front-end, 包括 Fetch 和 Decode) 给该核心输送μ-ops,在系统看来,便可以抽象为两个处理核心,这也就是超线程 (Hyper-thread)N 个物理核心,2N 个逻辑核心的由来。

乱序执行也并不一定 100% 达到顺序执行代码的效果。有些时候确实需要程序员引入内存屏障来确保执行的先后顺序。

但复杂的事物总会引入新的问题,这次矛盾转移到了 Fetch 阶段。如何在面对分支的时候选取正确的路?如果指令选取错误,整条流水线需要首先等待剩余指令执行完毕,清空之后再重新从正确的位置开始。流水线的层次越深,造成的伤害越大。后续的文章,将会介绍一些在编程层面优化的方法。

发布了0 篇原创文章 · 获赞 0 · 访问量 2430

猜你喜欢

转载自blog.csdn.net/zxjoke/article/details/105435694
今日推荐