You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 26 | Superscalar和VLIW如何让CPU的吞吐率超过1
到今天为止专栏已经过半了。过去的20多讲里我给你讲的内容很多都是围绕着怎么提升CPU的性能这个问题展开的。
我们先回顾一下[第4讲](https://time.geekbang.org/column/article/93246),不知道你是否还记得这个公式:
程序的CPU执行时间 = 指令数 × CPI × Clock Cycle Time
这个公式里有一个叫CPI的指标。我们知道CPI的倒数又叫作IPCInstruction Per Clock也就是一个时钟周期里面能够执行的指令数代表了CPU的吞吐率。那么这个指标放在我们前面几节反复优化流水线架构的CPU里能达到多少呢
答案是最佳情况下IPC也只能到1。因为无论做了哪些流水线层面的优化即使做到了指令执行层面的乱序执行CPU仍然只能在一个时钟周期里面取一条指令。
![](https://static001.geekbang.org/resource/image/dd/13/dd88d0dbf3a88b09d5e8fb6d9e3aea13.jpeg)
这说明无论指令后续能优化得多好一个时钟周期也只能执行完这样一条指令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讲](https://time.geekbang.org/column/article/101436)讲乱序执行的时候你会看到其实指令的执行阶段是由很多个功能单元FU并行Parallel进行的。
不过在指令乱序执行的过程中我们的取指令IF和指令译码ID部分并不是并行进行的。
既然指令的执行层面可以并行进行,为什么取指令和指令译码不行呢?如果想要实现并行,该怎么办呢?
其实只要我们把取指令和指令译码也一样通过增加硬件的方式并行进行就好了。我们可以一次性从内存里面取出多条指令然后分发给多个并行的指令译码器进行译码然后对应交给不同的功能单元去处理。这样我们在一个时钟周期里能够完成的指令就不只一条了。IPC也就能做到大于1了。
![](https://static001.geekbang.org/resource/image/85/32/85f15ec667d09fd2d368822904029b32.jpeg)
这种CPU设计我们叫作**多发射**Mulitple Issue和**超标量**Superscalar
什么叫多发射呢这个词听起来很抽象其实它意思就是说我们同一个时间可能会同时把多条指令发射Issue到不同的译码器或者后续处理的流水线中去。
在超标量的CPU里面有很多条并行的流水线而不是只有一条流水线。“超标量“这个词是说本来我们在一个时钟周期里面只能执行一个标量Scalar的运算。在多发射的情况下我们就能够超越这个限制同时进行多次计算。
![](https://static001.geekbang.org/resource/image/2e/d3/2e96fe0985a4ae3bd7a58c345def29d3.jpeg)
你可以看我画的这个超标量设计的流水线示意图。仔细看你应该能看到一个有意思的现象每一个功能单元的流水线的长度是不同的。事实上不同的功能单元的流水线长度本来就不一样。我们平时所说的14级流水线指的通常是进行整数计算指令的流水线长度。如果是浮点数运算实际的流水线长度则会更长一些。
## Intel的失败之作安腾的超长指令字设计
无论是之前几讲里讲的乱序执行还是现在更进一步的超标量技术在实际的硬件层面其实实施起来都挺麻烦的。这是因为在乱序执行和超标量的体系里面我们的CPU要解决依赖冲突的问题。这也就是前面几讲我们讲的冒险问题。
CPU需要在指令执行之前去判断指令之间是否有依赖关系。如果有对应的依赖关系指令就不能分发到执行阶段。因为这样上面我们所说的超标量CPU的多发射功能又被称为**动态多发射处理器**。这些对于依赖关系的检测都会使得我们的CPU电路变得更加复杂。
于是,计算机科学家和工程师们就又有了一个大胆的想法。我们能不能不把分析和解决依赖关系的事情,放在硬件里面,而是放到软件里面来干呢?
如果你还记得的话我在第4讲也讲过要想优化CPU的执行时间关键就是拆解这个公式
程序的CPU执行时间 = 指令数 × CPI × Clock Cycle Time
当时我们说过这个公式里面我们可以通过改进编译器来优化指令数这个指标。那接下来我们就来看看一个非常大胆的CPU设计想法叫作**超长指令字设计**Very Long Instruction WordVLIW。这个设计呢不仅想让编译器来优化指令数还想直接通过编译器来优化CPI。
围绕着这个设计的是Intel一个著名的“史诗级”失败也就是著名的IA-64架构的安腾Itanium处理器。只不过这一次责任不全在Intel还要拉上可以称之为硅谷起源的另一家公司也就是惠普。
之所以称为“史诗”级失败,这个说法来源于惠普最早给这个架构取的名字,**显式并发指令运算**Explicitly Parallel Instruction Computer这个名字的缩写**EPIC**,正好是“史诗”的意思。
好巧不巧安腾处理器和和我之前给你介绍过的Pentium 4一样在市场上是一个失败的产品。在经历了12年之久的设计研发之后安腾一代只卖出了几千套。而安腾二代在从2002年开始反复挣扎了16年之后最终在2018年被Intel宣告放弃退出了市场。自此世上再也没有这个“史诗”服务器了。
那么,我们就来看看,这个超长指令字的安腾处理器是怎么回事儿。
在乱序执行和超标量的CPU架构里指令的前后依赖关系是由CPU内部的硬件电路来检测的。而到了**超长指令字**的架构里面,这个工作交给了编译器这个软件。
![](https://static001.geekbang.org/resource/image/22/de/22b3f723ceee5950ac20a7b874dabbde.jpeg)
我从专栏第5讲开始就给你看了不少C代码到汇编代码和机器代码的对照。编译器在这个过程中其实也能够知道前后数据的依赖。于是我们可以让编译器把没有依赖关系的代码位置进行交换。然后再把多条连续的指令打包成一个指令包。安腾的CPU就是把3条指令变成一个指令包。
![](https://static001.geekbang.org/resource/image/f1/f6/f16a1ae443418caca0dc2fc3cec200f6.jpeg)
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里面是否有用到这些相关策略呢
欢迎留言和我分享你的疑惑和见解。你也可以把今天的内容,分享给你的朋友,和他一起学习和进步。