嵌入式C语言自我修养:从芯片、编译器到操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.5 CPU性能提升:流水线

流水线是工业社会化大生产背景下的产物。亚当·斯密在他的《国富论》中曾经描述这样一个场景:制作一枚回形针一般需要18个步骤,工厂里的工人平均每天也只能做100枚回形针。后来改进工艺,把制针流程分成18道工艺,然后让这10名工人平均每人负责1~2道工艺,最后这10名工人每天可以制造出48 000枚回形针,生产效率整整提高了几十倍!

在农业社会做一部手机,需要的是工匠、手艺人,就像故宫里修文物的那些匠人一样,是需要拜师学艺、慢慢摸索、逐步精进的:从电路焊接、手机组装、质检、贴膜、包装都是一个人,什么都要学。手艺人慢工出细活,但生产成本很高。到了工业化社会就不一样了:大家分工合作,将做手机这个复杂手艺拆分为多个简单步骤,每个人负责一个步骤,多个步骤构成流水线。流水线上的每个工种经过练习和培训,都可以很快上手,每个人都做自己最擅长的,进而可以大大提高整个流水线的生产效率。

做一部手机,焊接电路、组装成品这一步流程一般需要8分钟,测试检验需要4分钟,贴膜包装成盒需要4分钟,总共需要16分钟。如果有3个工人,每个人都单独去做手机,每16分钟可以生产3部手机。一个新员工从进厂开始,要培训学习三个月才能掌握所有的技能,才能上岗。如果引入生产流水线就不一样了,每个人只负责一个工序,如图2-33所示,小A只负责焊接电路、组装手机,小B只负责质检,小C只负责贴膜包装。每个人进厂培训10天就可以快速上手了,流水线对工人的技能要求大大降低,而且随着时间的推移,每个人会对自己负责的工序越来越熟练,每道工序需要的时间也会大大减少:小A焊接电路越来越顺手,花费时间从原来的8分钟缩减为4分钟;小B的质量检验练得炉火纯青,做完整个流程只需要2分钟;小C的贴膜技术也越来越高了,从贴膜到包装2分钟完成。每16分钟,小A可以焊接4块电路板,整个流水线可以生产出4部手机,产能整整提升了33.33%!老板高兴,小A高兴,小B和小C高兴,因为每做2分钟,他们还可以休息2分钟,岂不乐哉。

图2-33 手机生产流水线

看到这里可能有人抬杠了:你这么算是不对的,每道工序所用的时间都变为原来的一半,怎么可能做得到?其实要做到不难的,只要工序拆解得合理,容易上手,再加上足够时间的机械重复,很多人都可以做得到。

2.5.1 流水线工作原理

一条指令的执行一般要经过取指令、翻译指令、执行指令3个基本流程。CPU内部的电路分为不同的单元:取指单元、译码单元、执行单元等,指令的执行也是按照流水线工序一步一步执行的。如图2-34所示,我们假设每一个步骤的执行时间都是一个时钟周期,那么一条指令执行完需要3个时钟周期。

CPU执行指令的3个时钟周期里,取指单元只在第一个时钟周期里工作,其余两个时钟周期都处于空闲状态,其他两个执行单元也是如此。这样做效率太低了,消费者无法接受,老板更无法接受。解决方法就是引入流水线,让流水线上的每一颗螺丝钉都马不停蹄地运转起来。

如图2-35所示,引入流水线后,除了刚开始的第一个时钟周期大家可以偷懒,其余的时间都不能闲着:从第二个时钟周期开始,当译码单元在翻译指令1时,取指单元也不能闲着,要接着去取指令2。从第三个时钟周期开始,当执行单元执行指令1时,译码单元也不能闲着,要接着去翻译指令2,而取指单元要去取指令3。从第四个时钟周期开始,每个电路单元都会进入满负荷工作状态,像富士康工厂里的流水线一样,源源不断地执行一条条指令。

图2-34 ARM处理器的三级流水线

图2-35 处理器指令的流水线执行过程

引入流水线后,虽然每一条指令的执行流程和时间不变,还是需要3个时钟周期,但是从整条流水线的输出来看,差不多平均每个时钟周期就能执行一条指令。原来执行一条指令需要3个时钟周期,引入流水线后平均只需要1个时钟周期,CPU性能提升了不少。

流水线的本质其实就是拿空间换时间。将每条指令分解为多步执行,指令的每一小步都有独立的电路单元来执行,并让不同指令的各小步操作重叠,通过多条指令的并行执行,加快程序的整体运行效率。

CPU内部的流水线如此,工厂里的手机生产流水线也是如此,通过不断地往流水线增加人手来提高流水线的生产效率,也就是增加流水线的吞吐率。

2.5.2 超流水线技术

想知道什么是超流水线,让我们再回到工厂。

在手机生产流水线上,由于小A的工作效率不高,每焊接组装一步手机需要4分钟,导致流水线上生产一部手机也得需要4分钟。小A拖累了整条生产线的生产效率,老板很生气,后果很严重,小A没干到一个月就被老板炒掉了。接下来的几个月里,陆陆续续来了不少人,都想挑战一下这份工作,可惜干得还不如小A。老板招不到人,感觉又错怪了小A,于是决定升级生产线,并在加薪的承诺下重新召回了小A。

经过分析,老板找到了生产线的瓶颈:流水线上的每道工序都需要2分钟,只有小A这道工序需要4分钟,老板发现自己错怪了小A,这不是小A的原因,是因为这道工序太复杂。老板把这道工序拆解为两道工序:焊接电路和组装手机。如图2-36所示,焊接电路仍由小A负责,把电路板、显示屏、手机外壳组装成手机这道工序则由新员工小D负责。生产流水线经过优化后,小A焊接电路只需要2分钟,小D组装每部手机也只需要2分钟,生产每部手机的时间也由原来的4分钟缩减为2分钟。现在每16分钟可以生产8部手机,生产效率是原来的2倍!生产流水线的瓶颈解决了。

图2-36 改进后的手机生产流水线

和手机生产流水线类似,优化CPU流水线也是提升CPU性能的有效手段。流水线存在木桶短板效应,我们只需要找出CPU流水线中的性能瓶颈,即耗时最长的那道工序,对其再进行细分,拆解为更多的工序就可以了。每一道工序都称为流水线中的一级,流水线越深,每一道工序的执行时间就会变得越小,处理器的时钟周期就可以更短,CPU的工作频率就可以更高,进而可以提升CPU的性能,提高工作效率。

在手机生产流水线上,耗时最长的那道工序决定了整条流水线的吞吐率。CPU内部的流水线也是如此,流水线中耗时最长的那道工序单元的执行时间(即时间延迟)决定了CPU流水线的性能。CPU流水线中的每一级电路单元一般都是由组合逻辑电路和寄存器组成的,组合逻辑电路用来执行本道工序的逻辑运算,寄存器用来保存运算输出结果,并作为下一道工序的输入。

流水线通过减少每一道工序的耗费时间来提升整条流水线的效率。在CPU内部也是如此,CPU内部的数字电路是靠时钟驱动来工作的,既然每条指令的执行时钟周期数不变,即执行每条指令都需要3个时钟周期,但是我们可以通过缩短一个时钟周期的时间来提升效率,即减少每条指令所耗费的时间。一个时钟周期的时间变短,CPU主频也就相应提升,影响时钟周期时间长短的一个关键的制约因素就是CPU内部每一个工序执行单元的耗费时间。虽说电信号在电路中的传播时间很快,可以接近光速,但是经过成千上万个晶体管,不停地信号翻转,还是会带来一定的时间延迟,这个时间延迟我们可以看作电路单元的执行时间。以图2-37为例,如果每个执行单元的时间延迟都是1+0.5=1.5ns,那么你的时钟周期至少也得2ns,否则电路就会工作异常。如果驱动CPU工作的时钟周期是2ns,那么CPU的主频就是500MHz。现在的CPU流水线深度可以做到10级以上,流水线的每一级时间延迟都可以做到皮秒级别,驱动CPU工作的时钟周期可以做到更短,可以把CPU的主频飙到5GHz以上。

图2-37 流水线中每道工序的耗时

我们把5级以上的流水线称为超流水线结构。为了提升CPU主频,高性能的处理器一般都会采用这种超流水线结构。Intel的i7处理器有16级流水线,AMD的速龙64系列CPU有20级流水线,史上具有最长流水线的处理器是Intel的第三代奔腾四处理器,有31级流水线。

要想提升CPU的主频,本质在于减少流水线中每一级流水的执行时间,消除木桶短板效应。解决方法有三个:一是优化流水线中各级流水线的性能,受限于当前集成电路的设计水平,这一步最难;二是依靠半导体制造工艺,工艺制程越先进,芯片面积就会越小,发热也就越小,就更容易提升主频;三是不断地增加流水线深度,流水线越深,流水线中的各级时间延迟就可以做得越小,就更容易提高主频。

流水线是否越深越好呢?不一定。流水线的本质是拿空间换时间,流水线越深,电路会越复杂,就需要更多的组合逻辑电路和寄存器,芯片面积也就越大,功耗也就随之上升了。用功耗增长换来性能提升,在PC机和服务器上还行,但对于很多靠电池供电的移动设备的处理器来说就无法接受了,CPU设计人员需要在性能和功耗之间做一个很好的平衡。

流水线越深,就越能提升性能吗?也不一定。流水线是靠指令的并行来提升性能的,第一条指令还没有执行完,下面的第二条指令就开始取指、译码了。执行的程序指令如果是顺序结构的,没有中断或跳转,流水线确实可以提高执行效率。但是当程序指令中存在跳转、分支结构时,下面预取的指令可能就要全部丢掉了,需要到跳转的地方重新取指令执行。

在上面的汇编程序中,BEQ是一个条件跳转指令,根据寄存器R1和R2的值是否相等,跳转到不同的地方执行。正常情况下,当执行BEQ指令时,下面的ADD指令就已经被预取和译码了,如果程序没有跳转,则会接着继续往下执行。但是当BEQ跳转到here标签处执行时,流水线中已经预取的ADD指令就无效了,要全部丢弃掉,然后重新到here标签处取SUB指令,流水线才能接着继续执行。

流水线越深,一旦预取指令失败,浪费和损失就会越严重,因为流水线中预取的几十条指令可能都要丢弃掉,此时流水线就发生了停顿,无法按照预期继续执行,这种情况我们一般称为流水线冒险(hazard)。

2.5.3 流水线冒险

引起流水线冒险的原因有很多种,根据类型不同,我们一般分为3种。

● 结构冒险:所需的硬件正在为前面的指令工作。

● 数据冒险:当前指令需要前面指令的运算数据才能执行。

● 控制冒险:需根据之前指令的执行结果决定下一步的行为。

结构冒险很好理解,如果多条指令都用相同的硬件资源,如内存单元、寄存器等,就会发生冲突。如下面的汇编程序。

上面这两条指令执行时都需要访问寄存器R1,但是这两条指令之间没有依赖关系,不需要数据的传送,仅仅在使用的硬件资源上发生了冲突,这种冲突我们就称为结构冒险。解决结构冒险的方法很简单,我们直接对冲突的寄存器进行重命名就可以了。这种操作可以通过编译器静态实现,也可以通过硬件动态完成,如图2-38所示,我们在流水线中加入寄存器重命名单元就可以了。

图2-38 流水线中的重命名单元

通过硬件电路对寄存器重命名后,代码就变成了下面的样子,将SUB指令中的R1寄存器重命名为R5,结构冒险解决。

数据冒险指当前指令的执行需要上一条指令的运算结构,上一条指令没有运行结束,当前指令就无法运行,只能暂停执行。如下面的程序代码。

第二条SUB指令,要等待第一条ADD指令运行结束,将运算结果写回寄存器R2后才能执行。现在的经典CPU流水线一般分为5级:取指、译码、执行、访问内存、写回。也就是说,指令执行结束后还要把运算结果写回寄存器,然后下一条指令才可以到这个寄存器取数据。要解决流水线的数据冒险,方法有很多,如使用“operand forwarding”技术,当ADD指令运行结束后,不再执行后面的回写寄存器操作,而是直接使用运算结果。第二个解决方法是在ADD和SUB指令中间插入空指令,即pipeline bubble,暂缓SUB指令的执行,等ADD指令将运算结果写回寄存器R2后再执行就可以了。

如图2-39所示,为了防止数据冒险,我们在时钟周期2和时钟周期3内,添加了两个空指令,让流水线暂时停顿(stall),产生空泡(bubble)。在第5个时钟周期,ADD指令执行结束,并将运算结果写回寄存器R2之后,SUB指令才在第6个时钟周期继续执行。通过这种填充空指令的方式,SUB指令虽然延缓了2个时钟周期执行,但总比把后面已经预取的几十条指令全部丢掉强,尤其是当流水线很深时,这种方式很划算,你值得拥有。

图2-39 在流水线中添加空指令

控制冒险也是如此,当我们执行BEQ这样的条件判断指令,无法确定接下来要执行什么,无法确定到哪里取指令时,也可以采取图2-39所示的解决方法,插入几个空指令,等BEQ执行结束后再去取指令就可以了。

2.5.4 分支预测

条件跳转引起的控制冒险虽然也可以通过在流水中插入空泡来避免,但是当流水线很深时,需要插入更多的空泡。以一个20级深度的流水线为例,如果一条指令需要上一条指令执行结束才去执行,则需要在这两条指令之间插入19个空泡,相当于流水线要暂停19个时钟周期,这是CPU无法接受的。

如图2-40所示,为了避免这种情况发生,现在的CPU流水线在取指和译码时,都要对跳转指令进行分析,预测可能执行的分支和路径,防止预取错误的分支路径指令给流水线带来停顿。

图2-40 在流水线中添加分支预测单元

根据工作方式的不同,分支预测可分为静态预测和动态预测。静态预测在程序编译时通过编译器进行分支预测,这种预测方式对于循环程序最有效,它可以根据你的循环边界反复取指令。而对于跳转分支,静态预测就比较简单粗暴了,一般都是默认不跳转,按照顺序执行。我们在编写有跳转分支的程序时,要记得把大概率执行的代码分支放在前面,这样可以明显提高代码的执行效率。如下面的分支跳转代码,写得就不好。

执行上面的代码,不用纠结,99.99%的概率会跳转到else分支执行。如果我们在一个20级流水线的处理器上运行这个程序,一旦预测失败,就会浪费很多时钟周期去冲刷前面预取错误的流水线,从而大大降低程序的运行效率。我们可以稍微优化一下,将大概率执行的分支放到前面,就可以大概率避免流水线冲刷和停顿。

动态预测则指在程序运行时进行预测。不同的软件、不同的程序分支行为,我们可以采取不同的算法去提高预测的准确率,如我们可以根据程序的历史执行路径信息来预测本次跳转的行为,常见的动态预测方式有1-bit动态预测、n-bit动态预测、下一行预测、双模态预测、局部分支预测、全局分支预测、融合分支预测、循环预测等。随着大量新的应用软件的出现,为了应对新的程序逻辑行为,分支预测器也做得越来越复杂,占用的芯片面积也越来越大。在CPU内部,除了Cache,就数分支预测器的电路版图最大。

分支预测技术是提高CPU性能的一项关键技术,其本质就是去除指令之间的相关性,让程序更高效运行。一个CPU性能高不高,不仅在于你的流水线有多深、主频有多高、Cache有多大,还和分支预测技术息息相关。一个分支预测器好不好,我们可以从两个方面来衡量:分支判断速度和预测准确率。目前分支预测技术可以达到95%的预测准确率,然而技术进化之路永未停止,分支预测技术一直在随着计算机的发展不断更新迭代。

2.5.5 乱序执行

我们编写的代码指令序列按照顺序依次存储在RAM中。当程序执行时,PC指针会自动到RAM中去取,然后CPU按照顺序一条一条地依次执行,这种执行方式称为顺序执行(in order)。当这些指令前后有数据依赖关系时,就会产生数据冒险,我们可以通过在指令序列之间添加空指令,让流水线暂时停顿来避免流水线中预期的指令被冲刷掉。除此之外,我们还可以通过乱序执行(out of order)来避免流水线冲突。

造成流水线冲突的根源在于指令之间存在相关性:前后指令之间要么产生数据冒险,要么产生结构冒险。我们可以通过重排指令的执行顺序,而不是被动地填充空指令来去掉这种依赖。

在上面的程序中,第二条SUB指令要使用第一条指令的运算结果,要等到第一条ADD指令运行结束后才能执行,于是就产生了数据冒险。我们可以通过在流水线中插入2个空指令来避免。

通过暂停流水线2个时钟周期,我们避免了流水线的冲突。当指令序列中存在依赖关系的指令很多时,就需要在流水线中不停地插入空指令,造成流水线频繁地停顿,进而影响程序的运行效率。为了避免这种情况发生,我们可以将指令执行顺序重排,乱序执行。

因为指令3、指令4和指令1之间不存在相关性,因此我们可将它们放到前面执行。等再次执行到指令2时,指令1已经执行结束,不存在数据冒险,此时我们就不需要在流水线中添加空指令了,CPU流水线满负载运行,效率提升。

支持乱序执行的CPU处理器,其内部一般都会有专门的乱序执行逻辑电路,该控制电路会对当前指令的执行序列进行分析,看能否提前执行。如整型计算、浮点型计算会使用不同的计算单元,同时执行这些指令并不会发生冲突。CPU分析这些不相关的指令,并结合各电路单元的空闲状态综合判断,将能提前执行的指令进行重排,发送到相应的电路单元执行。

2.5.6 SIMD和NEON

一条指令一般由操作码和操作数构成,不同类型的指令,其操作数的数量可能不一样。以加法指令为例,它有2个操作数:加数1和加数2。当译码电路译码成功并开始执行ADD指令时,CPU的控制单元会首先到内存中取数据,将操作数送到算术逻辑单元中,取数据的方法有两种:第一种是先取第一个操作数,然后访问内存读取第二个操作数,最后才能进行求和计算。这种数据操作类型一般称为单指令单数据(Single Instruction Single Data,SISD);第二种方法是几个执行部件同时访问内存,一次性读取所有的操作数,这种数据操作类型称为单指令多数据(Single Instruction Multiple Data,SIMD)。毫无疑问,SIMD通过单指令多数据运算,帮助CPU实现了数据并行访问,SIMD型的CPU执行效率更高。

随着多媒体技术的发展,计算机对图像、视频、音频等数据的处理需求大增,SIMD特别适合这种数据密集型计算:一条指令可以同时处理多个数据(音频或一帧图像数据)。为了满足这种需求,从1996年起,X86架构的处理器就开始不断地扩展这种SIMD指令集。

多媒体扩展(MultiMedia eXtensions,MMX)指令集是X86处理器为音视频、图像处理专门设计的57条SIMD多媒体指令集。MMX将64位寄存器当作2个32位或8个8位寄存器来用,用来处理整型计算。这些寄存器并不是为MMX单独设计的,而是借用浮点运算的寄存器进行计算的,因此MMX指令和浮点运算不能同时工作。

SSE(Internet Streaming SIMD Extensions)指令集是Intel在奔腾三处理器中对MMX进行扩展的指令集。SSE和MMX相比,不再占用浮点运算单元的寄存器,它有自己单独的128位寄存器,一次可处理128位数据。后来AVX(Advanced Vector Extensions)指令集将128位的寄存器扩展到256位,支持矢量计算,并全面兼容SSE及后续的扩展指令集系列SSE2/SSE3/SSE4。短短几年后,AMD也不甘示弱,发布了3DNow!和SSE5指令集。3DNow!指令集基于Intel的MMX指令集进行扩展,不仅支持并行整型计算、并行浮点型计算,还可以混合操作整型和浮点型计算,不需要上下文来回切换,执行效率更高。

FMA(Fused-Multiply-Add)指令集,基于AVX指令集进行扩展,融合了加法和乘法,又称为积和熔加计算,可通过单一指令执行多次重复计算,简化了程序,比AVX更加高效,以适应绘图、渲染、立体音效等一些更复杂的多媒体运算。现在无论是Intel还是AMD,新版的CPU微架构都开始支持FMA指令集。

随着音乐播放、拍照、直播、小视频等多媒体需求在移动设备上的爆发,ARM架构的处理器也开始慢慢支持和扩展SIMD指令集。如图2-41所示,NEON是适用于Cortex-A和Cortex-R52系列处理器的一种128位的SIMD扩展指令集。早期的浮点运算已不能满足需求,ARM从ARM V7指令集开始引入NEON多媒体SMID指令,通过向量化运算,更好地支持音视频编解码、计算机视觉AR/VR、游戏渲染、机器学习、深度学习等需要大量复杂计算的新应用场景。

图2-41 ARM处理器中的SIMD指令执行单元

2.5.7 单发射和多发射

SIMD指令可以用一条指令来处理多个数据,其实就是通过数据并行来提高执行效率的。为应对日益复杂的多媒体计算需求,X86和ARM处理器都分别扩展了SIMD指令集,这些扩展的SIMD指令和其他指令一样,在流水线上也是串行执行的。流水线通过前面的各种优化手段来提高吞吐率,其实就是通过提升处理器主频来提高运行效率。CPU的主频提升了,但处理器在每个时钟周期能执行的指令个数仍是不变的:每个时钟周期只能从存储器取一条指令,每个时钟周期也只能执行一条指令,这种处理器一般叫作单发射处理器。

多发射处理器在一个时钟周期内可以执行多条指令。处理器内部一般有多个执行单元,如算术逻辑单元(ALU)、乘法器、浮点运算单元(FPU)等,每个时钟周期内仅有一个执行单元在工作,其他执行单元都闲着,甜豆浆咸豆浆,喝一碗倒一碗,这是多么的浪费啊!双发射处理器可以在一个时钟周期内同时分发(dispatch)多条指令到不同的执行单元运行,让CPU同时执行不同的计算(加法、乘法、浮点运算等),从而达到指令级的并行。一个双发射处理器每个时钟周期理论上最多可执行2条指令,一个四发射处理器每个时钟周期理论上最多可以执行4条指令。双发处理器的流水线如图2-42所示。

图2-42 双发射处理器的流水线

根据实现方式的不同,多发射处理器又可分为静态发射和动态发射。静态发射指在编译阶段将可以并行执行的指令打包,合并到一个64位的长指令中。在打包过程中,若找不到可以并行的指令配对,则用空指令NOP补充。这种实现方式称为超长指令集架构(Very Long Instruction Word,VLIW)。如下面的汇编指令,带有||的指令表示这两条指令要在一个时钟周期里同时执行。

VLIW实现简单,不需要额外的硬件,通过编译器在编译阶段就可以完成指令的并行。早期的汇编语言不支持指令的并行化执行声明,随着处理器不断地迭代更新,为了保证指令集的兼容性,现在的处理器,如X86、ARM等都采用SuperScalar结构。采用SuperScalar结构的处理器又叫超标量处理器,如图2-43所示,这种处理器在多发射的实现过程中会增加额外的取指单元、译码单元、逻辑控制单元等硬件电路。在指令运行时,将串行的指令序列转换为并行的指令序列,分发到不同的执行单元去执行,通过指令的动态并行化来提升CPU的性能。

图2-43 超标量处理器的动态发射

大家不要把乱序执行和SuperScalar弄混淆了,两者不是一回事。乱序执行是串行执行指令,只不过调整了指令的执行顺序而已,而SuperScalar则是并行执行多条指令。两者在一个处理器中是可以共存的:一个处理器可以是双发射、顺序执行的,也可以是双发射、乱序执行的;可以是单发射、乱序执行的,当然也可以是单发射、顺序执行的。超标量处理器通过增加电路逻辑将指令并行化来提升性能,其代价是增大了芯片的面积和功耗。不同的处理器,根据自己的市场定位,可以灵活搭配合适的架构:是追求低功耗,还是追求高性能,还是追求性能和功耗的相对平衡,总能做出一道适合你的菜。

VLIW和SuperScalar分别从编译器和硬件上实现了指令的并行化,各有各的优势和局限性:VLIW虽然实现简单,但由于兼容性问题,不支持目前主流的X86、ARM处理器;而采用SuperScalar结构的处理器,完全依赖流水线硬件去动态识别可并行执行的指令,并分发到对应的执行单元执行,不仅大大增加了硬件电路的复杂性,而且也存在极限。学者和工业界一致认为,同时执行8条指令将是SuperScalar结构的极限。

现在新架构的处理器没有指令集兼容的历史包袱,一般会采用显式并行指令计算(Explicitly Parallel Instruction Computing,EPIC)的指令集结构。EPIC结合了VLIW和SuperScalar的优点,允许处理器根据编译器的调度并行执行指令而不增加硬件的复杂性。EPIC的实现原理也很简单,就是在指令中使用3个比特位来表示相邻的两条指令有没有相关性、当前指令要不要等上一条指令运行结束后才能执行。程序在运行时,流水线根据指令中的这些信息可以很轻松地实现指令的并行化和分发工作。EPIC大大简化了CPU硬件逻辑电路的设计,1997年,Intel和HP联合开发的纯64位的安腾(Itanium)处理器就采用了EPIC结构。