极客时间已完结课程限时免费阅读

26 | Superscalar和VLIW:如何让CPU的吞吐率超过1?

26 | Superscalar和VLIW:如何让CPU的吞吐率超过1?-极客时间

26 | Superscalar和VLIW:如何让CPU的吞吐率超过1?

讲述:徐文浩

时长12:39大小10.14M

到今天为止,专栏已经过半了。过去的 20 多讲里,我给你讲的内容,很多都是围绕着怎么提升 CPU 的性能这个问题展开的。
我们先回顾一下第 4 讲,不知道你是否还记得这个公式:
程序的 CPU 执行时间 = 指令数 × CPI × Clock Cycle Time
这个公式里,有一个叫 CPI 的指标。我们知道,CPI 的倒数,又叫作 IPC(Instruction Per Clock),也就是一个时钟周期里面能够执行的指令数,代表了 CPU 的吞吐率。那么,这个指标,放在我们前面几节反复优化流水线架构的 CPU 里,能达到多少呢?
答案是,最佳情况下,IPC 也只能到 1。因为无论做了哪些流水线层面的优化,即使做到了指令执行层面的乱序执行,CPU 仍然只能在一个时钟周期里面,取一条指令。
这说明,无论指令后续能优化得多好,一个时钟周期也只能执行完这样一条指令,CPI 只能是 1。但是,我们现在用的 Intel CPU 或者 ARM 的 CPU,一般的 CPI 都能做到 2 以上,这是怎么做到的呢?
今天,我们就一起来看看,现代 CPU 都使用了什么“黑科技”。

多发射与超标量:同一时间执行的两条指令

之前讲 CPU 的硬件组成的时候,我们把所有算术和逻辑运算都抽象出来,变成了一个 ALU 这样的“黑盒子”。你应该还记得第 13 讲到第 16 讲,关于加法器、乘法器、乃至浮点数计算的部分,其实整数的计算和浮点数的计算过程差异还是不小的。实际上,整数和浮点数计算的电路,在 CPU 层面也是分开的。
一直到 80386,我们的 CPU 都是没有专门的浮点数计算的电路的。当时的浮点数计算,都是用软件进行模拟的。所以,在 80386 时代,Intel 给 386 配了单独的 387 芯片,专门用来做浮点数运算。那个时候,你买 386 芯片的话,会有 386sx 和 386dx 这两种芯片可以选择。386dx 就是带了 387 浮点数计算芯片的,而 sx 就是不带浮点数计算芯片的。
其实,我们现在用的 Intel CPU 芯片也是一样的。虽然浮点数计算已经变成 CPU 里的一部分,但并不是所有计算功能都在一个 ALU 里面,真实的情况是,我们会有多个 ALU。这也是为什么,在第 24 讲讲乱序执行的时候,你会看到,其实指令的执行阶段,是由很多个功能单元(FU)并行(Parallel)进行的。
不过,在指令乱序执行的过程中,我们的取指令(IF)和指令译码(ID)部分并不是并行进行的。
既然指令的执行层面可以并行进行,为什么取指令和指令译码不行呢?如果想要实现并行,该怎么办呢?
其实只要我们把取指令和指令译码,也一样通过增加硬件的方式,并行进行就好了。我们可以一次性从内存里面取出多条指令,然后分发给多个并行的指令译码器,进行译码,然后对应交给不同的功能单元去处理。这样,我们在一个时钟周期里,能够完成的指令就不只一条了。IPC 也就能做到大于 1 了。
这种 CPU 设计,我们叫作多发射(Mulitple Issue)和超标量(Superscalar)。
什么叫多发射呢?这个词听起来很抽象,其实它意思就是说,我们同一个时间,可能会同时把多条指令发射(Issue)到不同的译码器或者后续处理的流水线中去。
在超标量的 CPU 里面,有很多条并行的流水线,而不是只有一条流水线。“超标量“这个词是说,本来我们在一个时钟周期里面,只能执行一个标量(Scalar)的运算。在多发射的情况下,我们就能够超越这个限制,同时进行多次计算。
你可以看我画的这个超标量设计的流水线示意图。仔细看,你应该能看到一个有意思的现象,每一个功能单元的流水线的长度是不同的。事实上,不同的功能单元的流水线长度本来就不一样。我们平时所说的 14 级流水线,指的通常是进行整数计算指令的流水线长度。如果是浮点数运算,实际的流水线长度则会更长一些。

Intel 的失败之作:安腾的超长指令字设计

无论是之前几讲里讲的乱序执行,还是现在更进一步的超标量技术,在实际的硬件层面,其实实施起来都挺麻烦的。这是因为,在乱序执行和超标量的体系里面,我们的 CPU 要解决依赖冲突的问题。这也就是前面几讲我们讲的冒险问题。
CPU 需要在指令执行之前,去判断指令之间是否有依赖关系。如果有对应的依赖关系,指令就不能分发到执行阶段。因为这样,上面我们所说的超标量 CPU 的多发射功能,又被称为动态多发射处理器。这些对于依赖关系的检测,都会使得我们的 CPU 电路变得更加复杂。
于是,计算机科学家和工程师们就又有了一个大胆的想法。我们能不能不把分析和解决依赖关系的事情,放在硬件里面,而是放到软件里面来干呢?
如果你还记得的话,我在第 4 讲也讲过,要想优化 CPU 的执行时间,关键就是拆解这个公式:
程序的 CPU 执行时间 = 指令数 × CPI × Clock Cycle Time
当时我们说过,这个公式里面,我们可以通过改进编译器来优化指令数这个指标。那接下来,我们就来看看一个非常大胆的 CPU 设计想法,叫作超长指令字设计(Very Long Instruction Word,VLIW)。这个设计呢,不仅想让编译器来优化指令数,还想直接通过编译器,来优化 CPI。
围绕着这个设计的,是 Intel 一个著名的“史诗级”失败,也就是著名的 IA-64 架构的安腾(Itanium)处理器。只不过,这一次,责任不全在 Intel,还要拉上可以称之为硅谷起源的另一家公司,也就是惠普。
之所以称为“史诗”级失败,这个说法来源于惠普最早给这个架构取的名字,显式并发指令运算(Explicitly Parallel Instruction Computer),这个名字的缩写 EPIC,正好是“史诗”的意思。
好巧不巧,安腾处理器和和我之前给你介绍过的 Pentium 4 一样,在市场上是一个失败的产品。在经历了 12 年之久的设计研发之后,安腾一代只卖出了几千套。而安腾二代,在从 2002 年开始反复挣扎了 16 年之后,最终在 2018 年被 Intel 宣告放弃,退出了市场。自此,世上再也没有这个“史诗”服务器了。
那么,我们就来看看,这个超长指令字的安腾处理器是怎么回事儿。
在乱序执行和超标量的 CPU 架构里,指令的前后依赖关系,是由 CPU 内部的硬件电路来检测的。而到了超长指令字的架构里面,这个工作交给了编译器这个软件。
我从专栏第 5 讲开始,就给你看了不少 C 代码到汇编代码和机器代码的对照。编译器在这个过程中,其实也能够知道前后数据的依赖。于是,我们可以让编译器把没有依赖关系的代码位置进行交换。然后,再把多条连续的指令打包成一个指令包。安腾的 CPU 就是把 3 条指令变成一个指令包。
CPU 在运行的时候,不再是取一条指令,而是取出一个指令包。然后,译码解析整个指令包,解析出 3 条指令直接并行运行。可以看到,使用超长指令字架构的 CPU,同样是采用流水线架构的。也就是说,一组(Group)指令,仍然要经历多个时钟周期。同样的,下一组指令并不是等上一组指令执行完成之后再执行,而是在上一组指令的指令译码阶段,就开始取指令了。
值得注意的一点是,流水线停顿这件事情在超长指令字里面,很多时候也是由编译器来做的。除了停下整个处理器流水线,超长指令字的 CPU 不能在某个时钟周期停顿一下,等待前面依赖的操作执行完成。编译器需要在适当的位置插入 NOP 操作,直接在编译出来的机器码里面,就把流水线停顿这个事情在软件层面就安排妥当。
虽然安腾的设想很美好,Intel 也曾经希望能够让安腾架构成为替代 x86 的新一代架构,但是最终安腾还是在前前后后折腾将近 30 年后失败了。2018 年,Intel 宣告安腾 9500 会在 2021 年停止供货。
安腾失败的原因有很多,其中有一个重要的原因就是“向前兼容”。
一方面,安腾处理器的指令集和 x86 是不同的。这就意味着,原来 x86 上的所有程序是没有办法在安腾上运行的,而需要通过编译器重新编译才行。
另一方面,安腾处理器的 VLIW 架构决定了,如果安腾需要提升并行度,就需要增加一个指令包里包含的指令数量,比方说从 3 个变成 6 个。一旦这么做了,虽然同样是 VLIW 架构,同样指令集的安腾 CPU,程序也需要重新编译。因为原来编译器判断的依赖关系是在 3 个指令以及由 3 个指令组成的指令包之间,现在要变成 6 个指令和 6 个指令组成的指令包。编译器需要重新编译,交换指令顺序以及 NOP 操作,才能满足条件。甚至,我们需要重新来写编译器,才能让程序在新的 CPU 上跑起来。
于是,安腾就变成了一个既不容易向前兼容,又不容易向后兼容的 CPU。那么,它的失败也就不足为奇了。
可以看到,技术思路上的先进想法,在实际的业界应用上会遇到更多具体的实践考验。无论是指令集向前兼容性,还是对应 CPU 未来的扩展,在设计的时候,都需要更多地去考虑实践因素。

总结延伸

这一讲里,我和你一起向 CPU 的性能发起了一个新的挑战:让 CPU 的吞吐率,也就是 IPC 能够超过 1。
我先是为你介绍了超标量,也就是 Superscalar 这个方法。超标量可以让 CPU 不仅在指令执行阶段是并行的,在取指令和指令译码的时候,也是并行的。通过超标量技术,可以使得你所使用的 CPU 的 IPC 超过 1。
在 Intel 的 x86 的 CPU 里,从 Pentium 时代,第一次开始引入超标量技术,整个 CPU 的性能上了一个台阶。对应的技术,一直沿用到了现在。超标量技术和你之前看到的其他流水线技术一样,依赖于在硬件层面,能够检测到对应的指令的先后依赖关系,解决“冒险”问题。所以,它也使得 CPU 的电路变得更复杂了。
因为这些复杂性,惠普和 Intel 又共同推出了著名的安腾处理器。通过在编译器层面,直接分析出指令的前后依赖关系。于是,硬件在代码编译之后,就可以直接拿到调换好先后顺序的指令。并且这些指令中,可以并行执行的部分,会打包在一起组成一个指令包。安腾处理器在取指令和指令译码的时候,拿到的不再是单个指令,而是这样一个指令包。并且在指令执行阶段,可以并行执行指令包里所有的指令。
虽然看起来,VLIW 在技术层面更具有颠覆性,不仅仅只是一个硬件层面的改造,而且利用了软件层面的编译器,来组合解决提升 CPU 指令吞吐率的问题。然而,最终 VLIW 却没有得到市场和业界的认可。
惠普和 Intel 强强联合开发的安腾处理器命运多舛。从 1989 开始研发,直到 2001 年才发布了第一代安腾处理器。然而 12 年的开发过程后,第一代安腾处理器最终只卖出了几千套。而 2002 年发布的安腾 2 处理器,也没能拯救自己的命运。最终在 2018 年,Intel 宣布安腾退出市场。自此之后,市面上再没有能够大规模商用的 VLIW 架构的处理器了。

推荐阅读

关于超标量和多发射的相关知识,你可以多看一看《计算机组成与设计:硬件 / 软件接口》的 4.10 部分。其中,4.10.1 和 4.10.2 的推测和静态多发射,其实就是今天我们讲的超长指令字(VLIW)的知识点。4.10.2 的动态多发射,其实就是今天我们讲的超标量(Superscalar)的知识点。

课后思考

在超长指令字架构的 CPU 里面,我之前给你讲到的各种应对流水线冒险的方案还是有效的么?操作数前推、乱序执行,分支预测能用在这样的体系架构下么?安腾 CPU 里面是否有用到这些相关策略呢?
欢迎留言和我分享你的疑惑和见解。你也可以把今天的内容,分享给你的朋友,和他一起学习和进步。
分享给需要的人,Ta购买本课程,你将得20
生成海报并分享

赞 28

提建议

上一篇
25 | 冒险和预测(四):今天下雨了,明天还会下雨么?
下一篇
27 | SIMD:如何加速矩阵乘法?
unpreview
 写留言

精选留言(32)

  • Linuxer
    2019-06-24
    一个时钟周期也只能执行完这样一条指令,CPI 只能是 1。但是,我们现在用的 Intel CPU 或者 ARM 的 CPU,一般的 CPI 都能做到 2 以上,这是怎么做到的呢?这里不是ipc?
    共 6 条评论
    24
  • magicnum
    2019-06-24
    个人感觉VLIW架构下处理器乱序执行应该不需要了,因为编译器已经将可以并行执行的指令打包成了指令包;操作数前推和分支预测应该可以用吧?
    21
  • 钱勇
    2020-05-27
    不能叫VLIW的失败,只能说是EPIC的失败。 VLIW在配套专用编译器的专用芯片上,应用应该是有前途的。 只是不适合用在涉及到前后兼容的通用芯片上。
    共 1 条评论
    6
  • fcb的鱼
    2020-02-05
    想知道在cpu里边是怎么并行执行的?一直觉得cpu是一个单线程的工作模式。

    作者回复: fcb的鱼同学, 你好,多核CPU、流水线、超线程、Superscalar都是各种“并行”执行的方式呀,可以仔细读一下这几讲。

    6
  • 活的潇洒
    2019-09-01
    “安腾失败的原因有很多,其中有一个重要的原因就是“向前兼容”。”现在终于明白安腾为什么失败了 day26 笔记:https://www.cnblogs.com/luoahong/p/11441329.html

    作者回复: 向前兼容是很多产品成功的原因,但也是很多产品慢慢衰败的原因。 Joel Spolsky曾经专门写过一篇文章讲关于这一点,拿的就是Excel怎么去和Lotus 1-2-3做竞争的例子 https://www.joelonsoftware.com/2000/06/03/strategy-letter-iii-let-me-go-back/

    共 2 条评论
    6
  • QianLu
    2020-02-25
    在《计算机组成与设计:硬件、软件接口(第三版中文)》中,“指令级并行”和“多发射”应是属于章节6.9。
    4
  • 宋不肥
    2019-06-24
    这个本身就已经编译器是打乱顺序执行了吧。分支预测的话,相对于指令包的更多指令来讲,预测出错的话,清理缓存的开销应该会更大,但只要出错率*出错时的开销够小的话就应该依旧可行吧。操作数前推应该依旧可以用
    4
  • 童言
    2021-05-05
    多发射、超标量通过一次读取多条指令、并行进行指令译码来实现 IPC 的提升。但问题在于,有相互依赖的指令并不适合这个策略。如何解决这个问题?期待后续内容。
    1
  • WENMURAN
    2020-04-19
    如何让CPU的吞吐率超过1? 经过前面对于各种冒险的操作,每个周期可以处理的指令只有1条,那么如何让这个数超过1? 解决:多发射,与超标量 一次从内存里取出多条指令,然后交给多个并行的指令译码器,然后交给对应的功能单元去处理。多发射,就是一次取多条指令进行译码执行,超标量,就是一个周期内同时有多条流水线并行工作。
    展开
    2
  • prader26
    2019-09-22
    1 程序的执行时间= 指令数*CPI* 其中周期 2 为了进一步提升cpu的效率,引入了多发射和超标量(同时取多条指令,让多条流水线并行)。
    1
  • 范海良
    2022-03-06
    硬件层面是怎么检测依赖关系的?
    共 1 条评论
    1
  • pearl刘东洋
    2021-03-01
    我是觉得power服务器和aix的成功和现有阶段的难以替代的很一个重要因素可能就是它的编译器,我猜测可能用了和vliw想通的理念设计的
  • Wheat_Liu
    2021-01-19
    安腾为什么要把乱序指令打成指令包,在编译器重排序完了直接执行不行吗?重排时编译器知道数据依赖关系,执行的结果又不会变
  • 静静聆听
    2020-10-19
    "这说明,无论指令后续能优化得多好,一个时钟周期也只能执行完这样一条指令,CPI 只能是 1。但是,我们现在用的 Intel CPU 或者 ARM 的 CPU,一般的 CPI 都能做到 2 以上",老师,纠个错,这里是IPC吧
  • 相逢是缘
    2020-10-12
    徐老师,请教一个问题 内存的主频是很低的,怎能在一个时钟周期里面取多个指令呢? (内存外接32位地址线和32位数据线,每次内存的一个时钟周期,只能取一个32位数据啊)
  • Magic
    2020-10-06
    应该是可以的。超长指令字只是在编译器层面对指令数据依赖关系进行分析,并且找到没有依赖关系的n条指令打包成指令包,cpu取到指令包后,会将每条指令分发到不同的流水线执行,这个过程都是一样的
  • Mr_Ben
    2020-09-10
    老师,你好。请教下:安腾cpu架构下,将多个指令打包后一次执行。是否是cpu从指令段取出多条指令,一次执行?还是编译器编译时,就将指令打包了。这样的话,是否在这个cpu上运行的编译器版本有特殊要求?
  • Paul Shan
    2020-08-15
    个人以为超长指令字架构设计的一个问题是指令包的内部依赖会变得很复杂,这里的复杂度不是线性的而是平方级,指令包每扩大一倍,复杂度就是原来的四倍,这让摩尔定理如何玩得下去呀。 而且把处理这些复杂度都推给编译器,编译器又没法和硬件升级同步,除非是从应用软件到硬件通吃的公司才能处理,像英特尔这种主打开放架构的公司,现实中的操作性就大打折扣。
  • A君
    2020-07-03
    请问nvidia和amd的gpu是不是也是vliw的设计?
  • A君
    2020-07-03
    超标量技术也和乱序指令发射器一样让指令译码可以并行执行。vliw设计将指令乱序、操作数前推、加nop等工作交给了编译器,编译后的指令不再以一条条呈现,而是以一个指令包的形式出现,指令包和指令包之间应该也能应用乱序、操作数前推等操作。