首先,AMD Barcelona处理器会在一个月内发布。这款处理器成为构建在AMD新的K10微架构基础上的第一个解决方案,AMD对它寄予了厚望。我们还是一睹为快,先来了解一下新解决方案中所采用的微架构的诸多亮点吧。
指令控制单元
解码的MOP三元组会进入Instruction Control Unit (ICU,指令控制单元),由ICU将它们传送到排序缓冲器(ROB)。每个排序缓冲器都是由24行3 MOP构成。每个MOP三元组都写在自己的那行。因此,ROB允许控制单元最多可监控72 MOP,直到这些操作完成。
MOP在ROB中以同样的次序被分派给整数和浮点单元的调度程序序列,在那里他们被解码器撤销。MOP三元组被存储在ROB中,直到所有旧的操作都完成和撤销。在撤销时,最后的值会被写入结构寄存器和存储器。操作撤销时,ROB中的操作顺序,他们的数据会从ROB中删除,只保存最终的值。必须保证出现意外或中断时,所有接下来提前完成的操作结果都能被删除。
整型执行单元
K8和K10处理器的Integer Execution Unit(整型执行单元)是由三个对称的整型信道构成的。每个信道都有自己的调度程序,它包括一个8 MOP序列、一个相同的整型数学运算和逻辑单元(ALU)组、地址生成单元(AGU)和条件转移预测单元。除此以外,还有一个连接到pipe 0上的乘法单元,而pipe 2则与新操作LZCNT和POPCNT的执行单元相连。这两项操作将在这篇文章的后面详细介绍。

图3 整型执行单元
每个MOP的排队选择都是由三元组中指令的静态位置确定的。而三元组的每个微操作都是从ROB中按照要执行的顺序分派来的。一方面它简化了指令控制,但是另一方面,一旦相互依赖的操作链并未非常顺畅地放在程序代码上,就可能会导致序列中的加载失衡。事实上,这种情况极少发生,几乎不会影响实际的性能。解码器会将乘法器和扩展的操作放在相应的三元插槽上,这样它们就能进入正确的信道。
正如我们前面所讲的,在整型信道的调度程序序列中,MOP被分割成整型操作和寻址存储器请求。在现有数据的基础上,调度程序可能会按照顺序将一个整型操作分发给ALU,同时将一个寻址操作分发给AGU。最多可以同时有两个存储器请求。因此,每个时钟周期最多可以分发3个整型操作和2个存储器操作(在任何组合中64bit的读写)。各种不同运算的MOP微操作则不再按照顺序进行分发,它取决于数据的准备就绪情况。一旦执行了运算和寻址MOP微操作,那么这个MOP就从调度程序列表中被删除,将空间留给新的操作。
K8处理器在程序级别的基础上选择存储器请求的地址微处理。在程序代码中后出现的存储器请求不能先于前一个请求执行。因此,如果不能计算出前面操作的地址,那么接下来的寻址操作就会受阻,即使操作数已经准备就绪。
例如:
add ebx, ecx
mov eax, [ebx+10h] ?C快速寻址计算
mov ecx, [eax+ebx] ?C地址取决于前一个指令的结果
mov edx, [ebx+24h] ?C 如果前面所有的指令未全部计算出地址,就不能发送这条指令并执行。
这样可能会导致性能的丧失,也是K8处理器的重大瓶颈之一。因此,尽管K8处理器在每个时钟周期能处理2条读指令,但是对某些代码它执行存储器请求的效率可能要比Core 2处理器低。Core 2处理器虽然在每个时钟周期只能读取一条指令,但是它能随机执行指令,而且还能跳过前面的读取和写入过程。
现在,具有K10微架构的CPU不会再遇到这种阻碍。K10处理器不仅能随机进行读取,而且在读取之前就可以写入,前提是CPU是确定的,这些读取和写入的地址之间没有任何冲突。在读取之前就可以写入,因此处理器能够显著地提高某些类型代码的处理速度,如读取周期从另一个数据从存储器读取开始,以计算结果写入存储器结束。
L1:
mov eax, [esi] // 数据加载
.....// 数据处理
mov [edi] , eax // 保存结果
cmp
jnz L1
在写入之前不能读取的处理器情况下,当前操作已被完整地写入存储器之前,也不能开始处理下一个时钟周期的重复操作。支持读取重新排列的CPU则无须等待当前操作完成,就可以加载新的数据进行下一次重复操作。
遗憾的是,如果地址是未知的,那么K10处理器就和Core 2处理一样,不能在完成写入之前进行随机装载程序。尽管这些随机性有时候会导致性能的损失,但是在真正的程序代码中极少会发生这种情况,只有约5%的可能性。这也是随着预期性能的提升,随机加载程序绝对正确的原因。
K10整型单元的另一项改进就是优化了整型除法运算法则。现在,整型除法操作的稳定性取决于被除数和除法器的最重要的位。例如,如果被除数等于0,那么除法过程就只需要一半的时间。实际上,整数除法也是个很少执行的操作。因为这个过程通常会相当慢,所以大多数时间在真正的程序代码中要非常注意尽量避免除法运算。大家通常会采用倒数相乘、移位或其他的方法进行代替,这也是优化过程不太可能对整个应用性能产生相当显著影响的原因。
总而言之,K10整型单元的处理会是相当有效率的。一旦增加了无序存储器请求处理,就不会再有任何明显的瓶颈。尽管K10并没有像Core 2处理器一样多的排队队列,但是它没有那么多多限制,如读取它的寄存器文件,另外,Core 2处理器被限制不能总是以最高速度执行操作,这样的工作限制对K10也是不存在的。
浮点单元
K8和K10处理器的浮点单元(FPU)调度程序是从整型单元调度程序中分离出来的,设计稍有不同。每个调度程序缓冲器最多能容纳12组3 MOP 的操作,理论上就是36个浮点操作。与对称通道上的整型单元不同,FPU是由3个不同的单元组成的:FADD用于浮点加法,FMUL用于浮点乘法,而FMISC(也叫做FSTORE)则用于存储器的保存和辅助操作。因此,调度程序缓冲器并没有将组中每个特定的MOP指派给一个特定的单元(图4)。

图四:浮点单元
在每个时钟周期,K8和K10处理器会给每个浮点单元分发执行一个操作的指令。K8处理器采用80bit的浮点单元。在一个解码阶段, 128bit矢量SSE指令被分割成两个MOP,各自处理128bit操作数的一半64bit,并会相继在不同的时钟内执行。这不仅降低了矢量指令的处理速度,还削减了FPU调度程序缓冲器有效大小的一半,而且因此还降低了无序指令运行的深度。
K10处理器采用的浮点单元宽度则提升到了128bit。K10处理一个操作中的128bit的矢量操作符,从理论上说,K10处理矢量SSE指令速度是K8的两倍。不仅如此,由于现在的MOP数量加倍,因此增加了调度程序序列的有效长度,从而允许继续执行无序指令。
K8处理器采用FSTORE单元来执行加载SSE指令。一方面,它不允许任何其他的指令同时要求执行此单元,另一方面也限制了同时下达加载指令的数量,只能为一个。K8能执行从存储器读取的两个并行操作,只要其中的一个指令结合了一个存储器请求和一个数据操作(因此也称为加载-执行指令)。如ADDPS xmm1, [esi]。
K10处理器改进了装载SSE指令的架构。
首先,数据加载指令不再使用FPU资源。通过这种方法,就可以空出FSTORE端口,供其它指令使用。现在,每个时钟周期能执行两次加载指令。
其次,如果存储器中的数据沿着16字节的边界进行了指派,那么非指派数据装载(MOVU**)操作的效率与指派装载(MOVA**)操作相同。因此,使用MOVA**并不会再给K10处理器带来优越性了。
第三,K10处理器现在能使用非指派加载,甚至是结合了数据操作的加载-执行指令。如果存储器中的数据是否已指派还不明了,编译器(或程序)通常会使用MOVU**指令将数据读取到寄存器进行下一步处理。利用未指派加载以及加载-执行指令,他们能减少程序代码中独立的加载指令的数量,因此提升了性能。编译器应该完整地支持这个特性。实际上,Intel开发的SSE规范建议,向未沿着16字节边界指派的地址提出加载-执行指令的请求应除外。为了与规范相兼容,在设计和编译中应考虑新的处理器特性,通过程序代码中的特殊标记允许加载-执行指令的非指派加载。
第四,从K10处理器的L1高速缓存进行数据读取的两条总线被扩展到128 bit。因此,每个时钟CPU能读取两条128 bit的数据块。这是一个非常重要的特性,因为同时并行执行2条指令需要4个操作数(每条指令需要2个操作数),而且在一些流数据处理的运算中,4个操作数的2个通常会从RAM中读取。与此相反,K10处理器中写入的两条总线的数据的宽度仍然是64bit,因此,当写入存储器时,128bit的结果就被分割成2个64 bit的数据包。因此,每个时钟CPU只能进行1个128 bit写入或2个128bit的读取,或1个128bit的读取和1个64bit写入。但是,由于读取的数量通常至少是写入数量的2倍,因此,在128bit数据的处理过程中写入限制不会真的影响处理器的工作效率。
第五,现在能在这三个FPU单元中任何一个单元中执行128bit数据的复制,MOV***寄存器-寄存器,而不是仅仅在FADD和FMUL单元。因此,它还释放出FADD和FMUL单元进行专用操作。
正如我们看到的,K10处理器的FPU变得相当灵活。它具有Intel处理器还不曾具备的一些独一无二的特性,也就是高效的非指定性加载,它包括装载-执行指令和每个时钟周期2个128bit数据的读取。与Core 2不同,浮点和整型调度程序使用分隔的队列。分隔的队列消除了使用相同执行端口引起的操作冲突。但是,K10 处理器还在执行SSE存储操作和一些数据传输指令时共享FSTORE单元,有时会因此影响处理器的速度。
总而言之,K10的FPU处理效率相当高,而且比Core 2的FPU高级(例如,每个时钟2个128 bit 读取和高效的非指定性加载)。
存储器子系统
装载/存储单元
当K8 处理器的AGU已计算出存储器请求的地址,所有的加载和存储会被发送给LSU(加载/存储单元)。LSU包括2个队列:LS1和LS2。首先,加载和存储操作会进入LS1队列的12个码元。LS1队列会按照程序代码确定的顺序,以每个时钟2个操作的速度向L1高速缓存发送请求。如果高速缓存丢失,那么操作就会置入LS2队列的32个码元中。这也是向L2高速缓存和RAM发出请求的来源。
K10处理器的LSU已做修改。现在,LS1队列只接受加载,而存储操作会被发送给LS2队列。在考虑LS2的存储操作地址的前提下,从LS1发出的加载能以无序的方式执行。正如上面我们提到的,K10会将128bit存储当成2个64bit的数据进行处理,这也是为什么每次在LS2队列中总要占据2个位置。
L1 高速缓存
K8和K10处理器的L1高速缓存是分隔开的:64KB用于指令(L1I)和数据(L1D)。这种较低的结合性可能会导致目标是相同设置的数据行经常发生冲突,反过来也会增大高速缓存丢失的数量使性能降低。低结合性通常会由更大容量的L1高速缓存进行补偿。L1D很突出的优点是具有两个端口:任何组合情况下,它都能在每个时钟周期内处理两条读取和/或写入指令。
不足的是,K10处理器的L1高速缓存仍然具有相同的尺寸和结合性。唯一值得注意的改进是读取总线宽度的提升。正如我们在前面的章节中所提到的,现在,在每个时钟周期CPU能执行2条128bit的读取,因此,极大地提高了局部存储器中处理SSE数据的效率。
L2 高速缓存
双核和四核K8和K10处理器的每个核心都有各自独立的L2高速缓存。K10处理器中的L2高速缓存还是相同的:每个核心512KB,可结合16个。专用的L2高速缓存与Core 2 CPU中共享的L2高速缓存相比有其优点和不足。它的优点是,当几个处理器核心同时装载很大的程序时避免了高速缓存的冲突和竞争。而它的不足是,当只有一个应用程序在运行时,每个核心可用的高速缓存减少了,因此运行速度降低了。
L2高速缓存是专用的:L1和L2高速缓存中保存的数据不能复制。L1和L2高速缓存会沿着两条单向总线交换数据:一条接收数据,而另一条发送数据。在K8处理其中,每条总线的宽度都是64bit(8个字节)(见图
尽管目前还没有得到证实,但是K10处理器中的发送和接收总线会加宽一倍,也就是每条总线128bit(见图5b)。这样就会在同时有两个甚至更多个数据行进行寻址时,明显地降低高速缓存的存取时间。
L3 高速缓存
为了补偿相对较小的L2高速缓存,K10处理器还在所有的核心之间提供了一个共享的
L3高速缓存会提高核心之间的数据传送速率。正如我们已经创建的,当代Athlon 64处理器通过存储器总线来交换核心之间的数据。因此,存取共享的已做修改的数据的速度会变得相当慢。据AMD的资料显示,四核K10处理器可能会通过L3高速缓存来交换数据。一旦从某一个核心发出请求,那么拥有修改后数据的核心就会将其复制给L3高速缓存,提出请求的核心就可以从高速缓存中读取数据。从另一个核心的高速缓存存取修改后的数据的时间将会大大缩短。

Pic.6: Data transfer between the cores in K10 processors.
L3高速缓存的等待时间明显地要比L2高速缓存长。但是,AMD的资料显示,L3高速缓存会随着工作量的不同而进行自适应性调整。如果工作量并不是过于繁重,那么等待时间就会改善;如果处于繁重的工作量,那么带宽就会提高。
TLB
除了存储指令和数据的高速缓存外,处理器还另外拥有一种类型的高速缓存存储器:旁路转换缓冲器(TLB)。这些缓冲器用来存储从页码表获取来的虚拟页和物理页地址之间的连接。TLB进入的数量决定了会涉及多少内存页,无需附加庞大的页码表。这对于随机处理内存页的应用程序来说特别的重要,此时他们会经常对不同页上的数据提出请求。K10处理器有相当多的转换缓冲器。为了方便大家使用,下表将详细说明K8和K10处理器TLB的性能。

从表中可以看出,K10处理器有相当多的缓冲器可用来转换2MB的数据页。