gitbook/深入浅出计算机组成原理/docs/100554.md
2022-09-03 22:05:03 +08:00

112 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 21 | 面向流水线的指令设计奔腾4是怎么失败的
上一讲我给你初步介绍了CPU的流水线技术。乍看起来流水线技术是一个提升性能的灵丹妙药。它通过把一条指令的操作切分成更细的多个步骤可以避免CPU“浪费”。每一个细分的流水线步骤都很简单所以我们的单个时钟周期的时间就可以设得更短。这也变相地让CPU的主频提升得很快。
这一系列的优点也引出了现代桌面CPU的最后一场大战也就是Intel的Pentium 4和AMD的Athlon之间的竞争。在技术上这场大战Intel可以说输得非常彻底Pentium 4系列以及后续Pentium D系列所使用的NetBurst架构被完全抛弃退出了历史舞台。但是在商业层面Intel却通过远超过AMD的财力、原本就更大的市场份额、无所不用的竞争手段以及最终壮士断腕般放弃整个NetBurst架构最终依靠新的酷睿品牌战胜了AMD。
在此之后整个CPU领域竞争的焦点不再是Intel和AMD之间的桌面CPU之战。在ARM架构通过智能手机的快速普及后来居上超越Intel之后移动时代的CPU之战变成了高通、华为麒麟和三星之间的“三国演义”。
## “主频战争”带来的超长流水线
我们在[第3讲](https://time.geekbang.org/column/article/92215)里讲过我们其实并不能简单地通过CPU的主频就来衡量CPU乃至计算机整机的性能。因为不同的CPU实际的体系架构和实现都不一样。同样的CPU主频实际的性能可能差别很大。所以在工业界更好的衡量方式通常是用SPEC这样的跑分程序从多个不同的实际应用场景来衡量计算机的性能。
但是跑分对于消费者来说还是太复杂了。在Pentium 4的CPU面世之前绝大部分消费者并不是根据跑分结果来判断CPU的性能的。大家判断一个CPU的性能通常只看CPU的主频。而CPU的厂商们也通过不停地提升主频把主频当成技术竞赛的核心指标。
Intel一向在“主频战争”中保持领先但是到了世纪之交的1999年到2000年情况发生了变化。
1999年AMD发布了基于K7架构的Athlon处理器其综合性能超越了当年的Pentium III。2000年在大部分CPU还在500850MHz的频率下运行的时候AMD推出了第一代Athlon 1000处理器成为第一款1GHz主频的消费级CPU。在2000年前后AMD的CPU不但性能和主频比Intel的要强价格还往往只有Intel的2/3。
在巨大的外部压力之下Intel在2001年推出了新一代的NetBurst架构CPU也就是Pentium 4和Pentium D。Pentium 4的CPU有个最大的特点就是高主频。2000年的Athlon 1000的主频在当时是最高的1GHz然而Pentium 4设计的目标最高主频是10GHz。
![](https://static001.geekbang.org/resource/image/9b/f8/9bcdc5d5c7c1342085d70fe50d5190f8.jpeg)
为了达到这个10GHzIntel的工程师做出了一个重大的错误决策就是在NetBurst架构上使用超长的流水线。这个超长流水线有多长呢我们拿在Pentium 4之前和之后的CPU的数字做个比较你就知道了。
Pentium 4之前的Pentium III CPU流水线的深度是11级也就是一条指令最多会拆分成11个更小的步骤来操作而CPU同时也最多会执行11条指令的不同Stage。随着技术发展到今天你日常用的手机ARM的CPU或者Intel i7服务器的CPU流水线的深度是14级。
可以看到差不多20年过去了通过技术进步现代CPU还是增加了一些流水线深度的。那2000年发布的Pentium 4的流水线深度是多少呢答案是20级比Pentium III差不多多了一倍而到了代号为Prescott的90纳米工艺处理器Pentium 4Intel更是把流水线深度增加到了31级。
要知道增加流水线深度在同主频下其实是降低了CPU的性能。因为一个Pipeline Stage就需要一个时钟周期。那么我们把任务拆分成31个阶段就需要31个时钟周期才能完成一个任务而把任务拆分成11个阶段就只需要11个时钟周期就能完成任务。在这种情况下31个Stage的3GHz主频的CPU其实和11个Stage的1GHz主频的CPU性能是差不多的。事实上因为每个Stage都需要有对应的Pipeline寄存器的开销这个时候更深的流水线性能可能还会更差一些。
我在上一讲也说过,流水线技术并不能缩短单条指令的**响应时间**这个性能指标,但是可以增加在运行很多条指令时候的**吞吐率**。因为不同的指令,实际执行需要的时间是不同的。我们可以看这样一个例子。我们顺序执行这样三条指令。
1. 一条整数的加法需要200ps。
2. 一条整数的乘法需要300ps。
3. 一条浮点数的乘法需要600ps。
如果我们是在单指令周期的CPU上运行最复杂的指令是一条浮点数乘法那就需要600ps。那这三条指令都需要600ps。三条指令的执行时间就需要1800ps。
如果我们采用的是6级流水线CPU每一个Pipeline的Stage都只需要100ps。那么在这三个指令的执行过程中在指令1的第一个100ps的Stage结束之后第二条指令就开始执行了。在第二条指令的第一个100ps的Stage结束之后第三条指令就开始执行了。这种情况下这三条指令顺序执行所需要的总时间就是800ps。那么在1800ps内使用流水线的CPU比单指令周期的CPU就可以多执行一倍以上的指令数。
虽然每一条指令从开始到结束拿到结果的时间并没有变化,也就是响应时间没有变化。但是同样时间内,完成的指令数增多了,也就是吞吐率上升了。
![](https://static001.geekbang.org/resource/image/b0/6a/b055676975e68a7d4014e46969058f6a.jpeg)
## 新的挑战:冒险和分支预测
那到这里可能你就要问了这样看起来不是很好么Intel的CPU支持的指令集很大我们之前说过有2000多条指令。有些指令很简单执行也很快比如无条件跳转指令不需要通过ALU进行任何计算只要更新一下PC寄存器里面的内容就好了。而有些指令很复杂比如浮点数的运算需要进行指数位比较、对齐然后对有效位进行移位然后再进行计算。两者的执行时间相差二三十倍也很正常。
既然这样Pentium 4的超长流水线看起来很合理呀为什么Pentium 4最终成为Intel在技术架构层面的大失败呢
**第一个自然是我们在第3讲里讲过的功耗问题**。提升流水线深度必须要和提升CPU主频同时进行。因为在单个Pipeline Stage能够执行的功能变简单了也就意味着单个时钟周期内能够完成的事情变少了。所以只有提升时钟周期CPU在指令的响应时间这个指标上才能保持和原来相同的性能。
同时,由于流水线深度的增加,我们需要的电路数量变多了,也就是我们所使用的晶体管也就变多了。
主频的提升和晶体管数量的增加都使得我们CPU的功耗变大了。这个问题导致了Pentium 4在整个生命周期里都成为了耗电和散热的大户。而Pentium 4是在20002004年作为Intel的主打CPU出现在市场上的。这个时间段正是笔记本电脑市场快速发展的时间。在笔记本电脑上功耗和散热比起台式机是一个更严重的问题了。即使性能更好别人的笔记本可以用上2小时你的只能用30分钟那谁也不爱买啊
更何况Pentium 4的性能还更差一些。**这个就要我们说到第二点了,就是上面说的流水线技术带来的性能提升,是一个理想情况。在实际的程序执行中,并不一定能够做得到**。
还回到我们刚才举的三条指令的例子。如果这三条指令,是下面这样的三条代码,会发生什么情况呢?
```
int a = 10 + 5; // 指令1
int b = a * 2; // 指令2
float c = b * 1.0f; // 指令3
```
我们会发现指令2不能在指令1的第一个Stage执行完成之后进行。因为指令2依赖指令1的计算结果。同样的指令3也要依赖指令2的计算结果。这样即使我们采用了流水线技术这三条指令执行完成的时间也是 200 + 300 + 600 = 1100 ps而不是之前说的 800ps。而如果指令1和2都是浮点数运算需要600ps。那这个依赖关系会导致我们需要的时间变成1800ps和单指令周期CPU所要花费的时间是一样的。
这个依赖问题,就是我们在计算机组成里面所说的**冒险**Hazard问题。这里我们只列举了在数据层面的依赖也就是数据冒险。在实际应用中还会有**结构冒险、控制冒险**等其他的依赖问题。
对应这些冒险问题,我们也有在**乱序执行**、**分支预测**等相应的解决方案。我们在后面的几讲里面,会详细讲解对应的知识。
但是我们的流水线越长这个冒险的问题就越难一解决。这是因为同一时间同时在运行的指令太多了。如果我们只有3级流水线我们可以把后面没有依赖关系的指令放到前面来执行。这个就是我们所说的乱序执行的技术。比方说我们可以扩展一下上面的3行代码再加上几行代码。
```
int a = 10 + 5; // 指令1
int b = a * 2; // 指令2
float c = b * 1.0f; // 指令3
int x = 10 + 5; // 指令4
int y = a * 2; // 指令5
float z = b * 1.0f; // 指令6
int o = 10 + 5; // 指令7
int p = a * 2; // 指令8
float q = b * 1.0f; // 指令9
```
我们可以不先执行1、2、3这三条指令而是在流水线里先执行1、4、7三条指令。这三条指令之间是没有依赖关系的。然后再执行2、5、8以及3、6、9。这样我们又能够充分利用CPU的计算能力了。
但是如果我们有20级流水线意味着我们要确保这20条指令之间没有依赖关系。这个挑战一下子就变大了很多。毕竟我们平时撰写程序通常前后的代码都是有一定的依赖关系的几十条没有依赖关系的指令可不好找。这也是为什么超长流水线的执行效率发而降低了的一个重要原因。
## 总结延伸
相信到这里你对CPU的流水线技术有了一个更加深入的了解。你会发现流水线技术和其他技术一样都讲究一个“折衷”Trade-Off。一个合理的流水线深度会提升我们CPU执行计算机指令的吞吐率。我们一般用IPCInstruction Per Cycle来衡量CPU执行指令的效率。
IPC呢其实就是我们之前在第3讲讲的CPICycle Per Instruction的倒数。也就是说 IPC = 3对应着CPI = 0.33。Pentium 4和Pentium D的IPC都远低于自己上一代的Pentium III以及竞争对手AMD的Athlon CPU。
过深的流水线不仅不能提升计算机指令的吞吐率更会加大计算的功耗和散热问题。Intel自己在笔记本电脑市场也很快放弃了Pentium 4而是主推了使用Pentium III架构的图拉丁CPU。
而流水线带来的吞吐率提升,只是一个理想情况下的理论值。在实践的应用过程中,还需要解决指令之间的依赖问题。这个使得我们的流水线,特别是超长的流水线的执行效率变得很低。要想解决好**冒险**的依赖关系问题,我们需要引入乱序执行、分支预测等技术,这也是我在后面几讲里面要详细讲解的内容。
## 推荐阅读
除了之前的教科书之外,我推荐你读一读[Modern Microprocessors, A 90-Minute Guide!](http://www.lighterra.com/papers/modernmicroprocessors/)这篇文章。这篇文章用比较浅显的方式介绍了现代CPU设计的多个方面很适合作为一个周末读物快速理解现代CPU的设计。
## 课后思考
除了我们这里提到的数据层面的依赖,你能找找我们在程序的执行过程中,其他的依赖情况么?这些依赖情况又属于我们说的哪一种冒险呢?
欢迎留言和我分享你的疑惑和见解。你也可以把今天的内容,分享给你的朋友,和他一起学习和进步。