天天看点

IBM体系结构师对IA-64的批评与分析

        Intel和HP现在开放了足够的信息用于初步评价IA-64是否真是象宣称的那样是计算机体

系结构上的突破。以这一作者的观点-并不是。批评集中在整数性能。许多如浮点、系

统指令、多媒体和x86兼容性等重要问题并没有涉及,这篇文章也不是针对Itanium(Mer

ced), McKinley或者其它特别的实现的评价。文章试图探讨由IA-64体现的EPIC(expl

icitly parallel instruction-set computing显式并行)概念和在实际情况下它如何工

作。

【1】超标量的竞争

  使用寄存器重命名的乱序超标量指令发射已经成为实现RISC和CISC结构的标准做法。微

处理器设计者因为下列原因使用这一方法:

 1.将指令执行分为许多步是的提高主频成为可能,虽然这样会造成更多的流水段。

 2.复杂的指令被分解成多个简单的指令,是的指令集结构(ISA)将内部指令和给定的功

能单元对应这一要求显得不是太重要。这一方法简化了CISC体系结构如x86的实现,同样

对许多RISC风格的机器也有好处。

 3.巨大的再定序缓冲使得提前执行Loads(读入)操作成为可能,可以隐藏存储延时。如

果分支预测错误,长延时的loads操作可以在还未完成(in flight)时就被取消。

 4.超标量处理器通常禁止执行在预测路径上的指令。所以与采用多路径前瞻执行策略相

比使用的功能单元更少。更少的功能单元简化了bypassing旁路逻辑并减少了功耗。

 5.基于实际执行路径和Cache失效结果的动态指令调度得到了很好的ILP。

  当然这些优点是有代价的。全部概念集中在有效的分支预测上。一次错误的分支预测将

要求流水线排空并再充满。这样的耗费是复杂层次式分支预测硬件产生的动机。但是产

生的结果令人惊奇的好:超过95%的分支预测精度是很普通的。

除了复杂的分支预测以外,还有用于从巨大的再定序缓冲中去除错误相关,相当复杂的

相关性检测和寄存器重命名。Alias检测硬件也是必需的,为了保证Laods可以被提前到

Store之前以减少由存储器访问造成的流水线暂停。除了这些困难外,经验表明想要在性

能上超过一个良好设计的超标量处理器是困难的。

【2】 在微处理器性能上的艰难选择

   计算机的性能是时钟频率(兆赫兹),完成指定任务需要的指令数(也叫做路径长度p

ath length)以及每条指令所需要的平均时钟周期数的函数(CPI)。对于提高三个参数

中的一个指标是容易的,但是要得到纯净的性能提升就不是那么容易的了。让我们看一

看IA-64的会产生什么结果。

   IA-64的一个惊奇之处就是我们没有听到高主频的声明,虽然EPIC被认为是比超标量处

器更简单的结构。很难知道为什么会这样,但是我们可以猜测几乎全部的复杂度被集中

在CPI上,象IA-64那样,使得很难达到更高的兆赫兹。IA-64一个为CPI牺牲频率的理智

是在同一周期执行比较和分支相关性处理(dependent branches)。

   另一个频率问题可能来自于谓词执行(predicated execution)。谓词需要更多的功能

单元,因为一半的谓词结果可能被丢弃。但是更多的功能部件意味着更长的走线和更大

的硬件复杂度,都会影响频率的提升。另外频率的另一个重要限制是功耗。IA-64使用的

众多的资源将消耗更多的电能,使得再高频工作更加困难。

在IA-64中的动态路径长度将会比其他结构更长。下面的因素导致了这种更长的路径:

1.在指令执行时无效的前瞻(speculation)。同时即使这样的前瞻是有效的,也同样有

一个显式的“前瞻检测操作check operation”。

2.近一半的谓词指令(predicated instructions)结果被丢弃。

3.与其它结构中的load和store不同,IA-64只有一个基地址寄存器(base register),

并且没有偏移域(displacement field)。因此在大多数情况下有效地址必需显式的预

先计算。虽然IA-64有运行后更新(post-execution-update)形式的指令,允许某些顺

序的loads使用相同的基地址而不需要显式的地址计算,但是高频率的非零偏移load和s

tore指令保证会使IA-64的路径增长。

4.IA-64没有带符号扩展的Load,在一个64位处理器中执行含有(许多32位整数的)C代

码时将会显著的增加路径长度。

5.IA-64没有在通用寄存器中的整数除法和乘法。必需用指令显式的将数据拷贝到浮点寄

存器来完成这些操作,再将结果拷贝回来。

   IA-64也有一下指令可以减小路径长度,如移位加(shift-and-add)指令对数组下标计

算很有用,可以用在诸如强度消减(strength reduction)这样的编译器优化不适用的

情况下。另外预测消除了许多分支指令。但是,IA-64的路径长度比传统RISCs和CISCs的

增加是相当可观的。

   CPI是一个更复杂的因素。我们必需检测指令Cache和数据Cache的效率和几乎无限的Ca

che CPI。IA-64 128位的指令包中含有3条指令。在同样的位数下,典型的RISC处理器将

编码4条指令而一个x86处理器将有6到8条指令。另外,IA-64引入了何种指令占据在指令

包中特定位置的限制。同时最后,所有的分支目标必需是一个指令包的开始。最终的结

果是更大的代码体积,在大多数情况下,需要更多的时钟节拍来执行这些代码。

   恢复代码对于消除引起异常的前瞻操作的影响是必需的,它也增加了总的代码空间。如

果恢复代码离得很近,那么它会引起更大的指令Cache的污染(more instruction cache

pollution)。如果它离得很远,一个异常将会触发页实现异常处理从磁盘上载入恢复

代码。两种情况下更大的代码体积造成了更高(更糟)的CPI。

  全部的全部,IA-64的代码可能是作相同工作x86结构的4倍。这将给指令Cache带来相当

的压力。另外,结果不会被使用Load前瞻将会污染数据Cache并且浪费宝贵的带宽。

同样在IA-64的顺序执行执行中会出现流水线暂停,而乱序执行超标量不暂停的情况。考

虑两个并行致死的load操作:

load  ra =     //两条指令并行执行

load  rb =;;     //(;;标识一个指令包的结束)

如果在后面的一个指令包中ra和rb被使用:

add  rx = ra    //两条指令并行执行

load  ry = [rb];;

  如果一个ra的载入引起一个cache失效,这是一个在之前无法预测的事件。超标量处理器

将会修改其执行ry载入的调度-以及与条有依赖关系的指令-与Cache失效并行执行。在

操作数就绪后可以执行add操作。IA-64可能可以发射ry的Load操作,但是它不得不在Cache

失效返回之前暂停。

   丰富的资源和大量的无用指令执行几乎保证了IA-64在无限的cache CPI下表现良好,而

在这种Cache情况下由于Cache引起的流水线暂停是不作为考虑因素的。但是在实际的Cache

CPI中这恰恰是关键,而在这点上超标量处理器作得比IA-64要好。

IA-64体系结构为了提供性能作了一系列的选择。而决策的时候其实根本没有弄清楚这样

的决策是否会真正会产生整个在频率、路径长短或者CPI上的提升。还要同时考虑推向市

场的时间。复杂的设计需要更长的实现时间用于事实上也比简单设计作起来更慢。

【3】复杂性陷阱

    每一个人都喜欢简单的设计。困难的事情是同时达到低耗费和高性能同时保证整体

的简单性。让我们来考察一下有可能引起IA-64复杂性的几个假设的原因。前瞻是IA-64

中的几个核心思想,因此我们从这里开始。

    如果一个超标量处理器的再定序缓冲和寄存器重命名部件被显示硬件(explicit

hardware)替代,那么必需有某种方法来推迟异常。在IA-64中一个重要的推迟异常的记

住是在每个通用寄存器上的NaT(Not a Thing)位。

    引起异常的前瞻Load将设置目标寄存器的NaT位,以此来将异常结构传递的所有使用

这个寄存器的指令中知道它被重写或者遇到一个检查Check操作。如果check指令发现Nat

位被设置,一个被延迟的异常产生。Check指令将控制交给回复代码重新执行相关的操

作。

    这一Check特性允许loads和其它与之相关的指令提前到分支之前。但是代表机器状

态的Nat位,必需被保存saved和还原restored。为了完成这一操作有两个应用寄存器和

相关的指令来收集所有的NaT位,在指令修改,检测和重现Nat时使用。

    将Load移动的Store之前(数据前瞻)需要一个存储器变名检测表(memory-alias-

detection table)-在IA64中这个硬件表格被称作提前载入地址表(advanced-load-a

ddress table),或者ALAT。特殊的Load将会占用ALAT中的表项,而和一个ALAT表项中

地址相吻合的Store将清楚这一表项。在数据前瞻的时候,check指令将会检查ALAT看是

否有这样的store:有一个Load和它访问同一个存储器地址,而且已经被移动到它前面。

(数据前瞻在某些简单的情况下可以使用一个检查性载入check load来进行恢复。)恢

复代码要求原来治中的操作数仍然可用,这样才能重新执行。这样增加的寄存器的需求

也必然是导致一个大寄存器文件(128个通用寄存器,128个浮点寄存器)的因素。

   不幸的是更多的寄存器意味着更多的状态,仍在例程调用和返回时必需保存和恢复这

些状态。为了减少保留和恢复寄存器的消耗,IA64定义了一个寄存器堆栈处理,让人回

忆起Sun的Sparc体系结构中的寄存器窗口。一个当前帧标志CFM(current frame marker)

寄存器保存了控制堆栈的六个域。

   为了让这个寄存器堆栈工作起来,处理器保留0-31的寄存器名字不变化而通过从CFM

中加入不同的堆栈帧基地址来动态映射寄存器32-127。(实际的情况要比这个更复杂因

为循环寄存器(rotating-register HP爱使用)通过另外不同的方法来实现循环寄存器

重分配)。寄存器堆栈机制需要专门的管理寄存器来控制在堆栈溢出时将寄存器保留到

存储器中。由于在频繁使用call调用的时候堆栈帧溢出就会产生,一个寄存器堆栈引擎

RSE(register stack engine)在后台异步的保留和恢复寄存器。这一RSE机制将会营区

页故障,它需要更多的专用寄存器和指令。无论这些机制是否都是英文前瞻引起的,都

很难将IA-64看作是一个简单的机器。实际上使用乱序执行和寄存器重命名,同样在较高

频率下工作的Alpha21264要比Merced少两个流水段,只有10级。

【4】动态信息辅助

   超标量处理器扩展使用了它在执行时动态收集到的信息。与之相反,IA-64的机器几

乎完全依赖于在编译时静态搜集的信息。有时这会导致IA-64处理器极低的性能。这里有

一个未经优化的源程序例子;

    cmp    p1 , p2 = …

  (p1)  br.cond   low probability path;;

    l    ra = [ rb ] ;;

    add    rc = ra , rd ;;

    use of    ( rc )

   如果IA-64编译器假设分支发生是低概率的,它将会将代码变换为如下的控制前瞻方

式(其他无关的指令被省略了)

    l.s    ra = [ rb ] ;;   //前瞻载入

    add    rc = ra , rd   //与控制    前瞻相关的加法指令

    cmp    p1 , p2 = …

  (p1)  br.cond   low probability path;;

    check.s   rc , recovery code

    use of    ( rc )

   现在假设当低概率的路径被选择时rb的值是随机的。在这种情况下,一个Cache失效

将会让处理器在遇到add操作的时候暂停。它必须等待对ra的载入l.s完成,如果这一数

据必需从主存中获得的话这有可能将需要成百上千个周期。即使这个低概率路径只在10

%的情况下发生,性能也会严重下降。如果编译器得到的可能性是错误的,那么最终的

结果就会很恐怖了。(注意到除非add放在比较之后否则它是无法作为预测指令执行的,

而在调度之后再进行预测将引起新的相关性,而这些在超标量处理器中是不存在的。)

一个超标量处理器,已另外的方式执行,仅仅是执行第一段代码,尽可能早的开始启动

load操作,如果低概率路径发生的话将会将其取消。超标量处理器的好处是它可以根据

程序的动态行为来改变它的假设,而IA-64只能依靠编译好的代码生存。

   其他类似的信息如果Cache和TLB失效率对于处理器而言也是很有用的。某些信息是相

当复杂的,会引发多种性能交互现象(multiple interacting performance phenomena

)。这是的IA-64严重依赖与超级聪明的编译器。但是它需要的优化和现在RISC和CISC编

译器使用的有相当的区别。

   传统的编译器依赖于各种变换transformation,变换非常可能提高程序的执行效率。

例子包括代码移出循环、消除冗余计算、常量传播和寄存器分配。IA-64编译器用于需要

这些变换,并加上的全新的预测和前瞻要求。IA-64编译器还严重依赖于代码轮廓采集(

code profiling)来优化变换过程。

   当然Code Profiling技术在RISC和CISC编译器的某些变换过程是很有用的,但是得到

的益处还没有让这一技术变得非常重要。这其实是一件好事。让程序员去打开编译优化

选项是极为困难的,要说服他们去Profile他们的代码就更不可能了。另外为Profiling

写专门的可以代表代码在未来几年运行中可以碰到的任何可能情况的测试集几乎是不可

能的。Profling软件的逻辑必然是百万量级的源代码或者更多。

   在今天的计算领域编程已经是一个单独的大问题了,当一个公司试图推出他们的软件

产品时增加复杂度不是他们需要的。实际上我强烈建议SPEC仲裁委员会改变他们在基本

SPEC测试集中强制轮廓引导profiling-derccted的反馈这一决定。(当然,为了测试封

装性能Profling应该被允许使用。)当我们基于使用profiling-derected 反馈编译后的

程序来比较性能时,我们忽略了程序开发的实际。鼓励开发一种在测试集上运行良好,

而在需求应用环境中普普通通,在开发环境中性能变化的机器是不明智的。但是IA-64似

乎很象这样的东西。

还有一个最后需要注意的:关于由一个编译器产生的恢复代码的可靠性的严重问题。这

部分代码可能会异常复杂,但是由于它很少被激活、通用而不重复(???)因此很难

进行测试。如果程序员看到一个神秘的错误时,他们的第一反应是抱怨恢复代码然后将

关闭编译优化作为一种预防方法。这样的做法才可能会被用在实际没有问题的程序上。

【5】结论

    EPIC方法基于丰富资源的应用。这些资源包括比超标量处理器更多的load-store,

计算和分钟单元,同样有更大而低延迟的cache。因此IA-64的赌注在于,未来功耗不好

是一个主要的限制,并且丰富的资源和展示他们的机制将不会因为他们在时钟速度、路

径长度或者CPI因子上的相反作用而降低性能。我的观点明显是持怀疑态度,并且我的经

验是在计算机体系结构领域没有不被惩罚的聪明想法。但是我们面对的是一个复杂的问

题,只有时间能告诉我们是否IA-64机器将会超过x86 CISC或者RISC的性能。

   Marty Hopkins在IBM公司的编译器、编程语言和CPU体系结构领域工作了30年。他是

一个RISC处理器-IBM 801的原创体系结构师之一,同时他还是Pl.8编译器编译器-全局

优化和使用寄存器染色进行寄存器分配的先驱-项目的管理者。他对IBM 390,POWER,

PowerPC和AS/400等处理器的编译器和体系结构都有贡献。Marty的联系方式是[email protected]

bm.com

【附录】IA-64的代码膨胀

   下面是估计的IA-64与x86处理器比较时可能的代码膨胀。这是基于我关于一个类似I

A-64的IBM研究处理器的经验得出的估计。同时我合并了许多关于IA-64实际代码情况的

谣言。

   1.RISC风格处理器比x86体系结构的代码膨胀系数实际上要比RISC宣称的要高。我们

经常看到的是2:1的放大。让我们假设只有1.5:1。

   2.在RISC处理器放4条指令的空间内IA-64放置3条指令。这实际上有1.33:1的增长。

   3.由于在IA-64指令包中的指令类型限制,IA-64需要用NOP来填充空槽。平均每个指

令包中的有用指令数为2。这又带来1.33:1的增长。

   4.分支目标必须是一个指令包的起始。这要求如果分支目标在一个指令包中间时必须

复制分支目标点的那部分代码。分支一般在代码中占20%。我预计需要额外复制的将是

一半。也就是总数的10%,1.1:1的扩展。

   5.Check操作在不考虑前瞻执行的Load的使用时无论数据前瞻还是控制前瞻都会需要

它,否则很难想象IA-64可以和超标量处理器竞争性能。让我们假设25%的指令是Load,

一半需要前瞻。虽然某些情况下Check操作会双重使用dual use(???),它带来大致1.11

:1的扩展。

   6.在IA-64中没有RISC的基地址+偏移的Load。同样与x86不同也没有基地址+索引+

偏移的load。因此很多在x86中的Load和Store需要用显式的指令进行地址计算。IA-64的

存储指令有一种post-increment执行后增加模式可以减少一部分这样的显式地址计算。

但是考虑到在所有指令中存储指令大概占35%,很难看到代码的扩展少于15%,这很可能

是低估了。因此我们计算由此产生的代码膨胀为1.15:1。

   7.许多Check操作需要恢复代码recovery code。但是这样引入的代码扩展是不一致的

(out of line),因此我们在这里列举其可能带来的代码膨胀,但是不将其作为计算因子

。(在一半的Check操作需要恢复代码的情况下,恢复代码块平均需要重新执行两条指令

,并且由于返回地址不是指令包的边界需要一条分支指令,这样计算出来的扩展比是1.

2:1。)

   8.没有代符合扩展的load指令,我们在这里列举其可能带来的代码膨胀,但是不将其

作为计算因子。

   9.预测是一把双刃剑。某些时候由于消除了分支,它可以节省代码空间,但是在使用

预测是某些技术不得不复制代码。再一次,我们在这里列举其可能带来的代码膨胀,但

是不将其作为计算因子。

   10.有一类可以使用在任何机器上的优化方法,但是由于其在RISC和CISC机器上使用

的结果并没有体现出明显的加速比因此并没有在这两种机器上使用。而这类优化方法对

IA-64而言是至关重要的。他们包括例程嵌入procedure in-lining,IA-64用它来保证得

到足够大的编译优化用的窗口;许多VLIW的调度技术,例如路径调度trace scheduling

,全局调度global scheduling和末尾复制tail duplication。这些优化方法的耗费是很

难估计的,但是他们是足够复杂的,其它的处理器仅仅是明智的选择性使用;x86机器由

于缺少寄存器使用的这类优化方法甚至比RISC还少。1.3:1的IA-64对x86处理器代码扩展

可能是低估了这些优化的开销。

最后放到一起,这些因素可以预测IA-64代码将会是完成相同工作的x86代码的4.8倍。忽

略由于优化带来的30%开销,扩展将会是3.7倍。为了公平起见,我们同样列出可能消除

IA-64代码体积的技术:

   11.Load基地址寄存器的运行后增加更新post-increment updates某些时候可以消除

在Loop循环中的一条加法指令。

   12.将一个比较和和一个逻辑操作结合在一起的能力可以节省一条特殊的指令。

   13.IA-64有计算r1+r2+1的加法指令。

   14.循环寄存器文件rotating register files可以解药某些Loop循环中的指令。

这些技术是很有趣的,但是4者加起来总计还不能达到5%的变化。我认为在不包含恢复代

码的情况下,IA-64比x86有4倍的代码扩展是对优化后代码的合理预测。

继续阅读