★ 继续学习体系结构的知识。
1. 什么是分支预测
程序中有一种叫做分支指令的指令,如果在取指令阶段就可以预知本周期所取指令中是否存在分支指令,并且知道其方向(跳转/不跳转)以及目标地址,就可以从在下个周期从分支指令的目标地址开始取指,让流水线正确进行,提高处理器的执行效率。
(1)静态分支预测:预测分支指令总是不执行的,处理器总是顺序地取指令。
(2)动态分支预测:并不简单预测分支指令一直跳转或不跳转,而是根据分支指令在过去一段时间的执行情况来决定预测结果。
分支预测的最好时机是在当前周期得到取指令地址的时候,在取指令的同时进行分支预测,这样在下个周期就可以根据预测的结果继续取指令。
2. RISC分支指令类型
(1)根据分支指令是否执行的条件判断,分为无条件跳转分支指令与条件跳转分支指令。
(2)根据分支指令跳转地址的计算方式,分为直接跳转分支指令与间接跳转分支指令。
无条件/条件:无条件跳转分支指令一定会发生跳转,条件跳转分支指令需要根据条件判断是否跳转。
直接/间接:直接指跳转的目标地址可以根据指令编码的立即数和当前地址直接计算,间接指跳转的目标地址需要根据从寄存器索引的操作数计算。
组合来看,可以细分为四种跳转指令:
(1)无条件直接跳转分支指令,如RISC-V中的jal(jump and link)指令;
(2)无条件间接跳转分支指令,如RISC-V中的jalr(jump and link-register)指令;
(3)条件直接跳转分支指令,如RISC-V中的beq等指令(RISC-V中有6条此类指令);
(4)条件间接跳转分支指令,RISC-V中没有此种指令。
3. 分支指令的方向预测
3.1 基于两位饱和计数器的分支预测
(1)核心理念: 该分支预测方法基于这样一个观察:在程序执行过程中,很多分支指令具有一定的局部性,即它们在连续多次执行时,往往会倾向于选择同一分支方向(跳转或不跳转)。
- 稳定性假设:如果一条分支指令连续两次执行的方向都相同,那么它在下一次执行时很可能继续保持这一方向。
- 过滤假设:对于那些仅偶尔改变分支方向的指令,预测器不会因单次方向变化而立即改变预测结果,而是会“过滤”掉这种偶然变化,以减少预测错误。
(2)算法过程:根据一条分支指令的前两次执行结果预测本次方向,用四个状态机表示。
- Strongly taken(饱和状态,预测跳转,编码11):表示分支指令在过去连续多次(通常为两次)都跳转,预测器对下一次执行强烈倾向于跳转。
- Weakly taken(不饱和状态,预测跳转,编码10):表示分支指令最近一次跳转,但之前有一次未跳转,预测器较弱地倾向于跳转。
- Weakly not taken(不饱和状态,预测不跳转,编码01):表示分支指令最近一次未跳转,但之前有一次跳转,预测器较弱地倾向于不跳转。
- Strongly not taken(饱和状态,预测不跳转,编码00):表示分支指令在过去连续多次(通常为两次)都不跳转,预测器对下一次执行强烈倾向于不跳转。
状态机处于饱和状态时,只有两次预测失败才会改变结果。适用于分支指令方向总是朝一个方向,状态机处于饱和状态,则正确率较高。
状态机的工作机制:
-
状态转移规则:每当分支指令执行后,根据实际执行结果(跳转或不跳转),状态机会按照预设的规则更新到相应的状态。例如,若当前状态为“Strongly not taken”,而分支指令实际跳转,状态机将转移到“Weakly taken”。
-
饱和状态的特性:当状态机处于“Strongly taken”或“Strongly not taken”(饱和状态)时,预测器对分支方向的预测具有较高的置信度。在这种状态下,即使分支指令的实际执行结果与预测相反(即预测错误),也需连续两次这样的错误才能使状态机退出饱和状态,转向另一个预测方向。这种设计旨在保持对稳定分支行为的准确预测,同时对偶发的分支方向变化具有一定的容错能力。
PC值与计数器映射
分支预测以PC值为基础。由于硬件资源限制,为每个PC值(每条指令)单独分配一个计数器是不现实的。为此,采用PC的部分值寻址计数器的方法。这种方法的核心思想是:虽然可能会导致多个PC值映射到同一个计数器(即产生别名),但由于不是所有的指令都是分支指令,因此即使存在冲突,只要在同一时刻只有一个分支指令对应这个计数器,就不会影响预测结果。
PHT(Pattern History Table)
PHT是一个专门用于存储分支预测信息的数据结构,其主要包含两部分:
-
PC值的部分信息:只选取PC值的一部分(如低位比特)作为索引来寻址PHT。这样可以显著减少所需的存储空间,使得有限的硬件资源能够覆盖更广泛的代码范围。
-
两位饱和计数器的值:每个PHT表项对应一个两位饱和计数器,存储着与该PC部分值关联的分支指令的历史行为信息。根据这些信息,预测器可以在取指阶段对分支指令进行预测。
别名(Aliasing)
别名是指由于使用PC的部分值寻址PHT,导致不同PC值映射到同一个PHT表项的现象。别名可能导致预测错误,因为不同分支指令的历史行为可能被混淆。为缓解这个问题,可以对PC值进行哈希处理,通过增加地址映射的随机性来降低别名概率。
PHT更新
PHT需要在不同阶段根据实际情况进行更新,以维护其预测准确性。以下是三种常见的更新时机:
-
取指阶段:当进行分支预测时,根据当前PHT表项中的两位饱和计数器状态预测分支方向。若预测成功(即预测结果与实际执行方向一致),维持当前计数器状态;若预测失败,按照两位饱和计数器的状态转移规则更新计数器状态。
-
执行阶段:当分支指令的实际执行方向(跳转或不跳转)被流水线中的执行单元计算出来后,立即将此信息反馈给PHT,更新对应表项的计数器状态。此时更新相对较早,有助于快速纠正预测错误。
-
提交阶段:分支指令完全执行完毕并准备离开流水线时,再次更新PHT。这是最可靠的更新时机,因为此时分支指令的执行已经得到确认,不存在流水线冒险等因素导致的不确定性。然而,由于提交阶段距离分支指令的取指阶段较远,延迟较大,可能导致后续相关分支指令的预测准确性下降。
3.2 基于局部历史的分支预测
基于两位饱和计数器的分支预测的不足:对于很有规律的分支指令, 两位饱和计数器的准确率可能很糟糕, 例如下图的情况, 初始在weakly not taken , 则对于TNTNTN…这样的序列, 预测将一次都不对:
理论上任何有规律的事都能被预测,可以使用一个寄存器记录一条分支指令过去的历史状态,当历史状态有规律时,对其进行预测,这样的寄存器叫分支历史寄存器(Branch History Register,BHR)。一个位宽为n位的BHR可以记录过去n次的结果。这种方法也称自适应的两级分支预测。
基于BHR和PHT的分支预测机制,分支指令的前两次执行结果被存储在BHR中,通过两位二进制编码(00、01、10、11)来表示分支行为。PHT包含四个表项,与BHR的四种取值一一对应,每个表项包含一个饱和计数器,用于记录该模式下分支指令的历史跳转趋势。
BHR位宽为n,记录指令过去n次的结果,PHT的地址宽度与之对应,用BHR寻址PHT;PHT中每个表项其实并非饱和计数器,而只是存储了一个计数器的值,每次更新表项需要先读出计数器值, 然后放到统一的饱和计数状态机里获得下一个状态的值再写回。
BHR寄存器 (Branch History Register)是一种专门用于记录分支指令历史行为的硬件寄存器。
具有n位宽度,能保存最近n次分支指令执行结果的信息。每一位对应一次分支指令执行,若该指令发生了跳转,则该位置为1,否则置为0。BHR寄存器通过累积这些二进制位的序列,反映出分支指令的历史模式。
饱和计数器:为了解析BHR寄存器中的模式,引入了一个两位的饱和计数器。饱和计数器是一种特殊类型的计数器,其数值范围有限(通常为0、1、2),当达到最大值后不再增加,从而避免无限增长。这里使用两位饱和计数器来捕捉BHR中连续出现相同结果(跳转或非跳转)的次数。
Pattern History Table (PHT):PHT是一个二维表格,大小为2^n×2 bit。
每一行对应BHR可能的n位取值(共有2^n种),每一列存储与该BHR取值相对应的两位饱和计数器的值。
尽管图示中每个表项看似包含一个独立的计数器,实际上仅存储计数值,而计数器的更新是全局统一进行的。这意味着所有PHT表项共享一个硬件逻辑来进行计数器的增减操作.
分支指令处理流程:
当一条分支指令执行完毕并获得结果(跳转或非跳转)时,首先根据当前BHR寄存器的值在PHT中找到对应的表项。从该表项读取出当前的饱和计数器值。根据实际发生的分支结果,按照饱和计数器的规则(如:若实际跳转,计数器+1;若未跳转,计数器-1,但不得低于0)更新计数值。
将更新后的计数值重新写回PHT相应表项。
预测原理:PHT中的两位饱和计数器旨在识别BHR中的规律。如果某条分支指令在过去表现出高度规律性(即BHR中某段模式重复出现),相应的饱和计数器能够有效地捕捉到这种规律。当处理器遇到新的分支指令时,会利用当前的BHR值查询PHT,基于对应表项的饱和计数器值进行预测:如果计数值较高(如2),预测该分支将继续遵循过去的规律(如继续跳转);反之,如果计数值较低(如0或1),则预测分支行为可能发生改变。
每条分支指令的独立性:理论上,对于处理器中的每一条分支指令,都应配备一个专属的BHR寄存器和对应的PHT。这样,每个分支都能依据自身的执行历史进行个性化的预测,提高预测准确率。
例子:
分支指令执行序列“taken→not taken→taken→not taken→taken…”,对应的BHR值序列为“10_01_10…”,并遵循从右向左移入新结果的规则。
BHR值为10:此时分支指令总是接下来发生跳转(taken)。当BHR为10时,PHT中被访问的是entry2。由于每次遇到10时分支都确实发生跳转,entry2对应的饱和计数器会逐渐增加至Strongly taken状态(计数值为11),表明对后续分支强烈预测为跳转。因此,当分支指令执行时,若BHR为10,根据PHT中entry2的计数值11,预测器会准确预测分支将发生跳转。
BHR值为01:此时分支指令总是接下来不发生跳转(not taken)。当BHR为01时,PHT中被访问的是entry1。由于每次遇到01时分支都不发生跳转,entry1对应的饱和计数器会逐渐减小至Strongly not taken状态(计数值为00),表明对后续分支强烈预测为不跳转。因此,当分支指令执行时,若BHR为01,根据PHT中entry1的计数值00,预测器会准确预测分支将不发生跳转。
例子:
交替出现“TTNNTTNNTTNN…”序列。这样的序列可以用“1100_1100_1100…”的二进制形式表示,其中“1”代表分支被“taken”(执行分支目标),而“0”代表分支被“not taken”(不执行分支目标)。这种模式具有明显的循环周期为2的特性,即每两位后跟随的数值是固定的,如“11”后总是“0”,“10”后总是“0”,“00”后总是“1”,“01”后总是“1”。
在这种情况下,使用两位的分支历史寄存器(BHR)进行预测是非常有效的,因为每一对BHR值(如“11”,“10”,“00”,“01”)对应着唯一的下一跳分支方向。一旦分支预测硬件(如PHT,Pattern History Table)识别到这一规律,相关的PHT条目(如entry0、entry1、entry2、entry3)中的计数器就会进入饱和状态。
entry0和entry1中的计数器由于始终接收到“11”输入,会稳定在Strongly taken状态,表明预测下一跳分支会被taken。
entry2和entry3中的计数器由于始终接收到“00”或“01”、“10”输入,会稳定在Strongly not taken状态表明预测下一跳分支会被not taken。
训练时间(Training Time)是指从分支指令开始执行到PHT成功捕捉到其规律、计数器达到饱和状态所经历的时间。训练时间的长短与以下几个因素有关:
BHR位宽:较大的位宽意味着PHT需要跟踪更长的分支历史,这可能导致训练时间延长。因为需要观察更多的分支执行才能确定更复杂的模式。然而,更大的位宽也意味着能够捕获更丰富、更精细的分支行为特征,从而在训练完成后提高预测准确性。
分支序列复杂性:对于简单如上述“每两次改变方向”的序列,训练时间相对较短。但对于更复杂的、缺乏明显规律的分支序列,训练时间可能会显著增长。
饱和计数器数量:更宽的BHR确实需要PHT中存储更多的饱和计数器,增加了存储器占用。但这也可以视为提高了硬件对不同分支模式的区分能力,有助于适应更广泛的分支行为。
设计时需要在以下方面进行权衡:
预测准确性与训练时间:更宽的BHR虽然可能导致较长的训练时间,但一旦训练完成,因其能够记录更丰富的历史信息,预测准确性通常更高。反之,窄BHR可能训练时间较短,但可能无法充分捕捉复杂分支行为,长期来看预测准确性可能较低。
硬件成本与性能收益:增加BHR位宽会增大存储器占用,可能增加芯片面积、功耗等硬件成本。但如果由此带来的预测准确性提升足以显著减少分支预测错误导致的流水线阻塞或无效指令执行,那么这种成本可能是值得的。反之,如果实际应用中的分支行为相对简单,窄BHR可能已经足够应对,此时选择较宽BHR反而可能导致不必要的硬件开销。
分支历史寄存器(BHR)的宽度对于准确预测特定分支指令序列的跳转行为至关重要。若BHR宽度小于序列的循环周期,可能会导致预测效果不佳甚至无法准确预测。
BHR宽度与预测准确性
“000100010001…”序列中,循环周期为3,而使用宽度为2位的BHR进行预测。由于BHR只能记录最近两次的分支结果,无法捕捉到完整的循环周期信息(即第三个分支结果),导致其无法准确预测后续分支行为。在这种情况下,entry0中的计数器无法稳定在一个饱和状态,因为其接收到的分支结果(00或01)并不能唯一确定下一个分支是否跳转。
提升预测准确性的策略
针对上述问题,建议使用宽度不小于序列循环周期的BHR进行预测。以“000100010001…”序列为例,至少需要使用3位宽的BHR才能完整记录一个循环周期内的分支行为,从而实现对该序列的完美预测。
如果一个序列中,连续相同的数最多有p位,那么这个序列的循环周期为p。只要分支历史寄存器BHR的宽度n不小于序列的循环周期p,就可以对其进行完美预测。
从理论层面来看,为处理器中的每一条分支指令分配一个专用的BHR(Branch History Register)和一个PHT(Pattern History Table)是最理想的分支预测策略,因为这样可以针对每条分支指令的独特执行历史进行精准预测,最大化预测准确性。
理想情况存在的问题:
(1)存储空间需求过大: 每个BHR需要n位来记录分支指令的历史行为,而每个PHT大小为2^n × 2 bit。随着n值增大,所需的存储资源呈指数级增长。
考虑到现代处理器中分支指令数量众多,如果为每一个分支指令都配备独立的BHR和PHT,那么总体所需的存储空间将会极其庞大。实际上,处理器内部的物理空间和功耗预算都是有限的,无法容纳如此大量的硬件资源。
(2)实现成本与复杂度: 除了存储空间的需求外,为每个分支指令单独设计、制造和管理大量的BHR和PHT也会显著增加处理器的设计与制造成本,以及电路布局的复杂度。这包括额外的布线、控制逻辑、访问接口等,都会影响芯片面积、功耗、散热以及整体系统的可靠性。
(3)效率与性价比权衡: 工程师必须在预测准确性与资源效率之间做出权衡。虽然为每个分支指令提供独立的预测资源能提升预测精度,但付出的成本可能过高,导致整体系统性价比降低。因此,实际处理器设计通常采用更经济高效的分支预测策略,如共享预测资源、使用更紧凑的预测模型或牺牲部分预测准确性以换取更低的硬件开销。
实际处理器设计中如何应对为每个分支指令分配独立BHR和PHT所带来的存储空间过大问题?
组合BHR形成BHRT/BHT: 为了克服为每个分支指令分配独立BHR导致的存储空间过大问题,设计者选择将所有分支指令的BHR合并成一个大的数据结构,即分支历史寄存器表(Branch History Register Table, BHRT or BHT)。这样一来,所有分支指令共享一个统一的BHRT,而非各自拥有独立的BHR。这样做显著减少了存储空间需求,因为一个统一的BHRT比多个独立BHR所需的总空间要小得多。
使用PC值寻址BHT和PHT: 为了在BHRT中区分不同的分支指令,使用程序计数器(PC)的部分值(例如k位)作为索引来访问BHRT。同样地,PHT的访问也基于这部分PC值。这意味着不同的分支指令会根据它们的PC值共享同一BHRT和PHT中的不同表项。这样做的好处是进一步节省了存储资源,因为一个固定的BHRT和PHT可以服务于大量分支指令。
BHR的宽度为n位,表示它可以记录分支指令最近n次的跳转结果。整个BHT所占的存储空间为2^k × n bit,因为有2^k个不同的PC地址(由k位PC部分决定),每个地址对应的BHR为n bit。
PHTs结构:PHTs代表一组PHT的集合,每个PHT用于存储特定分支模式的跳转倾向。由于单个PHT可能占用较大存储空间,实际系统中通常不会包含大量PHT。PHTs的寻址使用PC的另一部分(t位,且t < k),这意味着有2^t个不同的PHT。
从PHT获取预测值:当处理器确定了当前分支指令对应的PHT后,使用该分支指令的BHR(从BHT获取)来进一步寻址选定的PHT。每个PHT表项关联一个饱和计数器,计数器的值反映了该分支模式下跳转行为的统计规律。通过BHR寻址PHT,即可找到对应的饱和计数器,其值提供了分支指令的预测方向(跳转或非跳转)。
空间冲突:尽管使用PC值寻址BHT和PHT有效降低了存储空间需求,但这也可能导致“空间冲突”。也就是说,不同的分支指令可能因具有相同的PC值前缀而共享同一个BHRT和PHT表项,进而混淆了各自的分支历史信息。这种情况可能会降低分支预测的准确性。
PHT优化:
为解决空间冲突问题并进一步压缩PHT的大小,提出了一个极端情况下的优化措施:
只使用一个单一的PHT(而不是每个BHR对应一个PHT)。在这种情况下,所有分支指令无论PC值如何,都共用同一个PHT。虽然这种做法可能导致预测准确性的进一步下降,但它极大地简化了硬件设计,极大地减少了存储空间需求。
总结:
(1)将所有BHR组合成一个共享的BHRT/BHT。
(2)使用PC值的部分位数寻址BHRT和PHT,使得不同分支指令共享这些资源。
(3)采用极端情况,仅保留一个单一的PHT供所有分支指令共用,以最大程度地减少存储空间占用。这一简化策略可能会牺牲一定的预测准确性,但在实际设计中实现了对存储资源的有效利用和硬件复杂度的降低。
为了最大程度地节省存储空间,使用简化版的分支预测器设计,其中仅包含一个PHT(Pattern History Table)。这种设计下,所有分支指令的BHR(Branch History Register)都映射到同一个PHT,以进行分支预测。由于PHT只有一个,不再需要PC(Program Counter)来寻址PHT,而是直接由BHR内容决定PHT中的计数器访问。
然而,这种设计会导致以下两种冲突情况:
情况一:当不同分支指令的PC值在k位(用于区分PHT条目的部分)相同时,它们会映射到相同的BHR寄存器,进而访问PHT中的同一计数器。这样,这些分支指令的执行结果会相互干扰,降低预测准确度。
情况二:即使不同分支指令对应不同的BHR,但若它们的BHR内容相同,也会导致共用PHT中的同一计数器,产生预测干扰。
为解决上述冲突,提出了如下改进方案:
(1)哈希处理PC值
对PC值进行哈希处理,得到一个固定长度的值,用作BHT(Branch History Table)的索引。这样可以确保不同的PC值(即使k位相同)能映射到不同的BHT条目,从而避免情况一中的冲突。
(2)拼接PC值与BHR值
从BHT中获取到分支指令对应的BHR值后,将该BHR值与PC值的一部分拼接成一个新的值,用于寻址PHT。这样即使BHR内容相同的不同分支指令,由于拼接了不同的PC值片段,也能访问PHT中不同的计数器,从而避免情况二中的冲突。
尽管上述位拼接方法简单易行,但它可能引入新的冲突,且效果可能不理想。
因此,可以考虑其他更有效的方法来处理PC值和BHR值,如异或(XOR)法。异或法通过逻辑异或操作将PC值和BHR值进行组合,相比简单的位拼接,异或法可能更有效地分散冲突,提高预测准确性。
理论上,只要分支指令的执行具有某种规律,这种方法就能够进行有效的预测。然而,实际应用中存在一些局限性:
循环周期与BHR宽度:对于循环周期较长的分支指令,需要使用宽度较大的BHR来记录其足够长的历史。但大宽度的BHR会导致:
(过长的训练时间(training time):更宽的BHR需要更长时间来观察足够的分支执行,以便识别规律并使PHT(Pattern History Table)中的计数器达到饱和状态,从而影响预测的初期准确度。
(2)增加存储器资源占用:更宽的BHR会使得PHT中需要存储更多的饱和计数器,从而占用更多存储器资源。这不仅增加了硬件成本,还可能影响芯片面积、功耗等方面。
受限的实际应用:由于现实处理器中BHR位宽有限,对于某些具有高度规律但循环周期极长的分支模式(如“999次跳转加1次不跳转”),基于局部历史的预测方法可能无法达到最理想的预测准确度。
与饱和计数器比较:尽管基于BHR的局部历史预测方法在面对复杂分支模式时可能存在局限性,但相比于仅基于两位饱和计数器(如Strongly Taken/Not Taken)的简单预测方法,它已经是一个显著的进步。两者都属于局部历史预测,因为它们都不考虑分支指令前序指令对其预测结果的影响。
忽视相关性:局部历史预测方法未能考虑一条分支指令的结果可能与其前面执行的其他分支指令紧密相关。在某些情况下,分支指令的执行并非仅仅取决于自身过去的行为,而是受到前序分支指令结果的直接影响。这种相关性在局部历史预测方法中并未得到体现和利用。