AMD Barcelona处理器将在9月10日发布,这款处理器成为构建在AMD新的K10微架构基础上的第一个解决方案,AMD对它寄予了厚望。我们还是一睹为快,先来了解一下新解决方案中所采用的微架构的诸多亮点吧。
介绍
AMD承诺在今年的八月末九月初推出采用K10微架构的新型四核处理器。而采用新的微架构的第一批处理器将是服务器Opteron芯片,它构建在代码名为Barcelona的基础之上。令人遗憾的是,AMD还无法将当前高频版本的芯片投入大规模生产。目前,继续提升四核处理器工作频率遇到的障碍是高速度引起的高能耗,而这要比TDP(技术成套数据)平台实际允许的功耗高得多。随着每次版本的更新以及更先进生产工艺的出现,工作频率将不断提高,而功耗不断降低。到目前为止,AMD必须立即展开处理器的销售活动来改善其财政状况,因此,开始销售的第一种芯片将是四核处理器,工作频率为2.0GHz。
AMD表示在2007年第4季,将提高Opteron的工作频率至2.4~2.5GHz,同时发布K10架构的桌面处理器,具体如下:
• Phenom FX (代码名Agena FX) C 4 核,2MB L3高速缓存,时钟频率初始速度2.2~2.4GHz,AM2+插座,F+;
• Phenom X4 (代码名Agena) C 4 核,2MB L3高速缓存,时钟频率初始速度2.2~2.4GHz,AM2+插座;
2008年初,AMD将对新处理器进行微小改动后推出新品,如:
• Phenom X2 (代码名为Kuma) C 2 核,2MB L3高速缓存,时钟频率初始速度2.2~2.6GHz,AM2+插座;
• Phenom X2 (代码名为Rana) C 2 核,未配置L3高速缓存,时钟频率初始速度2.2GHz,AM2+插座;
• Sempron (代码名为Spica) C 单核,时钟频率初始速度2.2~2.4GHz,AM2+插座。
当然,这些都还在酝酿之中,只是AMD将来的计划。我们还是先了解一下AMD新型微架构到底有哪些创新点。通过这篇文章,我会将新架构的所有细节尽量全部展现给大家,让大家了解它们到底有什么实际意义。
获取指令
处理器对代码的处理将从取指令开始,即从L1I指令的高速缓存和解码开始。由于x86的指令长度并不相同,因此在开始解码之前很难确定各指令的分界线。为了确保指令长度的识别不会影响解码速度,当指令行装载入L2I高速缓存的时候,K8/K10处理器就开始对指令进行解码。而指令的标签信息则被存储在L1I高速缓存的特殊字段中(指令的每个字节都有3bit的预解码信息)。将指令载入高速缓存的过程中就进行了预解码,这样各指令的边界无需通过解码信道就能确定,不受指令格式、长度的影响,保证了稳定的解码率。
处理器从高速缓存装载指令块,然后取出并发送需要的指令进行解码。K10微架构的CPU从排列成32字节块的L1I高速缓存取出指令,而K8和Core 2处理器则是从16个字节块中取出指令。在每个时钟周期,K8和Core 2处理器都能快速从16个字节块中取出指令,并发送3条平均长度为5个字节的指令进行解码。但是,有些x86指令可能长达16个字节,而且在某些算法中几个相邻指令的长度也可能会超过5个字节,因此,在这些情况下,就无法在每个时钟周期内对3个指令进行解码(见图1)。
Pic 1: A few adjacent long instructions limit the decoding speed
during instructions fetch 16-byte blocks.
图1
换句话说,SSE2是个很简单的指令,操作符为寄存器-寄存器类型,长度只有4个字节(如movapd xmm0, xmm1)。但是,如果指令提出了利用地址寄存器和偏移量进行寻址存储器的请求(如movapd xmm0, [eax+16]),那么指令的长度就要随着偏移量的大小提高到6~9个字节。如果辅助寄存器涉及到64bit模式,就会给指令代码附加一个字节的REX前缀。通过这种方式,64bit模式的SSE2指令长度就变成了7~10字节。如果它是个矢量指令(也就是说,如果它是4个32bit的值),那么SSE1指令还会再减少一个字节。但是如果它是个标量指令(在一个操作对象上),那么在同等条件下,它的长度将是7~10个字节。
在这种情况下,取出最大16字节块指令并不是对K8处理器的限制,因为它无论如何也不能在2个时钟周期内对3个以上的矢量指令进行解码。但是,对于K10微架构来说,16个字节块指令就可能成为其瓶颈,因此将最大取出的块大小提高到32个字节绝对是个正确的方向。
顺便说一下,Core 2处理器取16字节指令块的情况与K8处理器一样,这也是当指令的平均长度不超过4个字节时,Core 2处理器能在每个时钟周期有效地解码4条指令的原因。否则,解码器根本无法在每个时钟周期内有效地处理4条指令,有时甚至3条指令也不能有效完成。但是,Core 2处理器有一个专用的内置64字节缓冲器可以存储请求中最后4个16字节的指令块。指令可以从缓冲器中以每个时钟32个字节的速度取出。这个缓冲器允许指令储存的周期很短,取消了取指令速度的限制,而且每次推测到周期开始时指令可以储存一个时钟周期。但是在每个时钟周期内不应超过18条指令,也不应该超过4个条件转移,也不能包括任何返回指令。
转移预测
如果一组指令发生了转移,那么CPU就应试着去推测程序下一个方向,以避免解码发生中断,同时继续对大多数可能的转移进行解码。在这种情况下,就要用到转移预测运算来取出下一个指令块。K8处理器采用了两级可调节运算进行转移预测。这种运算会考虑到预测历史,不仅把包括当前的指令而且还包括之前的8条指令。K8转移预测运算的主要缺点是无法动态地交替地址来预测间接转移。
间接转移时要用到一个指针,在程序代码执行过程中通过它动态运算。通常这些间接转移会被编译器插入变换格结构中。在面向对象的程序中调用寻址功能和虚拟功能时也会使用它们。K8处理器总是使用最后的转移地址来获得下一步取用的代码块。如果地址已经发生了改变,那么该信道就会被清除。如果转移地址偶尔发生了交替,那么处理器就会始终出现预测错误。间接转移的动态改变地址的预测最先会在Pentium M处理器中推出。因为在K8 CPU中还没有哪个运算法则会比面向对象的代码中的效率还低。
正如我们所希望的那样,K10改进了条件转移预测运算:
• 它能够对动态变化的间接转移地址进行预测运算。这个运算法则使用了512个元素的表。
• 共用历史寄存器从8bit提高到12bit。用它可确定之前的转移指令的历史。
• 返回地址堆栈的深度也从12个位置提升到24个位置。利用这个堆栈可以迅速取得返回地址的函数,从而使得取指令能够持续下去,而且也不用等待返回指令就可以接受堆栈返回地址。
这些性能的改进都会帮助K10能更快速地执行由高级面向对象的代码编写的程序。但是遗憾的是,很难客观地估算出K10转移预测单元的效率。在某些情况下,它处理一些数据的效率可能会比Intel处理器低。
解码
从指令高速缓存接收到的数据块会被复制到Predecode/Pick Buffer中,在这里指令被选出,定义其类型,然后被发送到相应的解码通道中。可以用一个或两个微运算指令解码的简单指令会被发送到“DirectPath”解码器中。那些需要三个或更多运算指令才能解码的复杂指令,则被发送给微程序解码器,即VectorPath。
图2:解码器
每个时钟周期最多可以有3个微操作(MOP)离开解码器信道。每个时钟周期解码器会处理3个简单的单个MOP指令,或2 MOP指令和1个单独的MOP指令,或1.5个2 MOP指令(在两个周期执行3个2 MOP指令)。要对复杂的指令进行解码可能需要3个以上的MOP操作,这也是他们需要几个时钟周期才能完成指令的原因。为避免在离开解码器信道时产生冲突,K8和K10处理器的简单和复杂的指令可能会同时发送进行解码。
MOP由两个微操作(micro-ops)构成:一个是整数或浮点运算操作,另一个是存储器地址请求。微操作会由日程安排程序从MOP中选出发送后,彼此各自独立地执行。
每个时钟周期离开解码器的MOP会被组合成3个MOP的小组。但是有时解码器会产生由2个MOP甚至只有1个MOP的组,这是由于在选择要解码的指令过程中,DirectPath和VectorPath指令发生了交替或各种延迟。像这样不完整的组会被填上一个空的MOP后再发送执行。
K8处理器中的矢量SSE, SSE2和SSE3指令被分割成MOP对,由他们分别处理64bit设备中128bit SSE寄存器的上层和下层64bit数据。解码的指令减少了一半,调度程序队列中的指令数量也减少了一半。
好在K10处理器中有强大的128bit FPU单元,因此不需要再将矢量SSE指令再分割成2 MOP。K8过去解码作为DirectPath Double的大多数SSE指令,现在在K10中解码为1 MOP DirectPath。不仅如此,过去通过K8微程序VectorPath解码器解码的SSE指令,现在通过简单的DirectPath解码器就能解码成较少的MOP:1个或2个,这取决于具体的操作。
现在,堆栈指令的解码也被简化了。通常被CALL-RET和PUSH-POP函数使用的大多数堆栈指令,现在也能由简单的解码器在单一的MOP中处理。而且,特殊的Sideband Stack Optimizer(边带堆栈优化器)计划会将这些指令转换成相互独立的、能同时执行的微操作序列。
边带堆栈优化器
新的K10处理器的解码方案需要一个特殊的Sideband Stack Optimizer(边带堆栈优化器)块。它的工作原理与Core处理器中使用的新的Stack Pointer Tracker(堆栈指针追踪器)单元相似。那么我们需要做些什么呢?x86系统使用CALL, RET, PUSH和POP指令来调用某函数、撤销某函数、传送某函数的参数,同时还节省了寄存器的容量。所有这些指令虽然不是直接使用的,但是ESP寄存器却会指出当前堆栈指针的值。如果在K8处理其中调用某一函数,你就能跟踪些指令的执行情况,只需将他们的解码表示为连续的修改堆栈寄存器指令和装载/保存指令的同等基本操作。
Instructions | Equivalent operations |
// func(x, y, z); |
|
push esi | sub esp, 4; mov [esp], esi |
add esp, 12 | add esp, 12 |
从这个例子可以看出:调用函数时,指令会按照顺序修改ESP寄存器,因此,下一个指令实际上取决于上个指令的执行结果。在这个序列中的指令不能被重新排序,这也是以mov eax, [esp+16]开头的程序不能执行的原因,直到执行了最后的PUSH指令。Sideband Stack Optimizer单元会追踪堆栈状态的变化,并将指令序列更改成独立的指令序列。而这都是在指令直接与堆栈寄存器工作之前,通过为每个指令调整堆栈的偏移量,同时进行同步MOP操作实现的。能与堆栈直接处理的这种指令可以重新排序,而不受任何限制。
Instructions | Equivalent operations | |
// func(x, y, z); | mov [esp-4], X | |
push esi | mov [esp-20], esi |
|
| add esp, 12 | sync-MOP |
在本例中,程序主体中开始运算的mov eax, [esp+16]指令只取决于同步MOP操作。现在,这些操作能与其他前面的指令同时运行。通过这种方式加快了传送参数、寄存器存储的速度,而且程序主体也能开始装载这些参数,并进行处理,即使所有的参数还未被成功传送而寄存器也未完成存储。
因此,更快速的堆栈操作解码、Sideband Stack Optimizer单元、更深的返回地址堆栈以及成功地预测间接交替转移,使得K10在处理多函数代码方面效率更高。
每个时钟周期,K10处理器解码器无法象正常条件下Core 2解码器那样对4条指令进行解码。但是这不会成为执行程序的障碍。在一个时钟周期内,指令的平均处理速度很难达到3条指令,因此,K10解码器对于队列中不缺少任何指令的计算单元的处理效率很高,因此不会出现空闲状态。