1.3 流水线
1.3.1 Cortex-A77微架构
流水线是处理器微架构设计的一种技术,其主要目的在于提高处理器的性能。基本思想与工厂的流水线相似,将指令要完成的任务分为多个阶段,这样每个阶段都可以有一条正在执行的指令,每个阶段的指令执行完交给下一个阶段继续执行,如此可以大幅度提高处理器的指令吞吐量。
在流水线中加入超标量与乱序执行技术是现代主流高性能CPU的基本微架构。超标量技术是指使用多条特定执行功能的流水线,一次取多条指令同时放入不同流水线中执行。乱序执行技术是指在选择指令放入不同流水线执行时,不依赖指令顺序决定先后执行关系,只要指令所需资源已准备就绪即可执行。
由于连续的指令流之间存在很多相关性,因此处理器并不能使流水线中的指令直接地并行执行到结束。如对同一通用寄存器或同一存储器地址的先写后读(Read After Write,RAW)、先写后写(Write After Write,WAW)、先读后写(Write After Read,WAR)。在出现相关性的时候,使用很多额外的技术来处理这些情况,其中包括事务阻塞(stall)、旁路网络(bypassing network)、事务重发(replay)、寄存器重命名(register renaming)等技术。下面就基于AArch64中一款处理器——Cortex-A77来简单介绍这些技术和软件优化的关系。Cortex-A77是一款典型的超标量乱序流水线处理器,其微架构如图1.2所示。
简要介绍图中涉及的术语。
● 宏操作缓存:用于缓存指令译码后的宏操作。
● 返回地址栈:一种记录链接跳转类型指令将来返回时PC值的表,由于链接寄存器只有一个,因此一般链接跳转类型指令的返回地址会保存在栈中,此结构对这种特性进行了优化。
● 分支预测单元:通过记录曾经执行过的分支型指令的历史信息来预测将来指令流的PC值,如果预测错误,将会导致流水线中错误的指令全部被清除,这将严重影响性能。
● 重排序缓冲:记录指令执行的状态,对已执行完毕的指令按序使其从流水线中正式离开。
● 发射队列:存放待执行的指令信息,其中的指令准备就绪即会被交给相应的功能单元计算结果。
● 分发:将译码后的指令放入发射队列和重排序缓冲等状态信息记录表中。
此处理器的前端部分主要是分支预测、取指、译码。分支预测是为了在遇到分支指令时也可以保证连续地提供指令流而不需要等待分支类型指令的结果,通过与一个称作L0的MOP (Macro-Operation,宏操作)缓存协作,最终峰值可以输出6路宏操作。由于有预测错误代价,因此大量使用分支型指令会严重影响处理器的吞吐量。
在后端接收6路宏操作进行寄存器重命名、重排序缓冲、体系结构状态提交,得到10路微操作并进行分发(dispatch),通过发射队列(issue queue)进入12路执行单元通道。发射队列分为3个部分——整数运算、高级SIMD和访存。每路执行单元不关心其他单元的执行内容及情况。所以在程序中可以利用这种设计对代码中指令的顺序进行优化,进而获得更大的吞吐量。每种不同的指令在执行级使用的功能单元不同,功能单元执行内容和数量不同会导致不同类别指令在CPU中的吞吐量和延迟不同。详细参数请查阅ARM的官方手册。
图1.2 Cortex-A77的微架构
1.3.2 微架构与代码优化
下面将根据Cortex-A77的结构特点,介绍如何在程序中针对指令吞吐量进行优化。
分发阶段经过调度分配给发射阶段中不同执行通道的微操作数量有限制。当代码中的指令堆叠不超过限制时,在分发阶段将不会出现硬件缺失导致的微操作停顿。所以尽量不要将超过限制的同类型指令堆叠在一起,这样对吞吐量是不利的,微架构缓冲将被迅速填满,阻碍后面指令的执行。将具有不同微操作且不相关的指令交错放在一起,可以达到尽可能充满执行单元通道的目的,这样利用率和吞吐量都是最优的。但这只是理想情况,大多数时候,前后指令是存在数据相关性的。这个时候,指令将会滞留在分发阶段,等待操作数准备就绪。
在上述限制内根据执行元件数量及执行元件流水线吞吐量安排同类型的指令可以得到最大的吞吐量。例如,FP divide, S-form(S指单精度浮点型数据)指令组,AArch64的指令有FDIV,这是一条浮点数除法指令,使用FP/ASIMD 0(V0)执行。SIMD器件可以并行对4组数据执行相同的操作,由于除法指令使用的是迭代算法完成,因此只有一个除法操作完成了才可以进行下一个除法操作。一个除法操作的执行延迟是7个CPU周期,所以在数据不相关的时候其吞吐量为4/7(指令数每周期),只需要将4条以上数据不相关的FDIV指令排列在一起即可达到这个最大吞吐量。这种方法等效于使用一条类似的SIMD指令,即指令组ASIMD FP divide,Q-form,F32(Q-form指4路数据并发,F32指单精度浮点型数据)中的FDIV指令(注意,这里指令名和之前相同,但是这里使用的是ASIMD寄存器)。
之前简单介绍过分支预测在流水线技术中的重要性,为了配合流水线技术获得最佳性能,代码中应尽量保证在一个对齐32字节的存储区中放置超过4条分支指令。要使内存复制操作具有较大的吞吐量,建议在原本的循环中展开6次循环,以适应具有6路宏操作输出的结构,避免过多地进行无谓的分支预测。