1.相关性介绍
在CPU中,一段程序会被编译成一连串的汇编指令,指令与指令之间可能会具有相关性(dependency)。所谓相关性,即一条指令的执行会依赖于另一条指令的结果,相关性可以分为:① 数据相关性;② 存储器数据相关性;③ 控制相关性;④ 结构相关性。本文主要介绍数据相关性。
数据相关性(Data Dependence)可以分为以下几种:
① Output dependence,又称写后写相关(Write After Write,WAW),表示两条指令都将结果写到同一个目的寄存器中。
② Anti-dependence,又称读后写相关(Write After Read,WAR),表示一条指令的目的寄存器和它前面某条指令的源寄存器是一样的。
③ True dependence,又称写后读相关(Read After Write,RAW),是真相关,表示一条指令的源寄存器来自于它前面某条指令的计算结果。
数据相关性的三种类型WAW、WAR和RAW中,只有RAW是真相关,其他两种相关性都和寄存器的名字有关,可以通过使用不同的寄存器名字来解决,如下图所示:
通过更换寄存器的名字,就可以解决WAW和WAR的相关性, 因此这两种相关性也称为假相关。现在RISC-V处理器一般都有32个通用寄存器(也叫逻辑寄存器或架构寄存器),如果在指令发生相关性时,就需要更多的物理寄存器来解决相关性问题。
常见出现相关性的场景有:(1)循环loop,如果在一个循环体中向一个寄存器R1写入了值,并且每次循环的时候都会向R1写入值,那么就会产生大量的WAW相关,当寄存器用完时,WAW相关性就不可避免了,会导致程序变得很大,I-Cache缺失率升高。(2)代码复用,譬如有一个函数strcat()字符串拼接函数被频繁调用,处理器反复向一个寄存器R1写字符串,那么和第一种情况一样,也会发生WAW相关,导致同样的问题。
解决办法就是用硬件来管理寄存器的重命名(register renaming)。处理器内部实际存在的寄存器称为物理寄存器(Physical Register),指令集中定义的寄存器称为逻辑寄存器(Logical Register)或者架构寄存器(Architecture Register)。物理寄存器的个数要多于逻辑寄存器的个数,这样才可以使寄存器rename起作用,譬如MIPS处理器定义了R0~R31这32个逻辑寄存器,处理器中真正存在的寄存器也就是物理寄存器有128个。处理器在进行寄存器重命名的时候,会动态地将逻辑寄存器映射到物理寄存器,这样可以解决WAW和WAR的相关性。
如下图所示,有R0~R6共7个逻辑寄存器,有p0~p9共10个物理寄存器,编译器编译代码的时候会直接使用指令集中定义的逻辑寄存器,如图中左边的原始代码,箭头表示了指令之间存在的WAW/WAR相关性,通过寄存重命名的方法将它们消除掉,可以使原始程序获得并行性。
图中的重命名映射表保存着映射关系,对于程序中的指令来说,它的源寄存器通过读取重命名映射表,就可以得到它们对应的物理寄存器了。经过重命名之后的程序不存在WAW和WAR的相关性了,因此在超标量处理器中,这个程序可以获得最大的并行性,从而尽快执行完毕。
再举个简单点的例子,如下三行代码发生了WAW相关,
① 在R2=R1+R0执行时,重命名映射表将R2映射到了物理寄存器p2上,将R1+R0的结果写到p2中。
② 在执行第二行指令,对R2逻辑寄存器进行WAW的时候,更新重命名映射表,将R2映射到物理寄存器p4上,然后将R1×R0的结果写到p4中。
③ 执行第三行指令,需要读取R2逻辑寄存器做加法,此时就会去查询重命名映射表,发现R2映射到了p4物理寄存器,所以其实就会去读取p4寄存器的值(R1×R0),然后再做加法。
那么可能会产生一个疑问,p2物理寄存器怎么办?p2物理寄存器在WAW相关之后,映射的逻辑寄存器内的值被覆盖了,所以此时p2物理寄存器内存储的内容已经没用了,因此p2物理寄存器可以被释放,等待下一次有相关性冲突时,再被使用。
2.寄存器重命名设计方式
针对超标量处理器设计中,实现寄存器重命名(Register Renaming)这个功能有很多种设计方式,可以分为:① 将逻辑寄存器(Architecture Register File,ARF)扩展来实现寄存器重命名;② 使用统一的物理寄存器(Physical Register File,PRF)来实现寄存器的重命名;③ 使用ROB来实现寄存器重命名。这三种方法本质都是讲指令集中定义的逻辑寄存器映射到物理寄存器上,从而可以增加寄存器的个数。
所以接下来,我们需要搞清楚几个问题:
(1)什么时候占用一个物理寄存器?
(2)什么时候释放一个物理寄存器?
(3)发生分支预测失败时,如何进行处理?
(4)发生异常(exception)时,如何进行处理?
2.1 使用ROB进行寄存器重命名
这种方法将重排序缓存(ROB)作为了物理寄存器,在超标量处理器中,每条指令都会将自身的信息按照程序中原始的顺序存储到ROB中,当一条指令将结果计算出来之后,会将其写到ROB中,但是由于分支预测失败(mis-prediction)和异常(exception)等原因,计算的结果可能是错误的,这种状态称为推测状态(speculative)。
在一条指令真正退休之前,它会一直待在ROB中,只有当指令变成最旧的指令,并且被验证为正确的时候,才会离开ROB,并将它的结果写到ARF中。这种方式相当于将物理寄存器(PRF)和ROB集成到了一起。
当一条指令被写到ROB中的一个表项(entry)时,这个表项在ROB中的编号也就成了该条指令目的寄存器对应的物理寄存器,这就将一个逻辑寄存器和ROB中表项的编号建立了映射关系,完成了对目的寄存器的rename过程,只要ROB中有空余的表项,那么rename就可以一直进行。
ROB中存储着所有没有离开流水线的指令结果,而逻辑寄存器(ARF)中存储着所有“最新”离开流水线的指令结果。如果一个逻辑寄存器的值还在ROB中,那么重命名映射表会给出这个逻辑寄存器在ROB中所占用表项的编号。即当一条指令计算完毕,它的结果会被存储ROB的对应空间,当这条指令要离开流水线也就是退休时,它的结果会从ROB中更新到ARF中,这个位置的变动信息也要同步的更新到重命名映射表中。这样,一个寄存器在它的生命周期内,会有两个存放它信息的位置。
虽然基于ROB的寄存器重命名方式便于管理,但存在一些缺点:① 很多指令没有目的寄存器,因此也就不需要对目的寄存器进行重命名,但每条指令都占用ROB中的一个表项。哪怕该条没有目的寄存器,它在ROB中对应的表项也无法将物理寄存器省掉,资源利用率不高。② 对于一条指令来说,它既可以从ROB中读取源操作数,也可以从ARF中读取源操作数。最坏情况下,4发射的超标量处理器,每条指令有2个源寄存器,那么ARF和ROB都要支持2×4=8个读端口,对芯片的面积和延时有较大负面影响。
2.2 使用ARF扩展进行寄存器重命名
在指令的执行过程中,很多指令并没有目的寄存器,譬如store指令、分支指令和比较指令等,在一般的程序当中,这些指令大概占比25%左右。使用基于ROB的重命名方法会造成一定的资源浪费。因此,可以使用一个独立的存储部件,只有那些存在目的寄存器的指令才会占据这个存储部件当中的存储空间,这个部件称为PRF(Physical Register File),它可以看做ARF的扩展。
每次指令被解码(decode)之后,那些存在目的寄存器的指令会占据PRF中的一个表项,这个表项的编号对应物理寄存器的编号,每当指令退休的时候,由于指令的结果会被写到ARF中,所以在PRF中,这条指令所占据的空间也就没用了,可以进行释放,因此,PRF本质上也可以使用FIFO实现。如果PRF中已经没有空间了,那么流水线中寄存器重命名阶段之前的所有流水线都需要暂停(stall前级流水),知道流水线中有指令离开而释放PRF的空间时,流水行才能继续执行。
这种寄存器重命名的方式也需要一个重命名映射表,其中记录了每个逻辑寄存器的值是位于PRF中还是ARF中,一条指令在PRF中对应点的地址需要存储到这个表格中。
2.3 使用同一PRF进行寄存器重命名
将2.2节中的ARF和PRF合并在一起,合并之后的部件称为统一的PRF,其中存储了所有推测的(speculative)和正确的寄存器(retire)值。在这个PRF中,没有和指令产生映射关系的寄存器都是处于空闲(free)状态,当一条指令被重命名,并且它存在目的寄存器的时候,它就会占据PRF当中的一个寄存器,这个寄存器就处于占用的状态,处于这个状态的寄存器会经历:值没有被计算出来、值被计算出来但没有退休和退休三个过程。这种寄存器重命名方法也需要一个重命名映射表,用来存储每个逻辑寄存器和物理寄存器的对应关系。
同样的,如果此时没有空闲的物理寄存器,代表此时物理寄存器已经全部被占用,则流水线重命名阶段及其之前的流水线就要被暂停,直到有指令退休而释放掉物理寄存器为止。
3.寄存器释放
当一个物理寄存器不再被后面的指令使用时,这个物理寄存器就可以变为空闲状态了,只要最后一条使用这个物理寄存器的指令由于退休而顺利地离开了流水线,这个物理寄存器就可以变为空闲状态。
具体的,当一条指令和后面的某条指令都写到同一个目的寄存器时,则后面的指令退休的时候,前面指令对应的物理寄存器就已经没有用处了。
如上图所示,ADD和MUL两条指令都使用了r1作为目的寄存器。如果有一条指令使用了ADD的目的寄存器p1,作为源寄存器,那么这条指令必定位于ADD和MUL指令之间,当MUL指令退休时,则可以保证以后再也没有指令使用物理寄存器p1作为源寄存器了,因此物理寄存器p1可以变为空闲状态。由于MUL指令,内容p1已经被p6覆盖了,指令MUL后面的指令如果还用到了逻辑寄存器r1作为源寄存器,肯定会使用物理寄存器p6。因此当指令MUL退休的时候,就可以释放物理寄存器p1。
所以为了实现这个功能,在ROB中除了记录逻辑寄存器当前对应的物理寄存器之外,还需要存储它之前对应的物理寄存器,以便于在指令退休的事后,将它对应的旧映射关系的物理寄存器进行释放。