|
楼主 |
发表于 2007-5-26 19:18:44
|
显示全部楼层
标注为MIC的模块是XDR内存控制器,而XIO模块是物理层 - 所有的输入接收器和输出驱动器都在XIO模块中。数据管线也放在了XIO模块中。
正如在AMD的Athlon 64上已经看到的,拥有On-Die内存控制器显著减少了内存延时,这也发生在Cell上。
Cell的On-Die FlexIO接口
Cell另一个重要的I/O技术也受Rambus控制 - FlexIO接口。Cell配备了两个可配置的FlexIO接口,每个为48-bit宽,拥有6.4GHz数据通讯速度。

BEI模块是北桥接口,而FlexIO模块是物理FlexIO层。
“可配置”这个词是特别重要的,因为它意味着无需连接每条线路。进一步解释这个概念,就是不要把FlexIO接口看作只能连接到一个芯片的,而是具有不同宽度FlexIO接口的多个芯片。

Cell可配置FlexIO接口的一个可能的实现
虽然Cell的XDR接口提供了超过任何PC微处理器2倍的内存带宽,但Cell的FlexIO接口达到了76.8GB/s - 几乎10倍于AMD的Athlon 64芯片对芯片的带宽。
在Playstation 3中,几乎可以预料到NVIDIA的GPU和Cell处理器之间将占用这个极其巨大的带宽了,但它也可能被用做某些相当繁重的I/O接口。
在任何高性能游戏控制台中,主要的需求之一就是带宽,而由于Rambus,Cell拥有了足够的带宽。
三、Cell的有序构架
我们已经提到,PPE和SPE两者都是有序核心,但为了了解有序核心对性能的影响,我们首先必须知道一点背景知识。
相关性,指令排序和并行
什么是相关性?
在许多CPU文章中都提出了相关性这个概念。在非常基础的级别下,CPU接受到的指令流通常是这样的形式:
操作 目标单元格,来源1,来源2,...,来源n
指令格式将从一个CPU ISA变化到下一个,但一般的思想是CPU收到一个操作,一个目标单元格来存储操作的结果,以及一个或多个来源,在那上面获取数据来执行操作。取决于构架,目标单元格和来源可以是内存位置或寄存器。为了简化问题,我们假定目前所有的目标单元格和来源都是寄存器。
让我们来看一个填写一些数据的例子:
ADD R10,R1,R2
上面一行的汇编将被发送到CPU,告诉它把存储在R1(1号寄存器)和R2中的值加起来,并把结果存储到R10中。非常简单。现在我们再给CPU另一个要处理的操作:
MUL R11,R10,R3
这一次,我们把存储在R10和R3中的值相乘,并把结果存储在R11中。作为单独的一行操作,上面的代码是容易完成的,但在直接放到我们的第一个例子后的时候,我们遇到了一点问题:
ADD R10,R1,R2
MUL R11,R10,R3
ADD R9,R11,R4
第一行写入R10,同时第二行从R10中读取。在没有上下文的情况下,CPU可能在第一行完成前开始执行第二行 - 对第三和第二行的情况也一样。在这里我们遇到的是所谓的“写入后读取”(Read After Write,RAW)相关性。有许多类型的相关性,但理解了这个基础的例子就已经足以让我们进入即将到来的下一个话题了 - 这种相关性的影响。
相关性带来的问题是它限制了并行执行的能力。以Athlon 64为例。它有三个整数执行单元,所有这些执行我们上面使用的代码的能力是相等的。理论上Athlon 64能够同时执行并行的三行整数操作 - 假定操作之间不存在相关性。执行上面的代码时,Athlon 64整数执行单元中的两个将闲置,要等待代码的第一行被执行。
像我们上面讨论的这种简单的相关性,阻碍了现代流行微处理器发挥它们最佳性能的能力。它仿佛有三只手,但只能通过一次收拾一件物品来打扫房间;这样低下的效率真是令人沮丧啊。
围绕相关性调整指令
幸好,对代码中的相关性问题有多种解决办法;一是从硬件入手解决问题,另一个是在软件中解决问题。
软件编译器负责产生发送到CPU用于执行的汇编代码。因此,由于CPU内部工作的固有原理,一般来说编译器能够产生最少数据相关性的代码。
有微处理器构架是完全依靠编译器来实现指令级并行的,同时尽可能多地消除相关性。这些构架被认为是有序微处理器。
有序构架
顾名思义,有序微处理器只能按照指令被发送到CPU的顺序来执行它们。CPU最多可以并行执行多条指令,但它没有能力对指令重新排序,以更好地适应它的需要。
如果编译器足够好的话,那有序微处理器应该是优秀的。不过有两个关键的限制:
1. 对有序构架的二进制编译是非常依赖构架的
虽然Athlon 64和Pentium 4两者都完全能够运行x86代码,但它们采用了极其不同的微构架,具有不同的执行单元,并且各自擅长的领域有很大的差异。如果上述两个芯片都完全依靠编译器来实现并行和最大化性能的话,肯定有一个会遭受极大的损失。即使每个程序总会有两个版本,但那趋向于变得巨大而杂乱 - 特别是从升级/补丁的观点来看。编译器不得不密切关注着它正为之编译的构架,就算它像游戏控制台一样工作,没有众多厂商以共同的ISA提供不同构架的 CPU,但还是不如桌面x86市场的好。
2. 不可预知的内存延时
缓存在绝大多数时候是个好东西。微处理器上的缓存尽力保存了频繁使用的数据,所以它应该被做得与CPU通信的延时非常低。问题是缓存增加了一个不可预知的等级到它从内存获取数据的时间上。一次缓存冲突可能意味着数据要在10 - 20个周期后到达。一次缓存失败可能意味着好几百个周期的延误。至于有序微处理器,它不能根据数据可用性对指令重新排序,那么如果数据在缓存中不可用的话,CPU就不得不等待更长的时间以把它从主存中取出来,整个CPU不得不闲置,直到那个数据被从主存中读出。即使有其它的指令能够被执行,有序微处理器也无法有效处理待执行指令的重新排序,以克服不可预知的内存延时。
如果找到办法解决有序构架的限制的话,会有一些非常实际的好处:
1. 微处理器设计大为简化
为了处理待执行指令的重新排序,无序微处理器增加了数目惊人的复杂单元。我们将在下一节中更详细地讨论它们。通过把这个复杂度转移到软件/编译器那边,极大地简化了微处理器的复杂性,并为其它能够产生更好性能收益的单元剩下了晶体管额度。越低的复杂性也意味着越小的功耗和发热量。
2. 更短的管线
一般来说,为了处理指令的重新排序,构架的管线级数不得不增加,导致了更高的功耗,并且(由于更高的分支预报损失)而需要更精确的分支预报器。虽然对于较长的管线设计而言,增加管线深度的影响不会很大,但相对于较短的设计,增幅可能达到40%以上。
在历史上,简单的有序核心的思想一度被抛弃,因为有明显的另一个选择:无序构架。
四、无序构架
与有序构架相对的是无序构架。无序构架还是按照程序最初的顺序编译指令,并且仍然按顺序撤销指令,但实际的流程/指令的执行可能是无序完成的。
让我们来看看所有这些意味着什么吧。如果CPU改变了发送给它的代码的意图,那是毫无用处的。坦白地说,如果双击文件变成执行一串格式化命令的话,那CPU就没有价值了。虽然那是一个极端的例子,但为了确保这样的事情不会发生,CPU必须遵循两个准则:
指令必须按照程序最初的顺序进行编译(也就是通过CPU解释来找出它们正要求它做什么)
指令必须按照程序最初的顺序撤销(也就是每个操作的结果必须按照与它被发送到CPU的相同顺序被写入内存/磁盘)
有序和无序构架两者都遵循这两个准则 - 在这两个阶段之间无序构架所做的处理不同。我们前面提到了有序构架不能对待执行指令重新排序。假如我们拥有一个有序CPU,它有一个加法器和一个载入/存储单元,它被要求执行下列代码(为了简单,我们在这里不讨论带有网络的情况):
LD R10,R11
ADD R5,R10,R10
ADD R9,R9,#1
...
在第一条指令中,我们从存储在R11中的内存地址中载入数据到R10。然后,我们对刚从内存中取出的值做自加运算,并把结果存储在R5中。第三行,也是最后一行对存储在R9中的值赋予1的增量,并保存到R9中。快速浏览一下代码就发现,第二行不能在第一行之前执行。那样做将改变代码的目的(如果想要对某个值做自加运算,首先需要确保有这个值)。不过第三行是完全独立于第一跟第二行的。
对于有序微处理器,如果第一行要载入的数据就包含在缓存中的话,那么该指令将花费大约1 - 30个时钟周期来完成(根据构架和它所处的缓存级数而有所不同)。第二行在执行前将不得不等足那1 - 30个周期,然后到它执行了以后才轮到第三行。如果被请求的数据不是存储在缓存中的话(或许我们是第一次用到这个值,而我们又没有用到内存中存储在它附近的值),那我们有问题了。出乎意料地,第一行不是用大约1 - 30个周期来完成;现在它将用200个以上的时钟周期来完成。对于第二行倒没什么影响,因为它无论如何都要等第一行完成了才能执行,但对于第三行来说,它可以很容易地在CPU正等着从内存中取出数据的时候执行。跟在第三行后面的任何独立指令也受到缓存失败的影响。
然而,至于无序微处理器,缓存失败的情况就大相廷径了。代码仍然按照顺序解码,意味着它以跟有序CPU相同的顺序获得指令1,2及3,但这时候我们有了在第一和第二行之前执行第三行的能力,而不是空等着第一行完成。如果发生缓存失败,这就给了无序微处理器一个相当大的性能优势,因为它不是在那等着浪费时钟周期而什么都不干。那么,无序CPU是怎么工作的呢?
如果有人以任何你想要的顺序让你做一系列的事情,那你只用简单地接受这个列表并完成它就行了。但如果他们又让你以他们规定的顺序报告返回你已经完成的事情,那你肯定要先把它们写下来,再重新组织它们为符合需要的顺序。
无序CPU的工作非常接近于同样的方法,只是以一个指令窗口代替了待办事项列表。指令窗口的功能类似于待办列表 - 它有所有以最初的顺序解码的指令,并保留了一个记录以确保这些指令按照它们被解码的顺序撤销。
在指令窗口旁边,无序CPU还有一个时序安排窗口 – 就是在这个“窗口”中完成所有的指令重新排序的。时序安排窗口包含了正确的推理来标出相关和独立的指令,并在等待相关指令为执行作准备的时候把所有的独立指令发送到执行单元。
当先前的相关指令(也就是等待来自主存中的数据或等待其它指令完成的指令)变成独立时,它们就能够被执行了,也是按照打乱的顺序的。
你可能立即会说,增加的指令窗口,时序安排窗口,所有检测独立指令的相关推理以及没有提到的处理无序执行但有序撤销的推理,所有这些导致了更复杂的微处理器。但对于无序微处理器还有另一个重要的问题 - 性能上的增长和指令级并行是非常依赖于指令窗口的大小的。
这个窗口安排得越大,就完全能够执行越多的并行,因为CPU面对着一个更广的指令集合,从中选出独立的指令。同时,窗口安排得越大,时钟速度就会越低。例如,Itanium有非常大的指令窗口来满足它的执行单元,而Pentium 4为了达到更高的时钟速度,只有一个小得多的窗口。
尽管有着这样那样的不足,但所有的流行x86微处理器无一例外地采用了无序核心,因为保持单核心构造简单在制造工艺提升时并不是应予优先考虑的事情。无序构架的好处有两方面:
指令的动态重新排序让CPU回避了内存延时,可以获得更高的时钟速度。对于每次缓存失败,Pentium 4 3.6GHz不得不等待大约230个时钟周期来从主存中取出数据,这在CPU看来是许多的空闲时间。能够在期间通过执行其它的独立指令来利用这个空闲时间,是像Pentium 4和Athlon 64这类构架逃脱它们如此高内存频率倍频的惩罚的一个方法。
进一步增加指令级并行 - 通过对待执行指令的重新排序,无序构架能够在编译器无能为力的地方改善ILP。
所以,AMD和Intel两者显然已经为通用x86微处理器计算过了,无序是最有意义的。那么,为什么Cell的设计者又重起炉灶,为处理器配备了9个独立的有序核心呢?
要回忆起的第一件事是从有序构架中能够获得相当可靠的性能。Itanium是有序微处理器,基于类似于Cell的设计,通过它编译器应该能够实现无序核心的那种并行。最近一代的Itanium核心运行在流行x86核心的一半速度下,然而CPU每个时钟能够执行大约两倍于最快的x86 CPU的指令。引用Intel的Justin Rattner关于Itanium的话说,“一个适当设计的指令集应该毫无问题地帮到有序构架。”所以,很可能相同的情况也适用于Cell...
五、Cell的方法 - 不带缓存的有序
记住,Cell的设计者在估计他们使用的每个晶体管导致的性能增长的同时设计了该处理器(有点夸张了,他们没有计算到2亿3千4百万个晶体管中的每一个,但他们非常严密地估计了每个构架上的决策)。在这样做的过程中,鉴于无序核心将增加的复杂性,有序vs.无序的思想肯定引起了巨大的争论。
由于无序的主要好处是减少了对内存延时的敏感性,因此Cell设计者们提出了另一个选择 - 具有可控制的(理解为可预知的)内存延时的有序核心怎么样?
有序微处理器有一个缺陷,就是一旦引入了缓存就不再能够控制内存延时了。大多数时候,设计良好的缓存将提供访问所需数据的低延时。但考虑Cell面对的应用程序类型(至少是最初的设计意图) - 3D渲染,游戏,物理学,媒体解码等等 - 所有的应用程序都不依赖大容量缓存。看看Intel任何一款拥有巨大缓存的CPU,注意到缓存超过一定数量以后,3D渲染,游戏和解码性能通常不会再有很大的提升了。例如,Pentium 4 660(3.60GHz - 2MB L2)在Business Winstone 2004中较Pentium 4 560(3.60GHz - 1MB L2)有13%的增长,但在3D游戏中平均性能增长小于2%。在3dsmax中,完全没有由于额外的缓存造成的性能收益。在我们的媒体解码测试中同样可以看到类似的性能持平。Playstation 3的使用模式不会是用来运行Microsoft Office的;它将与许多这类“媒体相关”的应用程序打交道,像3D游戏和媒体解码。对于这些应用程序类型而言,巨大的缓存是完全没有必要的 - 低延时内存访问是必需的,许多的内存带宽是重要的,但没有缓存也能获得这两样东西。那怎么做呢?Cell会说明怎么做的。
每个SPE配备了256KB的局部存储器,注意,并不是缓存。局部存储器是不会自行工作的。如果想要在其中存放点什么的话,需要发给SPE一条存储指令。缓存是自动起作用的;它使用与硬件紧密联系的算法来对它应该存储什么做出合理的推测。SPE的局部存储器大小与缓存相仿,但却像主存一样工作。另一个重要的方面是局部存储器是基于SRAM的,而不是基于DRAM的,所以就获得了与缓存类似的访问时间(对于SPE是6个周期),而不是主存访问时间(也就是上百个周期)。
那么重点是什么呢?虽然没有缓存,但引入了延时非常低的局部存储器,所以每个SPE有效地拥有了可控制,可预知的内存延时。这意味着一个聪明的开发者或巧妙的编译器能够为每个SPE极其精确地安排指令的时间。编译器会准确地知道,来自局部存储器的数据何时将准备好,从而能够围绕内存延时确定指令和操作的时间,就像无序微处理器那么好,但却没有附加的硬件复杂性。如果SPE需要存储在跟 Cell相连的主存中的数据,那么延时是可以预知的,这还是因为没有了缓存,不必担心事情会搞乱。
把 SPE做成有序核心对于它们的任务来说是很有意义的。然而,PPE做成有序则更多是受到空间/复杂性的限制。虽然SPE处理了更多指定的任务,但PPE在 Cell中的任务是处理所有的通用任务,那些在SPE的阵列上执行得并不理想。这个方法具有的问题是,为了像一个性能相对可靠的通用处理器那样运行,它需要缓存 - 而我们已经说明了缓存可能会怎样拖累有序核心了。如果Cell构架有薄弱环节的话,那就是PPE了,但要再次指出,Cell不是面向通用计算的,尽管有人可能会这样颠倒来用。
有序PPE的不利方面在于通过把核心做成只有2个流程而尽可能多地简化了,就是说它最多只能并行执行两个操作。不过,对有序低效率设计的执行潜力损失在某种意义下被最小化了,至少没有许多晶体管被浪费在把PPE做成非常宽大的芯片上。一个好的编译器应该能够确保那两个流程端口被尽可能频繁地占用,即使微处理器是有序的也一样。PPE还能够一次处理两个线程,这也有意掩盖了有序核心对于通用代码的无能。
[ 本帖最后由 zglloo 于 2007-5-26 19:20 编辑 ] |
|