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.

110 lines
14 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.

# 04 | 穿越功耗墙,我们该从哪些方面提升“性能”?
上一讲在讲CPU的性能时我们提到了这样一个公式
程序的CPU执行时间 = 指令数×CPI×Clock Cycle Time
这么来看如果要提升计算机的性能我们可以从指令数、CPI以及CPU主频这三个地方入手。要搞定指令数或者CPI乍一看都不太容易。于是研发CPU的硬件工程师们从80年代开始就挑上了CPU这个“软柿子”。在CPU上多放一点晶体管不断提升CPU的时钟频率这样就能让CPU变得更快程序的执行时间就会缩短。
于是从1978年Intel发布的8086 CPU开始计算机的主频从5MHz开始不断提升。1980年代中期的80386能够跑到40MHz1989年的486能够跑到100MHz直到2000年的奔腾4处理器主频已经到达了1.4GHz。而消费者也在这20年里养成了“看主频”买电脑的习惯。当时已经基本垄断了桌面CPU市场的Intel更是夸下了海口表示奔腾4所使用的CPU结构可以做到10GHz颇有一点“大力出奇迹”的意思。
## 功耗CPU的“人体极限”
然而计算机科学界从来不相信“大力出奇迹”。奔腾4的CPU主频从来没有达到过10GHz最终它的主频上限定格在3.8GHz。这还不是最糟的更糟糕的事情是大家发现奔腾4的主频虽然高但是它的实际性能却配不上同样的主频。想要用在笔记本上的奔腾4 2.4GHz处理器其性能只和基于奔腾3架构的奔腾M 1.6GHz处理器差不多。
于是这一次的“大力出悲剧”不仅让Intel的对手AMD获得了喘息之机更是代表着“主频时代”的终结。后面几代Intel CPU主频不但没有上升反而下降了。到如今2019年的最高配置Intel i9 CPU主频也只不过是5GHz而已。相较于1978年到2000年这20年里300倍的主频提升从2000年到现在的这19年CPU的主频大概提高了3倍。
![](https://static001.geekbang.org/resource/image/18/80/1826102a89e4cdd31f7573db53dd9280.png?wh=756*468 "CPU的主频变化在奔腾4时代进入了瓶颈期")
奔腾4的主频为什么没能超过3.8GHz的障碍呢?答案就是功耗问题。什么是功耗问题呢?我们先看一个直观的例子。
一个3.8GHz的奔腾4处理器满载功率是130瓦。这个130瓦是什么概念呢机场允许带上飞机的充电宝的容量上限是100瓦时。如果我们把这个CPU安在手机里面不考虑屏幕内存之类的耗电这个CPU满载运行45分钟充电宝里面就没电了。而iPhone X使用ARM架构的CPU功率则只有4.5瓦左右。
我们的CPU一般都被叫作**超大规模集成电路**Very-Large-Scale IntegrationVLSI。这些电路实际上都是一个个晶体管组合而成的。CPU在计算其实就是让晶体管里面的“开关”不断地去“打开”和“关闭”来组合完成各种运算和功能。
想要计算得快一方面我们要在CPU里同样的面积里面多放一些晶体管也就是**增加密度**;另一方面,我们要让晶体管“打开”和“关闭”得更快一点,也就是**提升主频**。而这两者,都会增加功耗,带来耗电和散热的问题。
这么说可能还是有点抽象我还是给你举一个例子。你可以把一个计算机CPU想象成一个巨大的工厂里面有很多工人相当于CPU上面的晶体管互相之间协同工作。
为了工作得快一点我们要在工厂里多塞一点人。你可能会问为什么不把工厂造得大一点呢这是因为人和人之间如果离得远了互相之间走过去需要花的时间就会变长这也会导致性能下降。这就好像如果CPU的面积大晶体管之间的距离变大电信号传输的时间就会变长运算速度自然就慢了。
除了多塞一点人我们还希望每个人的动作都快一点这样同样的时间里就可以多干一点活儿了。这就相当于提升CPU主频但是动作快每个人就要出汗散热。要是太热了对工厂里面的人来说会中暑生病对CPU来说就会崩溃出错。
我们会在CPU上面抹硅脂、装风扇乃至用上水冷或者其他更好的散热设备就好像在工厂里面装风扇、空调发冷饮一样。但是同样的空间下装上风扇空调能够带来的散热效果也是有极限的。
因此在CPU里面能够放下的晶体管数量和晶体管的“开关”频率也都是有限的。一个CPU的功率可以用这样一个公式来表示
功耗 ~= 1/2 ×负载电容×电压的平方×开关频率×晶体管数量
那么为了要提升性能我们需要不断地增加晶体管数量。同样的面积下我们想要多放一点晶体管就要把晶体管造得小一点。这个就是平时我们所说的提升“制程”。从28nm到7nm相当于晶体管本身变成了原来的1/4大小。这个就相当于我们在工厂里同样的活儿我们要找瘦小一点的工人这样一个工厂里面就可以多一些人。我们还要提升主频让开关的频率变快也就是要找手脚更快的工人。
![](https://static001.geekbang.org/resource/image/f5/ed/f59f2f33e308000cb5d2ad017f2ff8ed.jpeg?wh=2504*1124)
但是功耗增加太多就会导致CPU散热跟不上这时我们就需要降低电压。这里有一点非常关键在整个功耗的公式里面功耗和电压的平方是成正比的。这意味着电压下降到原来的1/5整个的功耗会变成原来的1/25。
事实上从5MHz主频的8086到5GHz主频的Intel i9CPU的电压已经从5V左右下降到了1V左右。这也是为什么我们CPU的主频提升了1000倍但是功耗只增长了40倍。比如说我写这篇文章用的是Surface Go在这样的轻薄笔记本上微软就是选择了把电压下降到0.25V的低电压CPU使得笔记本能有更长的续航时间。
## 并行优化,理解阿姆达尔定律
虽然制程的优化和电压的下降在过去的20年里让我们的CPU性能有所提升。但是从上世纪九十年代到本世纪初软件工程师们所用的“面向摩尔定律编程”的套路越来越用不下去了。“写程序不考虑性能等明年CPU性能提升一倍到时候性能自然就不成问题了”这种想法已经不可行了。
于是从奔腾4开始Intel意识到通过提升主频比较“难”去实现性能提升边开始推出Core Duo这样的多核CPU通过提升“吞吐率”而不是“响应时间”来达到目的。
提升响应时间就好比提升你用的交通工具的速度比如原本你是开汽车现在变成了火车乃至飞机。本来开车从上海到北京要20个小时换成飞机就只要2个小时了但是在此之上再想要提升速度就不太容易了。我们的CPU在奔腾4的年代就好比已经到了飞机这个速度极限。
那你可能要问了接下来该怎么办呢相比于给飞机提速工程师们又想到了新的办法可以一次同时开2架、4架乃至8架飞机这就好像我们现在用的2核、4核乃至8核的CPU。
虽然从上海到北京的时间没有变但是一次飞8架飞机能够运的东西自然就变多了也就是所谓的“吞吐率”变大了。所以不管你有没有需要现在CPU的性能就是提升了2倍乃至8倍、16倍。这也是一个最常见的提升性能的方式**通过并行提高性能**。
这个思想在很多地方都可以使用。举个例子,我们做机器学习程序的时候,需要计算向量的点积,比如向量$W = \[W\_0, W\_1, W\_2, …, W\_{15}\]$和向量 $X = \[X\_0, X\_1, X\_2, …, X\_{15}\]$$W·X = W\_0 \* X\_0 + W\_1 \* X\_1 +$ $W\_2 \* X\_2 + … + W\_{15} \* X\_{15}$。这些式子由16个乘法和1个连加组成。如果你自己一个人用笔来算的话需要一步一步算16次乘法和15次加法。如果这个时候我们把这个任务分配给4个人同时去算$W\_0W\_3$, $W\_4W\_7$, $W\_8W\_{11}$, $W\_{12}W\_{15}$这样四个部分的结果,再由一个人进行汇总,需要的时间就会缩短。
![](https://static001.geekbang.org/resource/image/64/9d/64d6957ecaa696edcf79dc1d5511269d.jpeg?wh=4558*3271)
但是,并不是所有问题,都可以通过并行提高性能来解决。如果想要使用这种思想,需要满足这样几个条件。
第一,需要进行的计算,本身可以分解成几个可以并行的任务。好比上面的乘法和加法计算,几个人可以同时进行,不会影响最后的结果。
第二,需要能够分解好问题,并确保几个人的结果能够汇总到一起。
第三,在“汇总”这个阶段,是没有办法并行进行的,还是得顺序执行,一步一步来。
这就引出了我们在进行性能优化中,常常用到的一个经验定律,**阿姆达尔定律**Amdahls Law。这个定律说的就是对于一个程序进行优化之后处理器并行运算之后效率提升的情况。具体可以用这样一个公式来表示
优化后的执行时间 = 受优化影响的执行时间/加速倍数+不受影响的执行时间
在刚刚的向量点积例子里4个人同时计算向量的一小段点积就是通过并行提高了这部分的计算性能。但是这4个人的计算结果最终还是要在一个人那里进行汇总相加。这部分汇总相加的时间是不能通过并行来优化的也就是上面的公式里面**不受影响的执行时间**这一部分。
比如上面的各个向量的一小段的点积需要100ns加法需要20ns总共需要120ns。这里通过并行4个CPU有了4倍的加速度。那么最终优化后就有了100/4+20=45ns。即使我们增加更多的并行度来提供加速倍数比如有100个CPU整个时间也需要100/100+20=21ns。
![](https://static001.geekbang.org/resource/image/f1/e5/f1d05ec439e6377803df741bc07b09e5.jpeg?wh=3140*2039)
## 总结延伸
我们可以看到无论是简单地通过提升主频还是增加更多的CPU核心数量通过并行来提升性能都会遇到相应的瓶颈。仅仅简单地通过“堆硬件”的方式在今天已经不能很好地满足我们对于程序性能的期望了。于是工程师们需要从其他方面开始下功夫了。
在“摩尔定律”和“并行计算”之外,在整个计算机组成层面,还有这样几个原则性的性能提升方法。
1.**加速大概率事件**。最典型的就是过去几年流行的深度学习整个计算过程中99%都是向量和矩阵计算于是工程师们通过用GPU替代CPU大幅度提升了深度学习的模型训练过程。本来一个CPU需要跑几小时甚至几天的程序GPU只需要几分钟就好了。Google更是不满足于GPU的性能进一步地推出了TPU。后面的文章我也会为你讲解GPU和TPU的基本构造和原理。
2.**通过流水线提高性能**。现代的工厂里的生产线叫“流水线”。我们可以把装配iPhone这样的任务拆分成一个个细分的任务让每个人都只需要处理一道工序最大化整个工厂的生产效率。类似的我们的CPU其实就是一个“运算工厂”。我们把CPU指令执行的过程进行拆分细化运行也是现代CPU在主频没有办法提升那么多的情况下性能仍然可以得到提升的重要原因之一。我们在后面也会讲到现代CPU里是如何通过流水线来提升性能的以及反面的过长的流水线会带来什么新的功耗和效率上的负面影响。
3.**通过预测提高性能**。通过预先猜测下一步该干什么而不是等上一步运行的结果提前进行运算也是让程序跑得更快一点的办法。典型的例子就是在一个循环访问数组的时候凭经验你也会猜到下一步我们会访问数组的下一项。后面要讲的“分支和冒险”、“局部性原理”这些CPU和存储系统设计方法其实都是在利用我们对于未来的“预测”提前进行相应的操作来提升我们的程序性能。
好了,到这里,我们讲完了计算机组成原理这门课的“前情提要”。一方面,整个组成乃至体系结构,都是基于冯·诺依曼架构组成的软硬件一体的解决方案。另一方面,你需要明白的就是,这里面的方方面面的设计和考虑,除了体系结构层面的抽象和通用性之外,核心需要考虑的是“性能”问题。
接下来,我们就要开始深入组成原理,从一个程序的运行讲起,开始我们的“机器指令”之旅。
## 补充阅读
如果你学有余力,关于本节内容,推荐你阅读下面两本书的对应章节,深入研读。
1.《计算机组成与设计:软/硬件接口》第5版的1.7和1.10节,也简单介绍了功耗墙和阿姆达尔定律,你可以拿来细细阅读。
2.如果你想对阿姆达尔定律有个更细致的了解《深入理解计算机系统》第3版的1.9节不容错过。
## 课后思考
我在这一讲里面,介绍了三种常见的性能提升思路,分别是,加速大概率事件、通过流水线提高性能和通过预测提高性能。请你想一下,除了在硬件和指令集的设计层面之外,你在软件开发层面,有用到过类似的思路来解决性能问题吗?
欢迎你在留言区写下你曾遇到的问题,和大家一起分享、探讨。你也可以把今天的文章分享给你朋友,和他一起学习和进步。