CPU背后的秘密—详解寄存器1

来源:百度文库 编辑:神马文学网 时间:2024/04/26 00:29:51
1.什么是寄存器
所谓寄存器(register),它是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。

2.寄存器与CPU指令
在讲CPU的寄存器之前,我们先了解一下CPU指令系统。指令系统指的是一个CPU所能够处理的全部指令的集合,Athlon XP和P4都是基于x86指令集,这是CPU的根本属性,决定CPU运行什么样的程序。
指令一般分为:算术逻辑运算指令、浮点运算指令、位操作指令及其他的一些非运算指令,其中整数、地址、指令指针和浮点数据是按照数据形式来划分的。通常我们把需要CPU进行不同处理的单个数据称为标量数据(Scala Data)。标量数据既可以是整数数据,也可以是浮点数据。其中整数标量数据的存放区一般为通用寄存器(GPR),浮点标量数据的存放区一般为浮点寄存器(FPR)。与标量数据相对的是矢量数据(Vector Data),所谓矢量数据就是指一列需要由处理器作相同处理的数据集合。比如处理器在做MP3编码的过程中,需要对内存中的音频文件里的各字节数据作相同的MP3编码操作。那么通常使用MMX或SSE这类单指令多数据流(SIMD)指令,将数个字节打包为一组矢量数据,存放在MMX或SSE寄存器中,再送往相应的功能单元进行统一操作。
其中通用寄存器是处理器中最快的存储器,用来保存参加运算的操作数和中间结果。在通用寄存器的设计上,RISC与CISC(也就是我们常说的x86架构)有着很大的不同。CISC的寄存器通常很少——只有8个通用寄存器。由于CPU在执行指令过程中,存在指令依赖性,在一定程度上使得x86 CPU不能在每个时钟周期中立即发布大量的指令。所谓“依赖性”就是指令的执行需要前个指令的运算结果。比如程序员经常使用的分支程序,请看下面这个例子:
A=C*1
B=A+2
只要变量A的值还不知道,B=A+2就不能进行运算。也就是说,只要指令1的结果没有写进寄存器,CPU调度器就不能把指令2发布到执行单元。由于程序分支会造成具有较长流水线CPU运行停滞的,目前常用的解决方法是采用分支预测。
不过,分支预测同样存在一个问题:流水线越长,指令潜伏期也越长,等待前一指令运算结果的时间也越长,同样会造成CPU运行停滞。我们知道,程序指令通常都有各类型的条件分支语句,通过验证条件决定执行路线。但CPU执行单元内是通过一项特殊的预测机制选择一条路线直接执行(这样可以避免验证语句条件而处于等待情况),然后在后面进行验证。如果预测正确则继续往下执行,如果发现以前的预测错误,那么就必须返回原地重新开始,以前的指令就会作废。
因此,管线越长,意味着出现分支预测错误的机会就越多,越多在管线内的指令会被清除掉,而且重新让管道填满指令的时间也会越长。对于普通处理器来说,如果出现分支预测错误,CPU就不得不将整条流水线清空后从错误的地方重新装满数据、重新执行。毫无疑问这将花更多的时间,整体性能就会下降。因此,针对通用寄存器少的问题,在x86架构中比较完美的解决方法就是增加寄存器的数量和采用“乱序执行”。
3.为什么寄存器不够用
在上面我们已经提到,寄存器只是用来暂时存放指令值的,如果CPU需要把两个值加起来,它需要用1个寄存器来存放运算结果,用2个寄存器来存放相加的数值。例如,在以下的方程式中:A = 2 + 4
* 在寄存器1储存“2”;
* 在寄存器2储存“4”;
* 在寄存器3储存“寄存器1 + 寄存器 2”;
因为在微处理器里面有超过3个寄存器,因此这个运算能够轻易地执行,不会造成用光寄存器的情况。
在这些运算被执行之后,所有的3个数值都能够被保留并重新使用,因此如果我们再想在结果加上2的话,处理器只需要执行:寄存器 1 + 寄存器 3 就可以了。如果微处理器仅有2个剩余的寄存器,而我们又需要再次使用2和4的值,那么这些值在覆盖结果A之前,必须储存在主内存之中 。运算执行的过程则会变成如下所示:
* 在寄存器1储存 “2”;
* 在寄存器2储存“4”;
* 在主内存的某个空间储存“寄存器1 + 寄存器2”;
我们可以看到这里使用了其它的内存访问过程,而在这期间其实还有我们没有提到的其它处理过程,比如主内存的定位也需要占据寄存器,以便让CPU 告诉装载/储存单元该往哪里发送数据 。如果我们需要使用到这些结果的话,那么CPU将不得不首先到主内存中找回这些结果,把目前满载的寄存器驱逐一些数据,把它们写入主内存,然后再把寻找到的数据储存在寄存器里。

这里大家应该能够明白吧,对内存的访问次数将会可怕地增加;你需要访问内存的时间越多,那么处理器等待工作完成的时间就越长——因而造成性能的下降。因此面对超标量CPU在并行处理大量运算,x86体系仅有的8个通用寄存器远远不能满足需要,在同一时钟周期中,如果有3个指令发布,你就需要3个输出寄存器和6个输入寄存器。我们该怎么办呢?聪明的工程师们发现了突破这个限制的方法:“寄存器重命名”。
4.寄存器重命名技术
寄存器重命名,是CPU在解码过程中对寄存器进行重命名,解码器把“其它”的寄存器名字变为“通用”的寄存器名字,本质上是通过一个表格把x86寄存器重新映射到其它寄存器,这样可以让实际使用到的寄存器远大于8个。这样做的好处除了便于前面指令发生意外或分支预测出错时取消外,还避免了由于两条指令写同一个寄存器时的等待。

下面我们以一个超标量CPU执行8个算术指令为例:假设它在每个时钟周期中能对2个指令解码,引出计算结果是在指令发布后3个时钟周期发生的:
(1)在第1个时钟周期,两个指令发布:它们互不关联,因此,它们将在3个时钟周期后(第4个时钟周期)引出;
(2)在第2个时钟周期,我们首次遇到了“指令依赖”,指令3需要指令2的结果,此时指令3不能开始发布;
(3)如果是按序执行,指令4、5、6就不能在指令3前发布。只有在第5个时钟周期时(指令2的结果已得到)才能发布指令3;
(4)在第6个时钟周期有个大问题:我们想把结果写到寄存器R1,但这将改变指令5的结果。因此,我们只有在R1空闲时(第10个时钟周期)才能发布指令6。
按照正常情况处理的话,尽管这个CPU每个时钟周期可以对2个指令解码,但它每个时钟周期的指令执行数只有0.53。如果每次程序所需的寄存器正被使用,我们可以把数据放到其它的寄存器中,在第6个时钟周期将寄存器R1重命名,指令6和指令8不再耽误CPU的工作。结果是我们能够将每个时钟周期的指令执行数提高50%。寄存器重命名技术可以使x86 CPU的寄存器可以突破8个的限制,达到32个甚至更多。寄存器重命名技术现在已经深深地扎根于超标量CPU中了。
5.乱序执行技术
除此之外,处理器工程师还引入了乱序执行技术,从一定程度上来缓解通用寄存器不足的问题。采用乱序执行技术的目的是为了使CPU内部电路满负荷运转并相应提高了CPU运行程序的速度。
这好比请A、B、C三个名人为春节联欢晚会题写横幅“春节联欢晚会”六个大字,每人各写两个字,如果这时在一张大纸上按顺序由A写好“春节”后再交给B写“联欢”,然后再由C写“晚会”,那么这样在A写的时候,B和C必须等待,而在B写的时候C仍然要等待而A已经没事了。但如果采用三个人分别用三张纸同时写的做法,那么B和C都不必等待就可以同时各写各的了,甚至C和B还可以比A先写好也没关系(就像乱序执行),但当他们都写完后就必须重新在横幅上按“春节联欢晚会”的顺序排好(自然可以由别人做,就象CPU中乱序执行后的重新排列单元)才能挂出去。
不过,虽然采用寄存器重命名技术、乱序执行技术,但仍不能从根本上解决x86处理器通用寄存器不足的问题。以寄存器重命名技术来说,这种技术的寄存器操作相对于RISC来说,要花费一个时钟周期来对寄存器进行重命名,这无形中降低了处理器性能以及流水线工作效率,也增加了程序和编译器的优化难度。针对这个问题,最新的x86-64架构中(K8处理器),AMD在x86架构基础上将通用寄存器和SIMD寄存器的数量增加了1倍:其中新增了8个通用寄存器以及8个SIMD寄存器作为原有x86处理器寄存器的扩充。
这些通用寄存器都工作在64位模式下,经过64位编码的程序就可以使用到它们。这些64位寄存器称为RAX、RBX、RCX、RDX、RDI、RSI、RBP、RSP、RIP以及EFLAGS,在32位环境下并不完全使用到这些寄存器,同时AMD也将原有的EAX等寄存器扩展至64位的RAX,这样可以增强通用寄存器对字节的操作能力。从扩充方式上看,EAX等寄存器可以看做是RAX的一个子集,系统仍然可以完整地执行以往的32位编码程序。增加通用寄存器除了可高效存储数据外,还可作为寻址时的地址指针,从而缩短指令长度和指令执行时间,加快CPU的运算处理速度,同时也给编程带来方便。
此外,为了保证K8的分支预测更有效率,K8的分支预测寄存器增加到64个。分支指令可以被设为真或假,而每个指令中的6位被分配到单独一个预测寄存器中,只有预测寄存器被设定为“真”时,那些指向预测寄存器为“真”的指令结果才会被执行。其次由于所有的分支都能并行执行,CPU所花的时间同只执行单个分支的时间是相同的,降低了预测出错的风险。第三由于CPU不再跳跃执行,它不会把程序代码分成小块。也就是说,稍前和稍后的程序代码可以打包。这样CPU能够一并将它们发布,增大并行工作量。从而使性能提高10%~15%,特别是在整数代码部分。
不过在x86-64中,寄存器的扩展部分似乎仅对于整数、地址数据有效。对浮点和向量数据则仍然保持原样。我们能从K8向64位的扩展所获得的好处,只不过是可以在同样一条指令中,处理更大数值的整数数值以及管理空间更大的内存区域而已。而在32位的情况下,由于通用寄存器只能容纳最大32位的数据,因此显然要花费更多条指令对尺寸超过32位的数据进行处理。这种改进对服务器、科学计算这样的领域具有一定的意义,但显然并不是普通家用环境急需的改进。
可以说,处理器的寄存器对处理器的性能有着巨大的影响。但是无论怎么发展,通用型CPU目前还没有脱离x86架构的限制,也许有一天,新的寄存器技术能让我们的CPU变得更加功能强大!