单线程性能为何如此孱弱?IBM Power8处理器微结构前端分析

  • 来源:微型计算机
  • 关键字:IBM,Power8,处理器
  • 发布时间:2016-08-23 15:20

  当IBM首次披露其新一代服务器处理器“Power8”时,外界曾对它寄予厚望,认为它的规格强大得令人瞠目结舌,堪称蓝色巨人实力的最佳体现。然而从近期业内公布的Power8 CPU性能测试结果来看,其SPEC单线程的成绩却令许多曾经看好它的人大跌眼镜。一款拥有乱序8发射强劲规格的CPU最后居然被乱序四发射的Intel CPU轻松击败,原因何在?笔者仔细对比了Power8的微结构公开论文和其他众多工业级微结构的设计,发现其微结构前端设计存在单线程性能上的明显缺陷,接下来就让我们一起来了解下到底是什么拖累了这位蓝色巨人。

  虽然我们从Power8 CPU的微结构规格来看,它就是令人敬畏的怪兽级设计,拥有前端8条指令/周期的取指宽度,并支持8条指令的重命名,10条指令的执行宽度,以及8条指令的交付宽度,似乎是个名副其实的八发射处理器,但实际上并非如表面规格上看到的这么简单。Power8的单线程处理能力在面对分支密集型的整数应用时将受到严峻挑战,主要问题在于取指效率低下。笔者仔细对比了Intel与IBM两边的公开资料后得出的初步结论是,IBM的分支预测准确率有望力敌Intel,但是取指效率恐怕会落后Intel的设计一代到两代。若IBM一方的论文无误,Power8的实际单线程取指带宽远逊于其总体标称值。当然在仔细分析缺陷的来龙去脉前,我们必须先准备一些为后文进行铺垫的基础知识。

  指令读取逻辑基础

  一般我们提及CPU单线程处理能力时,很多时候是代指发掘指令级并行度的能力,也就是超标量执行、推测执行、乱序执行等方面的综合能力,但是要实现这些所有能力有一个前提,就是指令读取的能力。如果指令都读不上来,再强的乱序多发射都是空谈。

  取指令的速度对于乱序多发射的微结构来说,不仅关键,而且特殊。在执行后端产生的停顿,诸如一级高速缓存或者二级高速缓存缺失、真实数据依赖等等,都可以靠乱序执行引擎强大的调度能力,在乱序执行窗口内部调度不相关指令提前执行从而予以掩盖或者部分掩盖。概括地说,乱序执行的本质在于用不停顿的指令去掩盖停顿的指令,多发射的本质就是把许多条指令的执行周期进行相互掩盖。但是取指令停顿特殊的地方在于,这种停顿是乱序多发射无法掩盖的。因为连指令都取不上来,再强的乱序多发射也没有意义。正因为此,取指令在专业论文中常常有一个更加高大上的称呼,叫指令供应(instruction supply)。

  以乱序多发射的经典之作Alpha 21264为例,乱序执行引擎的“管辖范围”是从寄存器重命名(rename)阶段才开始。任何发生在这一阶段之前的流水线停顿都是乱序执行所力不能及的,只能依靠流水线来维持吞吐。例如,当一级指令缓存发生缺失,导致取指令停顿时,如果乱序执行所覆盖的流水线中所有的指令尚未排空,就能够暂时维持一段时间的指令结果吞吐,但乱序执行所覆盖的流水线阶段如果都已经排空,整个处理器核实际上就会处于没有指令结果交付、完全在等待二级缓存响应一级指令缓存的状态。

  指令供应是乱序多发射,特别是乱序宽发射架构中的核心难点之一。整个上世纪80年代和90年代,很多顶尖北美高校研究院所,包括当时的处理器微结构设计巨头Intel和DEC Alpha等贡献了无数篇经典论文,在计算机工程发展史上写下了值得永久传唱的经典篇章。大量的研究发现,取指令效率大致上取决于以下几个方面:

  1.缓存子系统供应指令的能力。精简指令集(RISC)系统的指令长度大都在4个字节,x86虽然是不定长指令集,但统计分析显示编译器产生的指令序列里单条指令的平均长度也在4个字节上下。因此以4个字节计算,如果要做乱序四发射,每个周期就要取4×4=16个字节。如果要让这个乱序四发射的微结构跑在3GHz的主频上,缓存系统仅为满足指令供应需要就得提供至少48GB/s的带宽,这也是现代高性能微结构无一例外地使用指令与数据分离的一级缓存结构的原因之一。除了带宽压力以外,缓存子系统也必须要恰到好处地留住经常取用的指令、踢出不太常用的指令。一级指令缓存和一级数据缓存一样需要注重局部性、注重替换算法,甚至配备一级指令缓存专属的预读取器(prefetcher)。

  2.分支预测准确率。一般的算术逻辑指令不会改变紧跟自己身后的下一条指令的读取地址,但有一大类指令被称为分支指令,包括条件跳转、非条件跳转、函数调用、函数返回,间接跳转等。这一类指令有可能改变下一条指令的读取地址。对于整数应用来说,大约四分之一到五分之一的指令是分支指令,如果处理器做出了分支预测,去预判分支的走向,但是预测发生了错误,就是误预测,或者叫分支预测失败。分支预测失败的后果是灾难性的,并且可能会进一步触发其他种类的推测执行失败。2008年路易斯安那州立大学的研究人员针对Nehalem结构在SPEC CPU2006上的实测数据显示,分支预测失败及其他各种推测执行失误的指令数占到总指令数的约1%~40%之间。这些预测失败导致的指令执行最终结果都是被丢弃掉的,不仅拉低了性能还推高了功耗。

  3.分支预测的判断速度。这个判断速度分为两部分,其一是分支预测错误恢复时间,有时也称为预测失败惩罚(mis-prediction penalty)。一般来说,分支预测做出以后,需要等到这条分支指令进入乱序执行后端,并且完成执行之后,才能做出预测是否正确的判断,因此分支预测失败的代价大致上等于分支结果计算完成的时间点扣去分支预测做出的时间点,这一时间一般等于简单整数执行周期数的一半或者一大半。在这期间取指的所有指令都是推测性执行状态,如果被发现前头的分支预测失败了,这些推测性执行的指令都要被冲掉,导致前文中提及的现象;其二是分支预测的预测速度。理想状况下,取指令是每个周期都要进行的,因此每个周期都要进行分支预测。高性能分支预测的方法是查询多张记录了分支历史信息的记录表,判断分支指令的种类、该分支自身的跳转发生情况,围绕该分支的其他分支指令的跳转发生情况等,然后依据这些历史信息为当前的分支点做出预测。为了满足分支预测准确率的要求,一般分支预测历史表都会做得很大。

  分支预测历史表本质上也是一块高速缓存,做大了以后也会导致访问延迟升高的问题,而单线程性能对分支预测的预测速度尤为敏感。如果分支指令改变了下一条指令的读取地址,但是分支预测还来不及完成,处理器一般会顺序地接着往下取指,于是就会出现误读取(misfetch)。误读取对比误预测来说发现时间短很多,一般可以在短短几个周期内予以发现并且纠正。但是误读取的出现频率远超过分支预测失败,配备了高性能的分支预测器以后,一般每千条指令只会出现几次预测失败,但是若不采取其他设计,误读取的概率是每十条指令即可发生一次。

  误读取的效应是非常可怕的。德克萨斯大学奥斯汀分校的研究人员在2000年对180nm和100nm工艺下的分支预测器建模研究结果显示:当分支预测历史表的大小抬升时,由于能够记录更完整的分支历史信息,分支预测准确率会上升,因此带来IPC的提升。但是把记录表加大到需要2个周期才能完成访问时,IPC会下跌20%左右,若是加大到需要3个周期才能完成访问,IPC会下跌到20%~30%。100nm和180nm的访问延迟时间和现在的超深亚纳米工艺自然不同,但是分支预测速度与IPC的关系属于纯微结构特征,并不随着工艺的变化而变化。在22nm、14nm乃至未来的10nm上,分支预测速度的下滑导致的IPC下滑趋势理应是一致的。

  4.横跨多个基本块的取指,在分支预测研究多年之后,业界发现仅有快速而准确的分支预测也是不够的,首先来解释一下什么是基本块。基本块(Basicblock)是广泛使用于编译优化和微结构优化上的一个术语。从指令流中的第一条非分支指令开始,到第一条分支指令结束,为第一个基本块。从上一个基本块之后的第一条非分支指令开始到第一条分支指令结束,为又一个基本块,以此类推。分支指令是基本块的分界线,基本块是许多编译优化和微结构优化技术的操作范围。

  SPEC CPU上的一个关键统计数字是,整数指令基本块的平均长度在4~5条指令之间,也就是说每4~5条指令就会遇到一条分支指令。看上去这个数字和前文中提到的“大约四分之一到五分之一的指令是分支指令”对比来看只是换了一个说法,实则有着微妙而重要的不同。对于基本块末尾的分支指令,如果不发生跳转,对于取指令而言是喜闻乐见的。假设有这样一个指令序列:

  算术逻辑指令1、算术逻辑指令2、访存指令3、分支指令4、算术逻辑指令5、访存指令6、访存指令7、访存指令8

  分支跳转地址:

  算术逻辑指令7、算术逻辑指令8、访存指令9

  如果分支指令4不发生跳转,并且取指单元的分支预测器正确预测了这个不跳转的结果,取指单元看到的是就是从算术逻辑指令1开始到访存指令8为止一共8条指令的两个连续基本块,只要取指带宽足够,可以在一个周期里一次性读取8条指令送交译码。如果分支跳转,取指单元看到的指令流就是算术逻辑指令1,算术逻辑指令2,访存指令3,分支指令4共同形成的第一个基本块,以及算术逻辑指令7,算术逻辑指令8,访存指令9共同形成的第二个基本块,这两个基本块前后不连续。处理器的取指单元会在第一个周期读取第一个基本块中的四条指令,如果分支预测器能够进行单周期预测,在第一个周期接近末尾时,取指单元可以得到分支预测器的提醒,从下一个周期开始改变指令读取地址,读取第二个基本块,有效取指带宽就从8指令/1周期跌落到8指令/2周期。

  直到今天,笔者所知的多数工业级处理器的取指逻辑仍然恪守着前述做法,每一个周期的取指进行到第一个发生跳转的分支为止,否则进行到取指带宽上限。只有两个连续基本块接口处的分支指令不跳转,那么两个基本块中的指令才可以合并起来满足最大取指带宽。不幸的是,多数基本块结尾处的分支指令是偏向于跳转的,对早期SPECint部分benchmark的统计显示,每两个跳转分支之间的间隔大约为5~8条指令。如果不能够跨越跳转分支进行下一个基本块的取指,有效取指带宽就将被牢牢限制在这个区间上。

  Power8的阿喀琉斯之踵

  对照着前一节中提到的取指效率关键影响因素,我们来分析一下IBM Power8的整个微结构前端设计。首先来看看指令缓存一侧的供应能力。IBM的论文中介绍,Power8的每个核心具备32KB的一级指令缓存,每个周期可以读取8条指令,取指带宽为32Bytes。一级指令缓存为8路相联、16-bank的设计,如此大手笔的bank设计可以将8条指令同时读取,并将可能造成的访问冲突降到非常低的程度。此外,Power8的一级指令缓存也配备了指令预读取器,可以预取最多3个缓存块上地址连续的指令。只要一级指令高速缓存的预取和替换策略不出现设计失误,指令缓存一侧的指令供应能力应该是不成问题的。

  第二个可能出问题的地方在于其分支预测准确率。笔者初步分析的结论是,Power8的分支预测准确率可以力敌Intel,瓶颈也不在这里。截止目前为止,从ARM到AMD、IBM到Intel都使用混合型分支预测器(hybrid branch predictor),(有的资料上可能将其称为锦标赛分支预测器tournament branch predictor,这两个名词有时指代同一种预测器,但混合型分支预测器的定义包含但超出锦标赛分支预测器)。因为分支指令的类型分为条件分支、无条件分支、函数调用/返回,间接跳转等,每一种分支指令的特征都不尽相同,因此现代CPU高性能微结构对每种分支都使用不同的预测器进行预测。多种预测器的混合就构成了混合型分支预测器。其中,无条件分支使用无条件分支专属的历史信息记录表,函数调用/返回类型的分支指令使用返回栈预测器(return stack predictor)。无条件分支每次都一定跳转因此无需预测分支方向,只需要使用分支目标地址缓冲区(BTB)来保存目标地址即可。条件分支的情况则较为复杂,研究显示条件分支指令的跳转情况有时会出现一定的重复性规律,捕捉各个条件分支指令的跳转历史即可提供高准确率的预测,这部分历史信息保存在局部历史表中;但是有时条件分支指令的跳转情况和自身过去的历史无关,反而与周围相邻的一些分支指令的跳转情况有关,这部分的历史信息保存在全局历史表中。这些分支预测器基本上都来自于公开的学术研究成果,对于分支指令的特征有了十多年的透彻研究,已经几无秘密可言,因此各家的预测准确率更多地取决于自身的一些实现技巧。与预测准确率直接相关的最重要参数之一便是各个历史记录表的大小,笔者估计IBM Power8在这个参数上估计位于均势地位,分支预测准确率的问题不会很大。

  首先来看负责条件分支的部分。IBM Power8的锦标赛分支预测器使用了16K全局历史(GBHT)+16K局部历史(LBHT)+16K选择历史(GSEL)的豪华配置,对比Power7的16K+8K+8K的配置可以说是更上一层楼(龙芯最新一代的GS464E也使用了与Power8相同的规格),Intel使用的大小未公布,但可以谨慎乐观地认为在这一个点上Power8至少不会明显落后。

  再来看看负责函数调用/返回指令预测的部分。Power8的返回栈预测器并非如Intel一样每个线程独享一个,而是多个SMT线程共享。在单线程状态和两路SMT状态下,每个线程使用32项,在四路SMT状态下就只有16项,八路SMT状态下就只有8项。单线程模式下的32项容量应该说是不错的,Intel的返回栈则是自身16项容量,但并不意味着Intel落后。笔者见到的SPEC CPU上的返回栈预测器容量评估显示16项和32项容量差距很小,而且Intel还使用间接跳转预测器来作为容量满溢之后预测函数调用/返回的后备,使得返回栈预测器几乎可以说是“无限容量”。更为重要的是,在IBM的公开文档中未见提及使用返回栈修复技术,而只是简单地推测性压栈退栈。但返回栈修复这项技术对于返回栈的预测准确率来说至关重要,它旨在帮助返回栈消除由于错误的分支预测触发的后继函数调用/返回。1998年普林斯顿大学的评估显示,带修复机制的返回栈和不带修复机制的返回栈在预测准确率上有10%~60%的绝对值差别。Intel和AMD各自从Nehalem/Bulldozer时代开始就引入了返回栈修复,希望IBM和当年的DECAlpha一样,只是藏了一手没说而已。

  再来看看负责无条件分支、并且为所有分支指令提供快速目标地址的分支目标地址缓冲区(BTB)。Intel和AMD近年来的微结构都使用分支目标地址缓冲区来保存经过分支预测确认的分支目标地址,以尽量降低指令误取。IntelP4时代的官方数字是BTB可以保存4096个分支目标地址,但BTB容量做大以后带来的访问延迟问题不好解决。Nehalem转向了两级BTB设计,一级BTB访问速度快但容量小,二级BTB访问速度慢但容量大,但两级BTB的具体大小未见披露。随后Intel从SandyBridge开始又转回了单级压缩BTB的设计,据第三方测试,容量可能仍维持在4096项。AMD在Istanbul Bulldozer时代也采用了两级BTB设计,一级BTB保存512个分支目标地址,为4路组关联设计,二级BTB保存5120个分支目标地址,为5路组关联设计,一级BTB需要一个周期访问,二级则需要两个周期。这些都是中规中矩但保证不犯大错的设计,而IBM的做法就让人有些摸不着头脑了。

  在指出其潜在缺陷之前,还是要先说一说其设计上的亮点。Power8的一级指令缓存上使用了许多工业级架构上使用的经典技术路预测(way prediction),这项技术是上世纪90年代中期由MIPS率先应用,IBM将之换了一个马甲名,称为IE AD(Instruction Eff ective Address Directory,指令有效地址目录)。这项技术的初衷是缩短一级高速缓存的访问延迟,首先推测性地访问一整组存储单元中的单单一个纵列(亦即一个cac heset中的某一way),这样访问速度最快,如果发现访问的位置不对,再多花费一个周期的时间把整组的其他单元一起打开,总能寻找到要的那一个。这一项技术已经比较成熟,上世纪90年代的DEC Alpha使用这项技术时就能做到85%以上的推测准确率,一旦推测正确就可以缩短访问延迟,这是一项值得赞扬的取指令优化设计。但更重要的是,我们关注的分支目标地址缓冲区(BTB)在哪儿呢?

  第一个可以基本确认的缺陷出现在分支预测判断速度上。笔者在IBM的论文和框图中反复搜寻,均未找到分支目标地址缓冲区或者类似结构的存在。分支目标地址缓冲区的缺失可以说是灾难性的,每次碰到分支指令时都要等待分支预测器的预测结果才能进行取指,这意味着每次取指时一旦碰到跳转分支指令就要多消耗几个周期等待分支预测的结果和分支目标地址的重新计算。而早在上世纪80年代早期,相关的解决方案就已经出炉了,连早期的Cortex-A8都有采用,这个缓冲区会保存经过分支预测器确认的,一部分最常执行的分支指令目标地址,每次取指时不用等待分支预测器的新结果就可以按照过往预测结果继续取指,一本万利的买卖,各家谈论自己的微结构时都不会回避这一结构,为何IBM不见提及?如果没有分支目标地址缓冲区,那么分支预测的速度就会极大地影响前端的性能表现。

  分支目标地址缓冲区的问题尚且可以说存疑,有可能Power8是真用了,但就是在跟大家玩捉迷藏。而给予Power8的单线程性能确确实实最沉重一击的地方,就是其分支预测的预测速度和相关补全机制的缺失。

  下面这段话出自发表在IBM内部期刊上的Power8论文原文:

  “It takes three cycles to obtain the next fetch address when there is a taken branch, and for two of these cycles there is no fetch for the thread. However, in SMT mode, those two cycles will normally be allocated to other active threads, and thus not lost. If the fetched instructions do not contain any branch that is unconditional or predicted taken, the next sequential address is used for the next fetch for that thread and no fetch cycles are lost.”

  翻译:“如果遇到跳转分支,需要花费3个周期才能得到下一次取指的地址,其中两个周期,对于这个线程来说不会有取指动作进行。然而,在SMT模式下,这两个周期一般会被分配给其他活跃线程,所以(吞吐量,笔者补充)没有损失。如果取上来的指令没有包含任何无条件分支或者是预测跳转的分支,那么下一次读取的地址就是顺序递增的地址,不会浪费取指周期。”

  第一句话实际上是说Power8的分支预测需要3个周期才能输出结果,这与其他许多工业级微结构的设计是类似的,但是大家八仙过海各显神通,一般会加入大容量的分支目标地址缓冲区,或者使用大步进顺序预取指之类的方法补救,而Power8在此处简直是裸奔出场,在取到的8条指令中如果遇到哪怕一个跳转分支,那么在接下来2个周期的时间里就只等待分支预测结果,不再取指。笔者在Power8的论文中看到这句话的时候也是心里一惊,这一下就让Power8的前端单线程指令效率在遇到跳转分支时跌为8指令/3周期,几乎成了单线程2.66发射的处理器!

  这一处让人大跌眼镜的设计,与IBM的宽发射宣传多少有点不太相符。根据IBM的解释,Power8可以使用其他线程来填补取指空白,但是这样并无益于单线程性能。前文中提到,此前的统计分析表明早期SPECint的跳转分支相互间隔只有5~8条指令,难道是这一趋势在最近的测试程序中发生了反转吗?

  笔者找到了德克萨斯大学奥斯汀分校在2009年发表于SPEC国际性能评估研讨会上的数据。强制预测每个分支均跳转和强制预测每个分支均不跳转相比,每个分支均跳转的失败率更低,这意味着至少多于一半的分支指令仍然偏向于跳转,可见这一趋势并未发生变化,并且还有分析表明SPECint 2006的基本块长度比SPECint 2000还有所缩减,也就是说,如果Power8的前端设计真的如同其论文中所述,那么取指单元运行在单线程模式下时将会极大概率地停顿,甚至是每次取指都停顿。翻阅Power7的文档,笔者发现Power7也存在同样的问题,论文中声明分支预测需要3个周期才能完成,但由于加入了128项分支目标地址缓存(BTAC),可以缓解遇到跳转分支时带来的取指停顿,其前端框架图中也明确绘制了这一结构。先不说这个分支目标地址缓存容量是否寒酸的问题,起码该有的是有的,而Power8的微结构当中这一关键结构却莫名其妙地不见踪影,倒是Power8的分支预测器尺寸比Power7明显加大。

  让我们来看看竞争对手Intel是怎么处理这个问题的。从早期的奔腾4开始,Intel吸收并实现了一项被称为踪迹缓存(trace cache)的技术,实现了每个周期跨越多个基本块进行取指,跳转分支以及跟随其后的指令会在踪迹缓存的处理下自动塌缩并连接成一条无间断的指令流,舒舒服服地提供给乱序执行引擎。这一技术后来更演化为闻名天下的uopcache,不仅保存无间断指令流,而且保存的指令都是x86不定长指令翻译而来的定长精简指令。由此笔者认为,Power8的取指前端设计在这一点上大大落后于Intel,IBM确确实实在单线程设计上略逊一筹。

  侧重多线程性能的产物

  必须声明的是,尽管笔者不看好Power8的单线程性能和其多少有些畸形的前端设计,但笔者认为Power8仍然是一款非常强大、不容忽视的多线程服务器处理器。截至目前为止,Power8是现存的已量产设计中乱序多发射最激进、SMT宽度最宽的。当年的Alpha26464虽然在很多设计上可能超越Power8,但毕竟Alpha21464的设计从未真正完成过,也没有走入到量产上市的阶段。单线程性能与其乱序多发射的激进设计对比,出现现在这样出乎意料的孱弱,是Power8的设计团队有意权衡、侧重SMT多线程性能的结果。Power8对其取指问题的解释是,虽然单线程取指会引发额外停顿,但是每个核心支持8个线程,此时其他线程继续保持取指,就算每个单一线程轮流停顿,也可以维持住总体的吞吐量。由此可见,Power8是有意侧重SMT性能的处理器,并不适合进行侧重单线程性能的任务。文中谈及的微结构问题对于强大的IBM来说并非技术上不可解决,毕竟已经有多家成熟公开的方案,Power8的单线程性能问题最终应该归结到路线的选择和设计的取舍抛弃了单线程性能的缘故。

  文、图/黄博文

……
关注读览天下微信, 100万篇深度好文, 等你来看……
阅读完整内容请先登录:
帐户:
密码: