计算机体系的一些基本知识

解决流水线停滞主要是解决:资源争用数据依赖性控制依赖性1. 资源争用资源争用指,流水线中的两个阶段都需要某个硬件资源,解决资源争用的主要方法:复制资源,例如:使用独立的数据、指令内存,内存结构提供多个读写端口检测资源争用,停滞其中一个阶段例如,在ID阶段需要访问RegFile,以取出操作数,在WB阶段也需要访问RegFile,以将执行结果写回寄存器组中,这样就发生了对RegFile这一资源的争用。实际中,可以让RegFile在前半个周期给ID使用,后半个周期给WB使用。2. 数据依赖性数据依赖性包括:先写后读(read after write,RAW)、先写后写(write after write,WAW)、先读后写(write after read,WAR)依赖。RAW (Flow Dependency)——真正的数据依赖性

 

计算机体系的一些基本知识

 

WAR (Anti Dependency)

 

计算机体系的一些基本知识

 

WAW (Output Dependency)

 

计算机体系的一些基本知识

 

其中,WAW、WAR是由于寄存器数量有限,要不断地重用寄存器名而造成的依赖性,实际上他们是对寄存器名的依赖,而非寄存器值的依赖,因此通过寄存器重命名解决。寄存器重命名将在后续实现精确异常时介绍,这篇文章主要讲介绍RAW数据依赖性的解决。解决RAW依赖性的方法:检测并等待(stall)√检测并绕行数据(bypass/forward)√检测并在软件层面消除依赖性(software level: compiler,插入nop)检测并将其移动到独立的指令之后(指令重排序,乱序执行)预测需要的值,试探性地执行,最后检验其他如细粒度多线程,无需检测2.0 依赖性检测(互锁)检测流水线中指令间的依赖性,以保证正确执行。也分为软件层面、硬件层面的互锁。实际上,MIPS也可称作 microprocessor without interlocking pipeline stages,也就是说硬件层面不做互锁、软件层面做互锁。依赖性检测方法:计分板(Scoreboarding)给RegFile中的每个寄存器 设置一个有效位Valid bit。如果一个指令要写寄存器,则将其有效位置0;在指令译码阶段,检测是否这条指令的 源、目的寄存器的有效位都为1,如果为1:无依赖性,如果为0:有依赖性、停滞流水线。 优点是简单,缺点是会因为所有类型的依赖性而停滞。组合逻辑检测设置特定的组合逻辑来检测 指令后面的阶段是否会写到当前正在译码出的寄存器中。 优点是不会在WAW、WAR依赖上停滞,缺点是逻辑更复杂、且随着流水线变深 逻辑很复杂。2.1 检测并等待等到指定数据被写入RegFile后,再从RegFile读操作数,会造成流水线停滞,很慢

 

计算机体系的一些基本知识

 

流水线停滞(stall)的实现方法:使PC 或者 IF/ID锁存器 失能(disable),确保停顿的指令 保持在原来的阶段插入nops指令2.2 数据绕行(data bypass)对于有数据依赖性的两条指令,后一条指令 只能等 前一条指令将结果写回RegFile,才能用写回的寄存器中的数据,这样会使流水线停滞、减小吞吐量、降低性能。可以通过 依赖性检测+数据绕过,一旦计算出前一条指令的结果,虽然还没到写回的阶段,可以提前把结果送给后一条指令。举例:

 

计算机体系的一些基本知识

 

① 在执行阶段,完成了加法运算,此时已经得到了应该写回s0寄存器的值;由于第二条指令的源寄存器 是 第一条指令的目的寄存器,存在RAW依赖性,如果等到写回后再执行第二条指令,效率很低;因此直接将第一条指令执行后的运算结果,提供给第二条指令的执行阶段,就能提高效率② 同理,在还没写回前,将运算结果先提供给后续的指令 作为源操作数③ 利用写回在前半周期、取操作数在后半周期(满足设计条件自动实现,可以看做无依赖性)对应到硬件实现:

 

计算机体系的一些基本知识

 

Hazard Unit 用来检测依赖性(检测方法如score boarding),检测出指令间的依赖性后,产生控制信号ForwardAE、ForwardBE,用来控制两个MUX,00表示无数据依赖性,直接使用对应的源寄存器值即可(对应举例中的③)01表示有数据依赖性,从访存后取结果 替代源寄存器的值(对应举例中的①)10表示有数据依赖性,从执行后的锁存器中取结果 替代源寄存器的值 (对应举例中的②)数据bypass 一般发生在 访存 或 写回 阶段,如果某个阶段要写目的寄存器、并且目的寄存器和后面的源寄存器冲突,就使用数据bypass。如果访存和写回阶段都和目的寄存器冲突,那么优先使用访存阶段的结果,因为它的值时最新执行出来的。数据绕行的特例——lw指令lw指令在访存阶段后才能得到执行结果,因此下一条依赖性指令必须stall一个周期,才能进行数据绕行。

 

计算机体系的一些基本知识

 

硬件实现:从Hazard Unit引出StallD和StallF,迫使译码和取指阶段的寄存器保持它们原有的值从Hazard Unit引出FlushE,清除执行阶段寄存器的内容,插入一个气泡(理解为此时用不到EX阶段的硬件资源)

 

计算机体系的一些基本知识

 

3. 控制依赖性控制依赖性,即对 程序计数器PC 依赖性。由于PC中存放着下一条指令的地址,因此所有的指令都和前一条指令有控制依赖性:如果下一条指令不是控制跳转指令,因为我们知道指令长度,因此可以很容易地 算出下一条顺序指令的PC如果下一条指令是控制跳转指令,例如,beq(branch equal)知道第四个阶段才知道是否要跳转,也就是分支指令的下一条指令的取指 是先于 分支指令被解析的。一旦分支预测错误,那么从分支指令往后的指令都会被冲刷。

 

计算机体系的一些基本知识

 

第一条指令在执行后,发现预测错误,需要跳转,于是进行流水线冲刷,这样就浪费了三个周期。解决方法:等分支指令执行完成,再执行后续的指令提前解析分支(Early Branch Resolution)做分支预测,并提高分支预测的准确性3.1 提前解析分支在译码阶段就提前判断。例如beq指令,提前判断两个源操作数是否一样:

 

计算机体系的一些基本知识

 

这样在第二个阶段就完成了分支判断,在第三个阶段开始下一条指令,最多浪费一个周期:

 

计算机体系的一些基本知识

 

优点:减小分支预测失误惩罚,减小CPI(cycle per instruction)缺点:会增加每个时钟周期的时间,频率降低额外的硬件开销,这种特定的硬件不会别的指令用到最终的MIPS五级流水线处理器

 

计算机体系的一些基本知识

 

包括数据依赖性检测、数据绕行、停滞、提前分支解析的逻辑。

 

 


1. 为什么要引入流水线?

 

 

对于一条指令,我们可以将其执行划分为以下五个步骤:

 

 

取指(IF,Instruction Fetch)

 

 

译码(ID,Instruction Decode)

 

 

执行(EX,Execute)

 

 

访存(MEM,Memory Access)

 

 

写回(WB,Write Back)

 

 

一个时钟周期执行上述步骤中的一个,那么理论上执行一条指令需要5个时钟周期,即CPI(Cycles Per Instruction)为5。

 

 

这种处理器叫作多周期处理器,其本质是状态机,不足在于并发性不足:在不同的指令处理周期中,有一些硬件资源是空闲的,例如:当指令在ID或EX时,IF资源是空闲的;在访存时,大多数数据路径都是空闲的。

 

 

因此引入流水线,利用空闲的硬件资源来提升并发性,获得更高的指令吞吐量。

 

 

2. 流水线

 

 

2.1 基本思想

 

 

把指令处理周期分成不同的处理阶段,确保在每个阶段有足够的硬件资源来处理一条指令,同一时间每个阶段在处理不同的指令。

 

 

如下图,当达到稳定状态后,以后的每个时钟周期都能完成一条指令,即CPI=1,吞吐量提升到原来的5倍。

 

计算机体系的一些基本知识

 

2.2 理想与实际的流水线

 

计算机体系的一些基本知识

 

2.3 流水线级数与吞吐量流水线在实现时,用寄存器将每个阶段隔开,当时钟周期到来时,将前一阶段的处理结果保存在寄存器,并输出给下一个阶段。在没有流水线情况下,延时为T,最后一级寄存器延时为S,则BW=1/(T+S)。(BW即bandwidth)

 

计算机体系的一些基本知识

 

对于k级流水线,均匀划分的情况下,每一级之间用寄存器隔开。每一级延时为T/k,寄存器延时为S,则BW_{k-stage}=1/(T/k+S)。虽然增加了吞吐量,但是每一级之间加入寄存器,增大了硬件消耗,并且这些需要传给下一级寄存器中的数据可能是非常庞大的。

 

计算机体系的一些基本知识

 

高流水线级数的处理器是追求更高的主频,以获取更高的吞吐率和性能。低流水线级数的处理器是极低的功耗,提高能效比。2.4 流水线中的控制信号对于一条给定的指令,其控制信号和单周期控制信号一样,只是控制信号是在不同的周期才被需要,两种思路:和单周期处理器一样,由指令一下子译码出全部控制信号,通过寄存器逐级向后传播至需要的阶段使用

 

计算机体系的一些基本知识

 

把指令字向后传播,在相应的阶段 译码出 需要的控制信号实际上,现在的处理器往往采用两者结合的方式。2.5 流水线设计中的问题平衡每个阶段的工作量,尽可能均匀划分使流水线在一些因素扰乱的情况下,尽可能 正确、充满、不断地流动,减小停滞(stall)解决指令间依赖性——数据依赖性、控制依赖性解决资源争用解决多周期操作的长延时问题处理异常、中断不断提高吞吐量,减小停滞

这篇文章主要将之前的流水线更加一般化,引入多周期指令的执行,并分析多周期指令执行时存在的问题,如何在含多周期操作的流水线中实现精确异常,给出ROB、HB、FF、Checkpoint这几种解决方法,本文主要详细介绍了最常用的ROB,ROB不仅便于实现精确异常,还能够通过寄存器重命名解决上一篇文章中提到的WAR、WAW依赖性,此外还为下一篇文章中要介绍的乱序执行打下基础。1. 多周期指令的执行在执行阶段,不同的指令会花费不同的执行周期数,对应地在EX阶段有多种功能单元(Function Unit,FU),它们需要花费不同的时间。

 

计算机体系的一些基本知识

 

例如,整数加法与除法程序:

 

计算机体系的一些基本知识

 

但这样的问题在于,违背了冯诺依曼架构,指令的完成并不是按顺序的,不符合ISA顺序执行的语义!!!除非在DIV执行完成前,后面执行的指令都是与其独立的。也正是利用这一点,可以合理调整指令执行顺序,乱序执行。并且如果除法指令在执行时发生了异常,那么很难找到除法指令对应的精确PC。2. 异常与中断异常:程序执行的内部问题(eg:除数为0)中断:在程序执行时,需要处理的外部事件(eg:键盘输入)异常和中断都需要:在当前运行处停止保存当前状态值处理异常/中断(在handler中处理)返回原程序继续运行(如果需要的话)3. 精确异常/中断当要处理异常/中断时,体系结构状态应该是精确的。精确指,能够精确地知道,只执行完异常指令后的RegFile、PC、Memory。需要满足:所有之前的指令全部交付之后的指令不能交付这里的交付/退休(commit/retire)指,完成执行并更新体系结构状态。3.1 为什么需要精确异常?保持冯诺依曼模型顺序执行的语义帮助软件调试使能够更容易地从异常中恢复使能够更容易地重新开始运行程序使能够陷入(trap)软件(如:软件实现的操作码)3.2 在流水线中检查、处理异常当最老的、准备退休的 指令被检测出会造成异常,控制逻辑将:确保体系结构状态是精确的(RegFile、IP、Memory)冲刷流水线中所有年轻的指令保存ISA规定的IP和寄存器重定位取指引擎到异常handler4. 多周期指令的精确异常处理解决办法:重排序缓冲(Reorder Buffer, ROB)历史缓冲(History Buffer)未来缓冲(Future Buffer)检查点(Checkpointing)下面主要介绍ROB:4.1 Reorder BufferROB解决流水线执行多周期指令问题的主要思想是:允许指令乱序执行完成,在指令执行完成后先将它们的结果先写到ROB,但是需要在结果对体系结构状态有效之前,对它们做重排序,在指令退休时将结果写回RegFile。

 

计算机体系的一些基本知识

 

ROB中存储着所有被译码、还没退休的指令信息:当指令译码时,在ROB中按顺序保留下一个entry当指令完成时,把结果写入ROB对应的entry当最老的指令没有异常地完成执行,将其结果写入RegFile或内存4.2 ROB EntryROB的一个entry为:(ROB缓存中的一行)

 

计算机体系的一些基本知识

 

V:用于指示当前存储空间是否有效(设想一条指令已经退休了,则V可设为无效)DestRegID:目标寄存器的IDDestRegVal:待存入目标寄存器的值StoreAddr:目标存储器地址StoreData:待存入目标存储器的值PC:这条指令对应的PCValid bits:需要有效位来确定指令是否已经执行完成Exception:是否发生异常的标志位(对于整体的ROB有oldest、youngest指针)其中信息用于实现以下操作:将ROB中的指令变回原来的顺序如果指令可以没有异常地退出,用指令的执行结果 更新体系结构状态如果一条指令在退休前出现异常/中断,精确处理异常/中断4.3 ROB用到 独立指令的流水线中在执行阶段后先将结果写到ROB,在指令退休时将结果写入RegFile。这样就符合冯诺依曼结构的顺序执行语义,并且方便精确异常的实现。

 

计算机体系的一些基本知识

 

4.4 ROB用到 非独立指令的流水线中后续指令需要之前执行完、但还没退休的指令的执行结果,这一结果可能存在ROB。可以通过停滞流水线,或从ROB中读取值来实现,显然后者是一种更优的选择。实际上,寄存器结果可能在 RegFile、ROB、bypass paths中:

 

计算机体系的一些基本知识

 

如果指令尚未执行完,则等指令执行完后、尚未写回ROB时,通过bypass paths获得value如果指令执行完(已经写入ROB)、尚未退休(尚未写回RegFile),通过ROB获取value如果指令已退休(写回RegFile),通过RegFile获取值。4.5 ROB的访问ROB的直接访问:ROB表设置的有oldest和youngest指针,分别指向最老的、还没退休的指令 和 最年轻的、刚译完码的指令。oldest到youngest所指的指令,是按照程序顺序排列的,可以据此访问ROB对应的Entry。ROB的间接访问:给RegFile中的每个寄存器设置 有效位、ROB ID、寄存器值。采用间接访问的方法,首先访问RegFile,检测目标寄存器是否是有效的,如果无效,那么RegFile中存储着对应的ROB ID,于是访问对应的ROB Entry即可。

 

计算机体系的一些基本知识

 

4.6 重点:用ROB给寄存器重命名之前讲到WAR(先读后写)、WAW(先写后写)不是真正的数据依赖性,因为同一个寄存器中的值前后没有关系,它们之所以存在就是因为 寄存器空间有限,ID名称有限。实际上,WAR、WAW只是覆盖原来的值,与原来的值无关。寄存器ID通过ROB entry ID重命名,重命名后ROB Entry ID就可以用来表示寄存器了,ROB中将存放着寄存器的值。因此可以解决WAR、WAW依赖性。4.7 顺序流水线与ROB流水线阶段:Fetch (F)Decode (D):访问RegFile/ROB,在ROB中分配entry,检查指令是否可执行,如果可以则派遣(dispatch)指令Execute (E):指令可以乱序完成执行Completion (R):将结果写回ROBRetirement (W):检查是否有异常,没有则写回体系结构寄存器/内存,有则冲刷流水线、从异常handler开始执行实现顺序派遣、乱序完成、顺序退休的流水线:

 

计算机体系的一些基本知识

 

4.8 ROB的tradeoffs优点:容易实现精确异常、可以消除WAR、WAW依赖性缺点:需要访问ROB才能获取还没被写到RegFile的值,增加了延迟与复杂性5. 补充介绍其他几种实现精确异常的方法:5.1 History Buffer(HB)基本思想:当指令完成时更新RegFile,但是在异常发生时撤销更新当指令译码时,保存一个HB entry当指令完成时,将这个寄存器原来的值写到对应的HB中当指令是最老的、且无异常/中断,这条指令对应的HB entry置为无效当指令是最老的、且有异常需要被处理时,在HB中的有效的值将写回体系结构寄存器。

 

计算机体系的一些基本知识

 

优点:RegFile包括指令执行后的最新结果。缺点:需要读取目标寄存器原来的值5.2 Future File(FF) + ROB基本思想:存两个RegFile体系结构RegFile:为了精确异常,按照程序顺序更新体系结构RegFile未来的RegFile:一旦指令完成就更新未来RegFile

 

计算机体系的一些基本知识

 

优点:无需从ROB中读取最新执行出的结果缺点:多个RegFile,需要在异常发生时,将体系结构RegFile复制到未来RegFile5.3 Checkpoint流水线问题——分支预测错误在流水线中,分支预测错误 就类似于一种 异常,分支预测错误的恢复 类似于处理异常。分支预测错误比异常更加常见,因此需要快速恢复状态,来减小预测错误带来的性能降低。分支预测错误状态恢复 的对比ROB:冲刷流水线中比分支指令年轻的指令,完成ROB中的所有指令HB:冲刷流水线中比分支指令年轻的指令,撤销分支之后的所有指令,从历史缓冲区的尾部倒回到分支,将旧的值逐一恢复到RegFile中FF:等待直到分支指令是最老的指令,将体系结构RegFile复制给未来RegFile,冲刷整个流水线更优的解决方案——Checkpoint在分支指令被解码时,记录前端寄存器状态为检查点,用比分支更早的指令结果更新检查点状态,一旦指令错误预测,恢复分支相关的检查点在分支指令解码时:复制一份future file,并将其与分支关联在指令产生一个寄存器结果时:所有更年轻的指令的future file checkpoints更新为新的值在检测出分支预测错误时:当检测出分支预测错误时,恢复future file checkpoint;冲刷比分支年轻的指令;释放比分支指令年轻的检查点优点:checkpoint一恢复,马上就能得到正确的前端寄存器状态 --> 低状态恢复延迟缺点:存储开销,管理checkpoints的复杂性

 

 


上一篇文章为了在流水线中实现多周期操作、精确判断异常位置,介绍了顺序派遣、乱序完成、顺序退休的流水线。这篇文章将介绍顺序派遣带来的问题,给出对于含有多周期操作流水线的更优执行方式——乱序执行,实现乱序派遣、乱序完成、顺序写回的流水线,逐周期地介绍经典的Tomasulo算法是如何实现乱序执行的,最后补充介绍乱序执行时load和store指令产生的内存依赖性。

1. 顺序执行的问题
上篇文章介绍了含多周期指令的流水线:

 

计算机体系的一些基本知识

 

实际上,在ID和IE阶段之间,还有一步称为:Dispatch(派遣),即:将译码后的指令派遣给相应的功能单元,顺序执行即顺序派遣后执行指令。

但是,顺序派遣的缺点在于:由于存在RAW依赖性(真依赖性),当前指令的停顿,会使后面指的派遣也停顿,尤其是如果前面的指令是延迟很长的指令,流水线停顿很久,大大降低性能。

例如:

 

计算机体系的一些基本知识

 

由于ADD指令的R3和前面的指令存在RAW依赖性,不得不等到前面的指令执行完才能继续下面的指令,但是前面的IMUL、LD都是延迟可变的指令(运行时才知道延迟),因此直到IMUL、LD的执行出结果,整个流水线是停滞的。

为了解决顺序派遣带来的长延迟问题,可以采用的方案有:

乱序执行(硬件动态调度)
编译时指令调度(软件静态调度)
预测值地试探性执行
其他如细粒度多线程
本文主要介绍乱序执行解决派遣延迟问题。



2. 乱序执行
基本思想:把依赖性指令移到独立指令后。

具体来说,如果指令B与之前执行的指令A存在依赖性,那么将指令B放入保留站(Reservation Stations, RS),让后面的独立指令先执行,直到A指令执行完成、B指令获得源数据,接下来派遣B指令。

实际上指令的派遣是数据流(dataflow)顺序的,也就是派遣源数据准备好的指令。

2.1 顺序派遣 vs. 乱序派遣
(1)顺序派遣+精确异常——16周期

 

计算机体系的一些基本知识

 

由于第二条指令依赖于前面的指令,使后续指令全部停滞

(2)顺序派遣+精确异常——12周期,更优

 

计算机体系的一些基本知识

 

虽然第二条指令是依赖性指令,它本身需要等待到它的源值都准备好时才能派遣进入E阶段;但是后续的独立指令源数据已经准备好了,可以超前于第二条指令执行。

2.2 乱序执行的实现
1)寄存器重命名:用tag给寄存器重命名

2)缓存指令:把指令插入保留站

3)追踪源值是否准备好:把tag广播,对比 源tag 和 广播的tag,如果一致,则说明源值准备好了

4)当所有的源值准备好了,派遣给对应的功能单元

2.3 双峰结构的现代流水线

 

计算机体系的一些基本知识

 

第一个峰:保留站(RS,调度窗),用于乱序派遣指令,减小停滞、提高效率

第二个峰:重排序(ROB,指令窗),用于按照程序的顺序写回,保持冯诺依曼顺序执行程序的语义



3. Tomasulo算法
Tomasulo算法是动态调度(乱序执行)的一种经典算法。

3.1 实现步骤
在重命名前
如果保留站在重命名前还有空间,就把指令和重命名的操作数(源值/tag)插入到保留站
否则停滞
在保留站中的每条指令需要:
监视通用数据总线(CDB),监视指令源值对应的tag;
一旦观测到tag,把tag对应的源值存入这条指令对应的保留站Entry;
当两个操作数都可用时,就准备派遣指令
当指令准备好时,将其派遣到相应的功能单元(FU)
当指令在FU完成执行后
仲裁CDB
把tag及其对应的值放到CDB(广播tag)
RegFile是连接在CDB上的
RegFile也有一个tag,指示寄存器的最新写入者
如果RegFile的tag与广播的tag匹配,将广播值写入寄存器(并设置有效位)
回收重命名tag
3.2 举例
下面举例详细介绍乱序执行过程:

我们将模拟下面的程序,并假设ADD需要4个周期、MUL需要6个周期、只有一个乘法器和一个加法器。

 

计算机体系的一些基本知识

 

我们需要用到寄存器别名表(Register Alias Table, RAT)、保留站表(RS):

 

计算机体系的一些基本知识

 

第1个周期:

正常完成取指。

 

计算机体系的一些基本知识

 

第2个周期:

为MUL指令分配空间RS x:

检查RS是否还有空间:还有空间,为这条指令分配RS x
访问RAT,把指令源寄存器的值存入RS x
重命名目的寄存器R3为x,它的新的值将有RS x 产生

 

计算机体系的一些基本知识

 

第3个周期:

(1)RS x 对应指令的MUL指令的两个源操作数都已经准备好,将其派遣到乘法单元,开始执行

(2)ADD指令译码,为其分配空间RS a,把R5重命名为a

 

计算机体系的一些基本知识

 

第4个周期

(1)RS a对应的ADD指令需要等待,因为其源数据有无效的

(2)第二条ADD指令译码,为其分配空间RS b,把R7重命名为b

 

计算机体系的一些基本知识

 

第5个周期

RS b 对应指令的ADD指令的两个源操作数都已经准备好,将其派遣到加法单元,开始执行

 

计算机体系的一些基本知识

 

第6个周期

RS c 对应指令的ADD指令的两个源操作数都已经准备好,将其派遣到加法单元,开始执行

 

计算机体系的一些基本知识

 

第7个周期

(1)RS y对应的MUL指令需要等待,因为其源数据有无效的

(2)R5原来被重命名为a,在这个周期被重命名为d,在RS中Source1仍为a

 

计算机体系的一些基本知识

 

第8个周期

(1)RS x对应的乘法指令已经执行完成,广播tag x和MUL指令的执行结果,更新tag x对应的RAT、RS(值与有效位)

 

计算机体系的一些基本知识

 

(2)RS b对应的加法指令已经执行完成,广播tag b和ADD指令的执行结果,更新tag b对应的RAT、RS(值与有效位)

 

计算机体系的一些基本知识

 

(3)RS y对应的MUL指令需要等待,因为其源数据有无效的

第9~20个周期

在第9、12、15、19个周期时,广播tag和执行结果

 

计算机体系的一些基本知识

 

最后补充两点:

(1)从上面的图看出并非是按顺序执行完指令的,因此需要ROB,使指令按顺序交付。

(2)这里的RS是分散的,对于每个FU有一个RS,实际上也可以做集中式的RS。



4. 思考
4.1 为什么乱序执行是有效的?
乱序执行通过并发执行独立操作,来容忍多周期操作的延迟。

回顾2.1小节,如果所有指令的执行只需要一个周期,那么顺序执行与乱序执行效果是一样的,因此乱序执行主要用来解决多周期指令使流水线停滞的问题。

4.2 如果有某条指令的执行需要1000个周期?
为了保证效率、使指令能够持续地译码,指令窗的大小也需要为1000。

指令窗指的是,机器中能存储的译码但还没交付的指令数目,它决定了乱序执行对延迟的最大容忍程度。



5. 补充介绍:乱序执行时的内存地址不确定——load和store
前面我们讨论的主要是寄存器间的依赖性,实际上内存之间也会存在依赖性,甚至处理起来更加困难,例如:
前一条store指令向内存中地址A写数据,后一条load指令需要把内存中地址A的数据读到寄存器中。

5.1 寄存器和内存的区别在哪里?

 

计算机体系的一些基本知识

 

5.2 load和store问题出在哪里?
load/store需要访问的内存地址在指令执行前是不确定的
对内存地址重命名是十分困难的(不像寄存器数目那么少)
load和store指令之间的依赖性必须在它们执行后才能判断
乱序执行时,当一条load/store地址准备好时,可能比它older/younger的store/load的地址还未知
5.3 解决思路
(保守法)使load指令停滞,直到它前面的所有store都计算出了需要访问的地址
优点:简单,无需恢复
缺点:会因为独立的load指令停滞流水线
(激进法)总是假设后来的load需要访问的地址与之前的store指令是独立的
优点:简单,不会因为独立的load指令停滞流水线
缺点:在预测错误时,需要恢复/重执行
(智能法)预测load是否依赖于那些不知道地址的store
使用一个store等待列表,检测load的地址是否和之前的store地址匹配
优点:更准确
缺点:在预测错误时,仍需要恢复/重执行
5.4 性能提升
由下图,预测store-load依赖性对性能提升很有帮助,简单的预测器(基于历史信息)就可以实现大部分潜在性能的提升。

 

计算机体系的一些基本知识

 

5.5 store和load指令间的数据bypass
尽管我们知道啦所有历史store指令访问的地址,我们仍然需要知道:
如何判断它是否和store指令有依赖性?
如果存在依赖性,如何把store的值bypass给load指令?

线代处理器主要采用LQ(load queue)和SQ(store queue)。

store-load数据旁路逻辑为:(store-to-load forwarding logic)

当store指令执行完成后,它把地址和值写到ROB或SQ中
当后续的load指令计算出地址后,它将:
用它的地址在SQ中搜索
用它的地址访问内存
从写入该地址的比它老的、最年轻的指令获取值(从ROB或内存二选一,预测器决定)

在第二篇文章中,讲到了控制依赖性,并且提到了后续指令总是和前面的指令存在控制依赖性的。实际上,在取指时,我们怎么知道一条指令是分支类型指令,就算当前取指指令是分支指令,我们如何获取下一条指令的PC呢?这篇文章将介绍分支预测的必要性、分支预测要预测的三大内容、静态与动态分支预测。

 

 

1. 控制依赖性与分支

 

 

1.1 分支类型

 

计算机体系的一些基本知识

 

对于条件分支,它有跳转、不跳转两个选择,通过第二篇文章,我们知道对于条件分支可以做分支提前解析(如beq),但是对于众多的指令如果都采用提前分支解析,硬件开销非常大,因此这篇文章将介绍分支预测来决定其是否跳转。

1.2 如何处理控制依赖性
停顿流水线,知道计算出下一条指令的取指地址(慢,总是等待)
分支预测,猜测下一条指令的取指地址
分支延迟槽,使用延迟的分支,总是先执行分支的下一条指令,再跳转(损失,往往要插入nop)
多路径执行,两个可能的方向同时执行(硬件开销大)
其他如细粒度多线程
1.3 分支问题
控制流指令(分支指令)是很频繁出现的,大概占总指令数的15%~25%
在流水线中,分支指令的下一条指令地址,需要在N个周期后才能确定
N指分支的最小解析周期数
如果流水线的宽度为W(一次取W条指令),那么一次分支预测错误会造成N×W的指令槽损失
1.4 分支预测准确率对性能的影响
假设N=20(流水线级数为20)、W=5(取指宽度为5),每5条指令有一条为分支指令,每 5 条指令以一个分支结束,我们来计算取指500条指令消耗的时间:

准确率100%
100 cycles
IPC = 500/100
准确率99%
100 + 20*1 = 120 cycles
IPC = 500/120,多取了20%的指令
准确率90%
100 + 20*10 = 300 cycles
IPC = 500/300,多取了200%的指令
准确率90%
100 + 20*40 = 900 cycles
IPC = 500/900,多取了800%的指令
由此可以看出提高分支预测的准确率,对提升性能(IPC,instruction per cycle)有很大的帮助。



2. 分支预测
基本思想:预测下一条指令的取指地址,使在下一个周期取指模块能够用上。

2.1 在取指阶段需要预测3个内容
取出的指令是否是一个分支指令
对于条件指令来说,还要预测分支跳转方向(跳or不跳)
如果分支跳转,需要预测分支跳转的目标地址
2.2 分支目标缓存(BTB)
在动态实例中,很多条件分支指令跳转的目标地址总是保持不变的,于是我们可以把前一条指令的目标地址保持下来,从这条指令的PC来访问它对应的跳转目标地址,这个存储空间就叫做分支目标缓存(Branch Target Buffer, BTB)。

如下图,根据当前指令的PC:

通过方向预测器,预测是否跳转
通过BTB查询是否有历史跳转记录、跳转的目标地址
如果预测为跳转、且在BTB中能查到历史跳转地址,则跳转到BTB所指定的跳转地址;否则不跳转,执行下一条指令,即NextPC=PC+指令长度。

 

计算机体系的一些基本知识

 

使用BTB对2.1中问题的解决:

可以预测指令是否为分支,只要能在BTB查到当前PC对应的历史跳转情况,这条指令就是分支
不能预测跳转方向(跳or不跳)
可以预测指令的跳转地址,只要能在BTB查到当前PC对应的历史跳转情况,就预测这条指令的跳转地址为BTB中的跳转记录
BTB不能预测跳转方向,那我们应该如何预测方向呢?

2.3 分支方向预测方法
编译时/静态预测:

总是预测为不跳
总是预测为跳
BTFN,向后则跳、向前不跳
基于已知资料(Profile based)
基于程序分析
运行时/动态预测:

根据上次的预测(single-bit)
基于2位计数器的预测(two-bit)
两级预测(全局+局部)
混合预测
更先进的算法(如:感知机)


3. 静态分支预测
3.1 总是不跳转
实现简单:无需BTB、跳转方向预测
准确率低:对于条件分支来说,准确率大概为30%~40%
3.2 总是跳转
无需跳转方向预测
对于条件分支来说,准确率大概为60~70%
比如循环分支,除了最后一次其他都跳转
3.3 BTFN (Backward taken, forward not taken)
如果目标地址比当前分支指令PC小,即往前跳,则跳转(如循环),
如果目标地址比当前分支指令PC大,即往后跳,则不跳。



4. 动态分支预测
基于运行时的动态执行信息,自适应地对分支预测情况做动态调整,但相应地硬件开销更大。

4.1 Last time predictor
以上一次的跳转情况作为这一次是否跳转的预测。

用状态机来描述跳转状态的更新情况:

 

计算机体系的一些基本知识

 

硬件实现:

设置一个分支历史表BHT(Branch History Table),每个分支在BHT中分配1bit的空间,1代表跳转、0代表不跳,每个分支执行后更新BHT中对应的entry,当下一次遇到同一个分支时,查询BHB,以查询结果作为预测的跳转方向。

如下图,以PC[2+BHT_ENTRIES_WIDTH : 2] 作为index,并检查tag一致,来访问BHT对应的entry,取指时查询BHT预测跳转方向,执行后以正确的跳转情况更新BHT。

 

计算机体系的一些基本知识

 

问题在于:预测器在跳与不跳之间切换地太快了,而分支可能是大多数都跳、或者大多数都不跳。

4.2 Two-Bit Counter Based Predictor(2BC)
更充分考虑过去的跳转情况,使BHT的每个entry为2 bit的,这样强预测方向就不会因为一个不一样的情况而改变。另外,也可以通过初始化BHT,为跳转情况提供先验信息。

用状态机来描述跳转状态的更新情况:

 

计算机体系的一些基本知识

 

在实现上,也只是将BHT拓展成2 bit。

实际上,对于大多数程序,基于2位计数器的预测器在准确率上能达到85%~90%,但我们需要回忆1.4中分析的准确率与性能间的关系,进一步提高准确率对性能帮助很大。

另外,4.1和4.2中的预测器都属于 One-Level Branch Predictor:

 

计算机体系的一些基本知识

 

4.3 Two-Level Predictor
前面两种预测期只是利用一条指令前几次的执行结果,而两级预测期的考虑为:

一条分支的结果和其他分支的结果有关系(全局分支相关性)
一条分支的结果和它前几次的执行结果有关(局部分支相关性)
两级的实现:

第一级:全局分支历史寄存器 GHR
存储着过去N个分支的跳转方向
第二级:每个分支的历史跳转表 PHT/BHT
存储着同一个分支前几次的跳转状态(常用2BC)

 

计算机体系的一些基本知识

 

以更完全的角度看这个预测器:

 

计算机体系的一些基本知识

 

提高全局预测期的准确性 —— Gshare predictor

基本思想:向全局预测器加入更多的上下文信息,考虑预测的是哪个分支的信息。

Gshare预测器是让GHR和分支对应PC的异或结果作为index,映射到PHT:

 

计算机体系的一些基本知识

 

以更完全的角度看这个预测器:

 

计算机体系的一些基本知识

 

提高局部预测器的准确性 —— Local History Register

基本思想:更加充分地考虑某一条指令的历史执行情况,以历史情况做映射查询。

每个分支指令PC映射到局部历史寄存器的一个entry,里面存储着这个分支的N为历史执行情况,再将这N位作为映射到PHT的index,查询预测结果:

 

计算机体系的一些基本知识

 

以更完全的角度看这个预测器:

 

计算机体系的一些基本知识

 

4.4 混合分支预测期
使用多个预测器,从中选一个最优预测结果。比如说:2BC和全局预测器的混合,根据指令只从中选一个结果作为预测结果。

优点是有更高的准确率,不同类型的分支可以用不同的预测器;
缺点是需要一个选择器来选择用那个预测器,另外访问延迟也更大了。

例如Alpha 21264中使用的预测器,自动选择局部预测或者全局预测:

 

计算机体系的一些基本知识

 

4.5 感知机分支预测器
基本思想是,用一个感知机来学习分支历史寄存器与跳转结果之间的关系。

如下图,用 x1...xn 来表示历史跳转情况,自动学习感知机权重 w0...wn ,权重 wi 表示 xi 对结果的影响程度(大小、方向),将它们加权相加后,得到一个输出 y ,如果 y>0 则跳转,否则不跳。

 

计算机体系的一些基本知识

 

实现框架如下图,根据分支PC选择对应的感知机,在取指阶段根据训练的权重判断当前是否跳转,在执行后根据分支真实的跳转情况更新感知机。

 

计算机体系的一些基本知识

 

优点是增加了学习机制、支持考虑长历史分支情况,准确性更高;
缺点是硬件实现困难,而且一个感知机只能学到线性的函数。

4.6 TAGE
实际上,为了更高的准确率,不同的分支可能需要不同的历史长度。因此,TAGE(TAgged GEometric)预测器用不同长度的GHRs去映射PHTs,并且自适应地选择一个最合适的PHT。

 

计算机体系的一些基本知识

 

TAGE预测器大大增加硬件开销,但同时也提升了预测准确性。

TAGE也是一个非常成功的想法,被用在很多现代处理器中。

 

 


存储是计算系统的重要组成之一,从应用角度,现在计算系统需要处理的数据是非常庞大的;从性能角度,提升内存的访问速度对流水线停顿等有着极大的改善;从能耗角度,内存访问消耗的能量大约是一次复杂加法的几百倍,大部分的性能能耗其实用在了移动数据上;从安全角度,研究存储系统可以提升安全性与可靠性。

这篇文章我们将介绍存储器基础,并介绍banking技术。

1. 存储组织
1.1 虚拟 -> 物理内存
从程序员的角度看内存是:

 

计算机体系的一些基本知识

 

程序员看到的是虚拟内存,可以认定虚拟内存的容量是无限的。

实际上,物理内存的大小是十分有限的,通过系统软件与硬件协同,会将虚拟内存地址映射到物理内存,系统自动管理物理内存空间,且对程序员是不可见的。

虚拟内存的好处是,程序员无需关心内存大小,一个小小的物理空间可以对外表现为很大的内存,但是系统软件架构会更难设计。

不过现在我们先忽略虚拟到物理内存的映射,先只考虑物理内存,后续的文章会更详细地介绍虚拟内存。

1.2 存储单元
常用的存储单元有:

 

计算机体系的一些基本知识

 

Register

在与非门构成的DFF中,主从锁存都需要4个与非门,一共就是8个与非,每个与非门需要4个mos管,也就是说一个DFF需要32个mos管,存储密度低
在同步电路中,通常使用DFF缓存数据,几乎不需要响应时间,速度最快,是整个存储系统层次的最顶层
通常用于做CPU内部寄存器,保存指令执行过程中临时存放的操作数和中间(或最终)的操作结果

 

计算机体系的一些基本知识

 

SRAM(Static Random Access Memory)

用两个交叉耦合的反相器存储1bit数据(4个晶体管)
用2个晶体管实现访问
组成为:6个晶体管
贵、快、存储密度较低

 

计算机体系的一些基本知识

 

DRAM(Dynamic Random Access Memory)

用电容的充电情况表示存储的数据,如果是充满电的则存储的是1,反之存的为0
通过对电容充放电,实现写数据
从DRAM读数据是破坏性的,需要刷新
组成为:1个电容+1个晶体管
慢、便宜、存储密度较高

 

计算机体系的一些基本知识

 

1.3 内存阵列
一个有N位地址线、M位数据的内存,有 2N 行、M列数据,内存容量为 2N×M :

 

计算机体系的一些基本知识

 

一个4*3的内存阵列如下图,根据地址译码,驱动相应行,读出这一行存储的数据:

 

计算机体系的一些基本知识

 

其中的存储单元常采用DRAM、SRAM:

 

计算机体系的一些基本知识

 

2. 搭建更大容量的内存
大容量的内存需要更多的存储单元,但是存储器越大,也意味着访问越慢,那么如何做到大内存、且访问不是特别慢呢?

一个思路是,把内存分成更小的阵列,并将它们互连到输入/输出总线上。

2.1 Banking
问题:一个单片的大内存阵列访问耗时很长,并且不能并行进行多个访问

目标:减小内存阵列访问的延迟,并且使能够并行访问内存

基本思想:把一个大阵列分成多个banks,使它们在同一个周期/连续周期内能够独立地访问

例如,将内存分成16个banks,这样就可以在每个周期完成一个bank的访问,也可以一次访问N个banks(要访问的数据在不同的bank中):

 

计算机体系的一些基本知识

 

广义的内存结构为:

 

计算机体系的一些基本知识

 

2.2 DRAM子系统
自上而下地看DRAM内存系统:

 

计算机体系的一些基本知识

 

Channel DIMM (Dual in-line memory module):

 

计算机体系的一些基本知识

 

DIMM Rank:

 

计算机体系的一些基本知识

 

Rank Chip:

 

计算机体系的一些基本知识

 

Chip Bank:

 

计算机体系的一些基本知识

 

Bank row&column:

 

计算机体系的一些基本知识

 

Bank内部实际上还被划分为Sub-Banks,例如下面两张图:

 

计算机体系的一些基本知识
计算机体系的一些基本知识

 

2.3 举例:Cache在内存系统的实现
Cache指高速缓存,一般把常用的数据、指令放在Cache中。

假设物理内存空间的低64bit为Cache区域,从Rank0的8个Chip下面的、第一个Bank中的、Row0 Col0 处获得8bit数据,作为cache的8bit存储空间:

 

计算机体系的一些基本知识

 

由于banking机制,这8bit的数据是可以并行访问的,这也是cache高速的原因。

以此类推,继续获得第二个8bit数据:

 

计算机体系的一些基本知识

 

这样一个64bit的cache块需要8个周期实现数据传输,一次读取8bit数据。

2.4 访问内存Bank的步骤
1) 行译码,驱动响应字线

2) 被选择的bits驱动位线

3) 放大行数据信号,读到row buffer

4) 列译码,选择行中相应的数据输出

5) 提前给位线充电,为下一次访问做好准备

 

计算机体系的一些基本知识

上篇文章介绍了内存组织,这篇文章将从层级结构角度介绍内存,根据内存局部性引出高速缓存Cache技术,然后详细介绍Cache基本原理、映射方法、基本操作、性能、多核与协同,最后介绍预取指和超前执行技术。1. 内存层次结构1.1 理想与实际的内存

 

计算机体系的一些基本知识

 

1.2 为什么需要内存层次结构?因为我们想要又快又大的内存,而通过单一层次的内存是无法实现的。解决思路为,设置多层级的内存,把更常用的数据放在访问的更快的内存层级中:

 

计算机体系的一些基本知识

 

对于内存来说,基本的tradeoff就是,快则小、大则慢。充分考虑延迟、价格、大小,可以设置以下的内存层级结构:

 

计算机体系的一些基本知识

 

最常用的数据放在RegFile中,其次常用的放在Cache中,再其次就放在主存、外存中。采用这种分层级的内存结构,把常用的数据放在小而快的存储结构中,优先从其中读取数据,可以减少访存带来的长延迟。对于Cache,也可以设置为多级的,越小则层级越高、访问越快:

 

计算机体系的一些基本知识

 

1.3 内存局部性一般程序在使用内存内容时存在着很多局部性:时间局部性:在一定时间内,程序可能会多次访问同一内存空间空间局部性:在一定时间内,程序可能会访问访问附近的内存空间Cache就是利用了时间与空间局部性,自动管理存储的内容:利用时间局部性:存储最近访问过的数据期望访问过的数据还会被访问,比如循环利用空间局部性:存储最近访问过的地址 附近的地址中的数据期望附近的数据即将被访问,比如顺序指令访问、数组遍历1.4 流水线设计中的Cache在流水线中,我们希望能够在一个周期内就能访问内存,这样流水线就不会因为依赖性而停顿。想要实现内存的快速访问,就需要在流水线中加入Cache,优先从Cache中读取数据。另外,我们希望流水线频率高,那就不能把Cache做得很大,但是我们既想要大容量Cache,又想尽可能地提升频率,解决思路为采用分级的Cache:

 

计算机体系的一些基本知识

 

对于Cache内容的管理,采用自动管理的方法,硬件实现数据在各个层级直接的移动,而程序员无需关注,提高编程人员的开发效率。2. Cache基础Cache就是存储常用数据/结果的结构,它能够减小长延迟内存访问的出现频率。如下图,cache是被分成块的,每个块中存着数据与tag,tag用来标志cache的映射地址等信息。

 

计算机体系的一些基本知识

 

2.1 Cache的逻辑组织一个关键的问题:如何把主存块的地址空间映射到cache块中?(main memory chunk cache block)

 

计算机体系的一些基本知识

 

回答:一般有三种映射方式,全关联映射:把一块主存映射到每个cache块中直接映射:把一块主存映射到一个cache块中集合关联映射:把一块主存映射到几个cache块中

 

计算机体系的一些基本知识

 

2.2 基础概念块(block):cache的存储单元在访存时:命中cache(Hit):通过tag查找,数据在cache中,用cache中的数据,而不是去访问内存未命中cache(Miss):通过tag查找,数据不在cache中,把数据块移到cache中,再从cache获取数据

 

计算机体系的一些基本知识

 

在cache设计中需要重点考虑:存放:在哪存放数据?如何找到数据?替换:需要把哪些数据移出,来给新的内容腾空间?粒度管理:块的大小怎么设置?写策略:如何处理写入?指令/数据:需要区别对待指令和数据吗?评价指标:击中率:cache hit rate = (# hits) / (# hits + # misses) = (# hits) / (# accesses)平均访问时间:Average memory access time (AMAT) = ( hit-rate * hit-latency ) + ( miss-rate * miss-latency )另外很重要的一点:减小AMAT总是有利于性能提升的吗?3. 基本的硬件Cache设计我们将从一个基本的硬件cache设计开始介绍,之后将对其改进。3.1 Cache地址主存在逻辑上被划分为了固定大小的块,而cache只能存放有限数量的内存块。每个内存块地址通过index bits映射到cache中(多对一),通过tag判断是否为所需要的数据,用byte in block来确定是block中的哪一位。例如,主存容量为 28=256 bytes(对应下图的8位地址),cache容量为 26=64 bytes(对应下图的低6位地址),每个块的大小为 23=8 bytes(对应下图的低3位地址)。内存映射是以块为单位的,那么我们要做的就是把256/8=32个内存块,映射到64/8=8个cache块中。

 

计算机体系的一些基本知识

 

访问cache时:用地址中的index对cache位置做索引,查询这个index对应entry的有效位和tag,检查有效位是否有效、比较entry中的tag是否和地址中的tag一致,如果是,则为cache hit,于是访问cache块。下面我们将根据上面举例的假设,介绍内存的几种映射方法。3.2 Direct-Mapped Caches直接映射缓存:一个内存块只能映射到cache的一个位置。

 

计算机体系的一些基本知识

 

如图,用Block的后三位地址作为index映射到Tag Store,通过检查有效位、比对tag来确定是否为需要访问的block,如果是,则可以访问Data Store中的对应entry,并通过地址的后三位byte in block来确定是block中的哪一位。

 

计算机体系的一些基本知识

 

但是这样不考虑前两位的映射,会导致相同index的内存block的访问矛盾,例如block 00001、01001、10001、11001的index一样,会争用同一个cache位置(001),这样的矛盾就可能导致cache miss。可以说直接映射是 one index one entry的映射,两个index相同的内存块是不能同时出现在cache中的,如果交替访问多个映射到同一位置的内存块,会导致0%的击中率。3.3 Set-Associativity Caches集合关联性缓存:在set中联合内存块,使一个index可能有多个可能映射位置。

 

计算机体系的一些基本知识

 

在上面的直接映射中,cache有一列八行,我们现在将其设为两列四行:

 

计算机体系的一些基本知识

 

相应地,cache地址的格式变为:

 

计算机体系的一些基本知识

 

直接映射设置了一列,那么一个index只有一个可能的映射位置,实际上就是4个blocks共用一个entry;而现在设置了两列,一个index就有两个可能的映射位置,实际上就是8个blocks共用2个entries,tag中存储着具体是哪一个block的信息,通过比对tag可以确定是否cache hit。Higher Associativity:当然,我们也可以进一步增加集合关联性(减小index位数,增加相同index映射后的可能位置数),例如设置4列2行:

 

计算机体系的一些基本知识

 

对应的cache地址格式为:

 

计算机体系的一些基本知识

 

3.4 Fully-Associativity Caches全关联缓存:内存块可以映射到缓存中的任何位置。

 

计算机体系的一些基本知识

 

当我们把集合关联性提升到最大时,index为0位,tag为5位:

 

计算机体系的一些基本知识

 

此时,不同通过index来定位可能的存储位置,一个内存块可以放在cache的任意位置!!只需要比对5位的tag即可判断是否cache hit:

 

计算机体系的一些基本知识

 

更高关联性的tradeoffs:更高的击中率更慢的cache访问时间(几种延迟、数据访问延迟)更大的硬件开销(比如需要更多的比较器)4. Cache操作实际上,在set-associatIve caches中,我们需要知道set中每个block的优先级,据此来决定set中内存块的更新。在set中有3个关键的决策:insertion:在填充cache后,在哪里插入block?优先级会怎么变化?promotion:在cache hit后,优先级会怎么变换?replacement:在cache miss后,替换掉哪个block?优先级会怎么变化?4.1 替换策略如果有无效的位置,优先插入无效的位置如果所有的位置都是有效的,解决思路:随机替换 √FIFO (first in first out)LRU (least recently used) √Not MRU (not most recently used)LFU (least frequently used)least costly to re-fetch混合替换 √LRU的实现与问题基本思想:替换掉近期访问最少的block,需要跟踪块的访问顺序。实际上,大多数线代处理器在高度集合关联时,不会去实现真正的LRU,因为LRU只是对局部性的一个估测,它并不是最好的cache管理策略。例如,对于一个4-way cache(一个set中有4个内存块),循环访问A、B、C、D、E,由于程序的实际工作set大于关联的set,击中率为0,此时,随机替换相比LRU是一种更优的选择。这也就说明,替换策略所对应的性能改变,取决于具体的程序,LRU击中率和随机替换击中率大体上是相近的,因此可以将它们混合,通过set采样来决定采用哪一种方法。4.2 写cache我们应该在什么时候将cache中修改后的数据写到下一级呢?有两种思路:write through:优点:可以在替换前,将多个写入到一个块的操作合并,节省cache层级之间的带宽、能耗小缺点:在tag store中需要多存一位,来指示这个block的状态:dirty/modifiedwrite back:优点:设计简单,cache所有层级都是最新且一致的,cache协同更加简单缺点:需要更大的带宽,没有对写

 

计算机体系的一些基本知识

 

4.3 指令与数据cache指令和数据cache应该设置为分立的、还是统一的?统一cache的优缺点:优点:动态共享cache空间缺点:指令和数据可能会互相替换;在流水线中指令和数据的访问是在不同位置的,需要考虑如何放置统一的cache,实现最快的访问一般第一级cache大多是分立的(主要考虑流水线约束),更高层级的cache大多是统一共享的。在流水线中的多级缓存设计:一级缓存设计是由周期时间决定的容量小、set associativity小(对低延迟的要求)tag store和data store一般是并行访问的二级缓存设计需要平衡击中率和访问延迟容量较大、set associativity也较大(延迟没那么重要)tag store和data store可以串行访问5. Cache性能影响cache性能的主要有:cache大小、block大小、关联性(associativity)、Replacement策略、Insertion策略、Promotion策略。5.1 影响cache性能的参数cache大小cache大小指cache的容量(不包括tag),cache大小与击中率的关系为:

 

计算机体系的一些基本知识

 

“工作集”指:在一段时间内,执行程序所引用的全部数据集。显然,cache容量越大,击中率越高,但是:cache过大,访问速度变慢,甚至影响了critical path;cache过小,没有充分利用时间局部性,需要经常性地替换数据。block大小块大小是内存被划分的单位,它和击中率的关系为:

 

计算机体系的一些基本知识

 

可以看出,块大小需要在一个适中的位置,才能有较大的击中率;块过小时,不能充分利用空间局部性,并且tag的开销会很大;块过大时,总块数很少,不能充分利用时间局部性,并且如果空间局部性不高,会浪费cache空间、总线带宽、能量。Associativity关联性指,同一index对应的可能cache位置的个数,它的大小与击中率的关系为:

 

计算机体系的一些基本知识

 

更大的关联性,则访问冲突减少,击中率也增加,但是击中延迟更大、面积开销更大;更小的关联性,则开销更小,但是击中率降低。5.2 cache未命中的类型与解决强制未命中第一次引用地址(块)总会导致未命中解决:通过cache无法解决,需要通过预取指预测接下来将会取的内存块来解决容量未命中cache太小,不能存下需要的所有内容,即使在全关联的情况下也会发生未命中解决:更好地利用cache空间,保留将被引用的块;通过软件的方法,划分工作集和计算,使得每个“计算阶段”都有适合的缓存冲突未命中不是强制和容量未命中的其他未命中情况解决:增大关联性,或者软件方法5.3 提高cache性能三个基本的目标:降低未命中率降低未命中延迟与开销降低击中延迟与开销具体的性能提升方法有很多,可以改变5.1中的参数,下面简要介绍几种软件方法与更好的替换策略。解决之软件方法:1) 调整数据访问的模式例如,x[i+1,j]和x[i,j]相邻,而x[i,j+1]离x[i,j]很远,那么性能好与不好的程序应该为:

 

计算机体系的一些基本知识

 

2) 改变数据布局例如下面的两段程序,显然右边的程序有更好的空间局部性,cache击中率更高。

 

计算机体系的一些基本知识

 

解决之更好的替换策略——MLP内存级并行(Memory Level Parallelism, MLP)指,并行生成和服务多个内存访问。

 

计算机体系的一些基本知识

 

传统cache替换策略的目标是减少miss数目,但是最少的miss数不一定代表着最好的性能!MLP-Aware Cache替换则认为,减少独立的miss是更有效的。例如,P1,P2,P3,P4的miss是并行的,S1,S2,S3的miss是独立的,采用含4个blocks的全关联cache:

 

计算机体系的一些基本知识

 

若以减少miss数为目标:

 

计算机体系的一些基本知识

 

若以减少独立miss数为目标:

 

计算机体系的一些基本知识

 

显然后者性能更优。6. Cache中的多核问题在多线程/多核系统中,cache效率十分重要,有很多问题需要考虑。6.1 私有 vs. 共享Caches私有Cache:Cache属于一个独立的核共享Cache:Cache在多个核之间是共享的

 

计算机体系的一些基本知识

 

共享cache的优点:有效容量高可以动态划分cache空间容易做cache协同缺点:由于cache并不是和核紧耦合的,访问慢由于其他核的访问,可能会导致cache访问冲突难以保证每个核都得到一定的服务水平QoS(访问不公平、饥饿)6.2 Cache Coherence在多处理器时,有一个基本的问题:如果多个处理器需要缓存同一个块,如何确保它们都看到的状态是一致的?这时就需要cache协同。例如,有P1、P2两个处理器,它们都要访问内存中的x数据:

 

计算机体系的一些基本知识

 

此后,P1又需要写内存x:

 

计算机体系的一些基本知识

 

这时,如果P2还要读x的内容:

 

计算机体系的一些基本知识

 

如何保证P2读到的x为最新的x?也就是如何让多个处理器能够看到一致的存储状态?一个非常简单的协同方案为:所有cache都“窥探”彼此的写读操作,如果一个处理器写入一个块,所有其他处理器都会使该块无效。7. 预取指(Prefetch)基本思想:在程序需要数据之前,预先取得数据。7.1 为什么要预取指?访存延迟很大,如果我们能够尽早地、准确地取出需要的数据,就可以减少延迟可以消除强制性cache miss减小未命中率、未命中延迟另外,预取指错误预测也不会影响正确性,因为在错误预测的地址处预取的数据也不会被用到。不像分支预测错误,预取指错误预测不需要恢复体系结构状态。预取指可以有硬件、编译器、程序员、系统实现。4个核心问题What?预取什么地址?(地址预测算法)When?什么时候初始化一个预取指请求?Where?在哪里放置预取的数据?(cache/独立缓冲区)在哪里放置预取器?(内存层次结构的哪一层)How?预取器怎么操作、谁来操作?(软件/硬件/基于执行的)7.2 预取器的性能准确率 = used prefetches / sent prefetches覆盖率 = prefetched misses / all misses时效性 = on-time prefetches / used prefetches带宽消耗cache污染,预取器会占用cache空间,导致额外的cache miss7.3 Runahead Execution指令窗满会造成的流水线停顿(full-window stalls):

 

计算机体系的一些基本知识

 

红色的指令因为L2 miss,需要花100个周期去访存;绿色的指令与前面的L2 miss无关,可以乱序执行,但是必须等待前面的指令交付后才能交付;蓝色的指令为指令窗之外的指令,因为指令窗大小有限,它们必须等待进入指令窗才能执行。长延迟的cache未命中是full-window stalls的主要原因:

 

计算机体系的一些基本知识

 

问题在于:乱序执行需要很大的指令窗,来容忍访问主存的长延迟,但是指令窗大会导致功耗大、延迟大、设计验证更复杂。超前执行(Runahead Execution)能够获得大指令窗口的内存及并行优势。具体实现为:当指令窗中最老的指令发生了一个长延迟cache miss,将当前架构状态保存为checkpoint,进入超前执行模式在超前执行模式中,试探性地提前执行指令,以生成预取指,将L2-miss相关的指令标记为无效当原来的未命中返回时,恢复checkpoint,冲刷流水线,回到正常的执行模式举例,如下图:Perfect Caches:在每次都能击中Small Window:如果一个load未击中,由于指令窗小,只能乱序执行窗内独立的一小部分指令,此后流水线因为访存而停滞,直到访存结束,load miss指令交付,新的指令进入指令窗,继续正常执行;后续发生load miss同样操作Runahead:如果一个load未击中,由于指令窗小,先乱序执行窗内独立的一小部分指令,此后流水线因为访存而停滞,在此期间,由于流水线停滞,硬件资源空耗,可以利用这些硬件资源超前地向后执行,如下图,在超前执行load 2 miss后,就可以提前去访问load 2对应的主存;load 1 miss访存结束、指令交付,新的指令进入指令窗,继续正常执行,在执行到load 2时,由于超前执行,已经将其数据从内存中搬移至cache中,就可以hit

 

计算机体系的一些基本知识

 

优点:能够产生精确地数据/指令预取易于实现,所需要的硬件基本上都有不浪费上下文情况,根据主线程上下文进行预取无需构建一个预执行线程缺点:额外执行指令受分支预测准确性的限制不能预取和cache miss相关的指令有效性受限于内存级并行性 (MLP)预取数目受限于访存延迟Runahead Execution被用在Sun ROCK, IBM POWER6, NVIDIA Denver等处理器中。