"什么叫好设计"取决于你在为哪个市场设计。同一项技术,在手机上是负担,在服务器上是必需。自个人计算机诞生以来,计算机在外观和使用方式上的变化从未如此剧烈,由此形成了 5 种计算市场,每种都有自己的应用、需求和技术。
| 特征 | 物联网 / 嵌入式 | 个人移动设备 PMD | 桌面计算机 | 服务器 | 集群 / 仓库级计算机 |
|---|---|---|---|---|---|
| 系统价格 | 10 ~ 100 000 美元 | 100 ~ 1000 美元 | 300 ~ 2500 美元 | 5000 ~ 1000 万美元 | 10 万 ~ 2 亿美元 |
| 微处理器价格 | 0.01 ~ 100 美元 | 10 ~ 100 美元 | 50 ~ 500 美元 | 200 ~ 2000 美元 | 50 ~ 250 美元 |
| 关键设计问题 | 价格、能耗、应用的特有性能 | 成本、能耗、媒体性能、响应速度 | 性价比、能耗、图形性能 | 吞吐量、可用性、可扩展性、能耗 | 性价比、吞吐量、能耗均衡性 |
物联网 / 嵌入式计算机:价格是第一约束
嵌入式计算机藏在微波炉、洗衣机、打印机、网络交换机和所有汽车里。物联网(IoT) 指的是通常以无线方式接入互联网的嵌入式计算机;再配上传感器和驱动器,它们就能采集数据、与物理世界互动,于是有了智能手表、智能调温器、智能音箱、智能汽车、智慧家庭、智慧电网、智慧城市。
这一类的计算能力和成本差异最大:从 0.01 美元的 8 位 / 32 位处理器,到用于汽车和网络交换机、售价 100 美元的高端 64 位处理器。它的设计目标有一个鲜明特点:以最低价格满足性能需要,而不是花更多钱买更高性能。
个人移动设备(PMD):成本、能效与"可预测的快"
PMD 指带多媒体用户界面的无线设备,比如手机和平板。 它的设计约束是一组连锁关系:零售价只有几百美元,意味着成本是关键因素;靠电池供电,意味着强调能效;外壳便宜(塑料或陶瓷)、没有风扇散热意味着总功耗也被限制住;能耗与尺寸要求存储用闪存而不是磁盘。
多媒体应用还带来一个特殊要求:响应性能和可预测性能。所谓实时性能需求,是指应用中某个程序段有一个确定的最大执行时间——比如播放视频时,处理器必须在下一帧到来前处理完当前帧,每帧的处理时间是有上限的。更宽松的一种叫软实时:限制的是特定任务的平均时间和"超时实例"的数目,偶尔违反一次时间约束可以接受,但不能频繁发生。
PMD 的处理器常被当成嵌入式计算机,但这里它单列一类,判据很清晰:能不能运行第三方(外部开发的)软件。能运行的算非嵌入式,PMD 属于此列,它和桌面计算机有很多共同特征;而其他嵌入式设备在硬件和软件复杂性上都受到很大限制。
桌面计算机:性价比
以资金论,桌面市场可能仍是最大的市场,覆盖从不到 300 美元的低端上网本到 2500 美元的高配工作站。
这个市场的核心追求是最优性价比:客户最看重计算性能、图形性能和价格,因此最新最强的微处理器和最低成本的微处理器往往都首先出现在桌面系统上。
服务器:可用性、可扩展性、吞吐量
它有三个和桌面完全不同的关键特征:(1)可用性至关重要。银行 ATM、航班订票系统必须 7×24 小时不间断工作,一台服务器故障的后果远比一台个人电脑故障严重。(2)可扩展性。服务器要能随着需求增长扩展计算能力、内存、存储和 I/O 带宽。(3)高吞吐量。服务器的整体性能(每分钟事务数、每秒网页数)比单个请求的响应速度更关键——真正的核心指标是单位时间能处理多少请求所决定的总体效率和成本效益。
集群与仓库级计算机(WSC):用规模换成本,用软件换可用性
一组桌面计算机或服务器通过局域网连在一起,整体像一台更大的计算机运行,但每个节点跑自己的操作系统,节点之间用网络协议通信。数万台服务器像一台服务器一样工作。
这种计算机有几个需要注意的:(1)成本结构:大部分成本与"仓库"内计算机的功耗和冷却有关;另一个大头是计算机与网络设备本身的费用(甚至通常每隔几年就得换一次)。(2)可用性同样关键。(3)实现可用性的方式与服务器不同:WSC 用大量廉价组件搭建,靠软件层捕获和隔离故障来提供可用性;它的可扩展性来自连接计算机的局域网,而不是像服务器那样靠集成计算机硬件。
WSC 与超级计算机的区别
相同点是都极其昂贵,要花数千万美元。
不同点在于——超级计算机强调浮点性能,跑的是通信密集的大型批处理程序,一次可能连跑几周;WSC 强调交互式应用、大规模存储、可靠性和高互联网带宽。
计算机性能增长大概分为如下的阶段:
(1)1986 年之前,年增长率约 25%,相当于每 4 年翻一番
增长几乎全靠制造工艺进步,体系结构贡献有限
此时登纳德缩放比例定律发挥作用。晶体管有长宽高,工艺要求三者需要同比例缩小。高度缩小一半时,为了防止晶体管被击穿,电压也需要跟着缩小一半,以确保电场不变。电压缩小一半后根据欧姆定律,电流也缩小一半,于是整个功耗降到原来的 1/4。同时由于长宽缩小一半,可以容纳的晶体管就变为原来的 4 倍,因此性能提升的同时功率还没有变化。
(2)1986 ~ 2003 年,年增长率约 52%,相当于每 2 年翻一番
市场上出现两个变化:
汇编语言几乎不再被使用,于是"目标代码必须兼容"这个枷锁松了;
出现了独立于厂商的标准化操作系统(UNIX 及 Linux),推出新体系结构的成本和风险大幅下降。
于是 20 世纪 80 年代早期,RISC(精简指令集计算机)成功落地。RISC 的设计者把精力集中在两项关键性能技术上:
利用指令级并行:先是流水线,后来是多指令发射;
使用缓存:最初形式很简单,后来演化出复杂的组织方式与优化手段。
后续的行业格局也由此决定:DEC VAX 没跟上,被 RISC 取代;Intel 选择"在芯片内部把 80x86 指令翻译成类 RISC 指令",从而也能享用 RISC 倡导的新技术——到 90 年代后期晶体管数量暴涨,这层翻译的硬件开销已可忽略。但在手机这类低端场景,翻译带来的功耗与硅面积代价不能忽略,于是一种纯 RISC 体系结构成了主流,这就是 ARM。
(3)2003 ~ 2011 年,年增长率约 23%,相当于每 3.5 年翻一番
登纳德缩放比例定律终结,原因是当晶体管高度缩小到一定的时候,会出现量子隧穿效应,电子不需要 "击穿" 就能直接穿过绝缘层,栅极漏电流指数级暴涨,待机功耗完全不可接受。
后续的处理器选择使用新的工艺,可以做到晶体管的厚度变得更厚,电压需要维持在一个下限,但是晶体管的长宽可以继续缩小,于是晶体管的密度就还可以继续增长,摩尔定律发挥作用。
但后面功耗成为主要的限制。行业被迫用多个高能效处理器替代单个大功耗处理器。Intel 在 2004 年取消了高性能单处理器项目,转向"每个芯片放多个处理器"。
(4)2011 ~ 2015 年,年增长率不足 12%,相当于每 8 年翻一番
多核带来的收益撞上 Amdahl 定律的上限了,即串行的部分成为了性能的瓶颈。
(5)2015 年之后,年增长率仅 3.5%,相当于每 20 年翻一番
集成电路逻辑技术、半导体 DRAM、半导体闪存、磁盘技术、网络技术,这五项技术对计算机发展影响最大:
集成电路逻辑技术
这一条就是摩尔定律在物理层面的两个来源:
单位面积上的晶体管数量每年增长约 35%(本质是晶体管越做越小)。
芯片(晶片)的面积每年增大约 10%~20%(本质是工艺良率提高了,允许做更大的芯片)。
两者相乘,得到「每块芯片上的晶体管总数」每年增长约 40%~55%,也就是大约每 18~24 个月翻一番。这正是摩尔定律的通俗说法。要注意区分这两个来源:晶体管变小和芯片变大是两件独立的事,只是恰好同时在发生。
半导体 DRAM
历史上 DRAM 容量的增长曾非常凶猛(曾达到「每 3 年翻两番」即 4 倍的水平),但近年急剧放缓:2014 年出现 8Gb 的 DRAM 芯片,16Gb 的芯片要到 2019 年才量产,而 32Gb 的芯片按当时的判断可能根本不会出现——因为 DRAM 单元已经很难继续缩小了。内存跟不上处理器,这是一个矛盾。
半导体闪存
它的容量每年增长约 50%~60%,比 DRAM 快得多;而且每比特的价格约为 DRAM 的 1/8~1/10。这就是它能在「内存」和「磁盘」之间插进来,成为存储层次里一个新层级的原因。
磁盘技术
磁盘密度的增长率一直在增长,从大约每 3 年翻一番涨到大约每年翻一番,而随后增长速度开始下降,到近年年增长率已不足 5%。
价格方面要记住这条三级台阶:磁盘每比特的价格约为闪存的 1/8~1/10,而闪存又约为 DRAM 的 1/8~1/10;于是磁盘每比特的价格约为 DRAM 的 1/200~1/300。这条台阶正是「为什么存储层次必须是分层的」最直接的经济学理由:越快越贵,所以只能少量地用在最靠近处理器的地方。
网络技术
网络性能同时取决于交换机的性能和传输系统的性能。它同样在快速改进,而且改进主要体现在带宽上。
站在整个系统(而不是芯片内部)的角度,设计者要区分三个功率数字:
热设计功率(TDP, Thermal Design Power):散热系统必须能持续带走的热量,也是电源必须能持续提供的功率。它既不是峰值功率也不是平均功率——典型情况下 TDP 大约比峰值功率小一点,比平均功耗大很多。它是一个「工程约定值」:按这个数字去设计风扇和电源,就不会在正常使用中出问题。
峰值功率:芯片理论上能达到的最高瞬时功率。真实工作负载几乎不可能长期维持在这个水平,所以按峰值配散热是浪费。
平均功耗:实际运行时的平均值,决定了实际电费和电池续航。
**超频(Turbo Mode,也叫睿频)**就是当芯片温度还有余量、且只有少数核心在忙时,临时把频率提到额定值以上。但此时不会出现问题,因为热量的积累需要时间,短时间的高频不会立刻超温。这等于「借用」还没用掉的热预算。
现在进到芯片内部。CMOS 芯片的功耗由两部分组成:
(1)动态功耗
晶体管每次翻转(0 变 1 或 1 变 0)都要给电容充放电,这就产生动态功耗。公式是:
请注意这两行公式的区别:能耗式子里没有频率,功率式子里有频率。动态能耗表示的是一次翻转消耗的量,只跟电容和电压有关。而功率是「每秒翻转多少次 × 每次多少能量」,所以要乘上频率。
值得注意的是,把电压降一点点,能耗按平方下降——这是性价比最高的省电手段,远比减少电容负载有效。
(2)静态功耗
即使晶体管没有翻转,也会有电流从它身上漏过去,这叫漏电流(leakage current),产生的功耗叫静态功耗:
静态功耗有两个让人头疼的性质:
它跟晶体管数量成正比。芯片上晶体管越多,漏得越多。而摩尔定律恰恰在不断增加晶体管数量。
它跟电压有关,而降压又会加剧漏电。这里出现了一个直接的矛盾:为了省动态功耗我们想降电压,但电压降低后,晶体管的阈值电压也得跟着降,而阈值电压一降,漏电流就指数级上升。这就是电压不能无限降下去的原因。
在一些高性能的处理器上,漏电常常达到总功耗的 50%。其中一个具体原因是:大型 SRAM 缓存必须持续供电才能保住里面的数据,而缓存在现代芯片上占面积很大。
有三股力量在持续压低计算机部件的价格,它们的作用机理各不相同:
| 力量 | 机理 | 量化程度 |
|---|---|---|
| 学习曲线(时间) | 随着生产时间变长,工人和工艺越来越熟练,良率(合格品比例)上升,于是同一个产品的成本自然下降。 | 良率翻倍 ≈ 成本减半 |
| 产量(数量) | 产量越大,一是学习曲线走得越快,二是固定的研发与设备成本被摊薄到更多产品上,三是采购议价能力更强 | 产量每翻一番,成本约下降 10% |
| 商品化(竞争) | 当一种产品由多家厂商生产、彼此可替代(如 DRAM、磁盘、键盘),市场竞争会把价格压到接近成本。这类产品叫商品(commodity) | 利润率被压到很薄,价格几乎等于成本 |
对于集成电路,其制造的大概过程如下:
C++1234567芯片版图 ↓ 制作 掩膜/光罩 ↓ 光刻投影 晶圆上的光刻胶 ↓ 显影、刻蚀、沉积等工艺 晶圆上的电路结构
其中掩膜是制造芯片时用来「印刷」每一层图形的模板,相当于印刷用的底片。它是一次性投入,不管你最终做 1 片还是 1 亿片,这笔钱都要先花。16 nm 工艺一套掩膜约 400 万美元;28 nm 约 150 万美元。
掩膜费用如此高昂,对小批量项目(比如研究原型、学生项目、初创公司试产)几乎是致命的。业界的解法是共享掩膜:多个客户的设计拼在同一套掩膜上一起做,分摊掩膜费。例如台积电的服务:约 3 万美元就能拿到 80~100 片 尺寸为 1.57 mm × 1.57 mm 的小晶片。
晶圆(wafer)是一整片圆形的硅片,直径通常是 300 毫米(有的产线是 450 毫米)。芯片是在晶圆上成批做出来的。
晶片 / 裸片(die)是从晶圆上切下来的一小块方形芯片,一片晶圆能切出几百个晶片。
修正项的存在原因很直观:晶圆是圆的,晶片是方的,靠边缘那一圈放不下完整的方块,只能浪费掉。把这个损失可以近似为 。
其中:
单位面积缺陷数:每平方厘米有多少个制造缺陷,工艺越成熟这个数越小。
N:反映工艺复杂度的参数,原书称为「过程复杂度因子」,工艺越先进、层数越多,N 越大。
晶圆良率:整片晶圆完全废掉的比例,通常接近 1,做题时常按 100% 处理。
越先进的工艺,N 更大、缺陷率更高、晶圆更贵,三个因素同向变坏。这就是为什么一个新工艺刚投产时良率很差、单芯片成本很高,必须等学习曲线走一段时间才划算。
在最后还要进行封装与测试:把合格的晶片装进外壳、接上引脚,然后测试。
对 WSC(仓库级计算机)来说,成本结构和单台机器完全不同,主要分两种:
CAPEX(资本性支出)一次性的建设投入:买服务器、买交换机、建厂房、装配电与冷却设备。
OPEX(运营性支出)持续发生的运行开销:电费、带宽费、维护人力、设备更换。
约一半的月运营成本,是服务器与网络设备的摊销(也就是把 CAPEX 按使用寿命分摊到每个月);这意味着提高服务器利用率(让同样的机器干更多活)与降低单机成本同等重要。
约 40% 与电有关——包括直接电费,以及配电与冷却基础设施的摊销。这意味着能效不是环保口号,而是最大的成本项之一。
过去,集成电路曾是计算机里最可靠的组件之一:管脚容易被掰坏、信道也可能出问题,但芯片内部的故障率极低。可是随着第四部分讲过的特征尺寸缩小到 16 nm 甚至更小,这个传统观念正在改变——临时性故障(出现一下又恢复,比如一个宇宙射线打翻了某个存储单元的电平)和永久性故障(真的坏掉,不会自愈)都变得越来越常见。
所以体系结构师不能再假设芯片不会出错,而必须设计出「能应对出错」的系统。另一方面,服务故障会损失巨额收入。这些正是这一节要建立度量可靠性方法,以及使用各种纠错机制的原因。
SLA(服务等级协议)/SLO(服务等级目标):服务提供方与使用方约定的「服务达到什么水平算正常」。有了这条线,系统的状态就可以被清晰地二分:
服务达成(service accomplishment):服务按约定正常提供;
服务中断(service interruption):服务未达到约定水平。
系统在这两种状态之间来回切换:从达成到中断叫失效(failure),从中断回到达成叫恢复(restoration)。
然后我们有四个指标来度量可靠性:
| 指标 | 全称与单位 | 含义 |
|---|---|---|
| MTTF | 平均无故障时间(Mean Time To Failure),单位:小时 | 从开始正常工作到出现故障,平均能撑多久。越大越好 |
| FIT | 失效率(Failures In Time),单位:每 10 亿小时的失效次数 | MTTF 的倒数换了个单位。用它是因为单个部件的故障率极低,用「每十亿小时多少次」表达更方便,而且多个部件的 FIT 可以直接相加 |
| MTTR | 平均修复时间(Mean Time To Repair),单位:小时 | 坏了之后,平均多久能修好恢复服务。越小越好 |
| MTBF | 平均故障间隔时间(Mean Time Between Failures) | 两次故障之间的平均间隔,等于 MTTF + MTTR |
由这几个量得到可用性:
想提高可用性,有两条路——让它更不容易坏(增大 MTTF),或者让它坏了修得更快(缩小 MTTR)。很多时候后者往往更容易实现。
分母里 MTTR 的权重不容小觑:如果 MTTF 是 10 000 小时而 MTTR 是 24 小时,可用性是 99.76%;把 MTTR 压到 1 小时,可用性立刻变成 99.99%。同样的硬件,运维水平决定了两个九的差距。
计算一个由多个部件组成的系统的 MTTF,可以把每个部件的故障率换算成 FIT,然后把所有部件的 FIT 直接相加,得到系统总 FIT,系统 MTTF = 10⁹ ÷ 系统总 FIT。
由此可见,部件越多,系统越容易坏**。** 因为 FIT 是相加的,10 个部件的系统故障率就是单个部件的 10 倍,MTTF 只有 1/10。所以对于由上万台服务器组成的 WSC 来说,「每天都有机器坏」是设计前提,不是意外。
一般我们说性能好,指的是两个方面的内容:
响应时间(response time)/执行时间(execution time):完成一个任务需要的时间。个人用户最关心这个(点一下按钮多久有反应)。
吞吐量(throughput):单位时间内完成的任务总数。数据中心运营者最关心这个(每小时能处理多少订单)。
这两者可以背道而驰**。** 比如加一台服务器,吞吐量翻倍,但单个请求的响应时间一点没变;再比如把请求批量攒起来一次处理,吞吐量提高了,但每个请求的等待时间反而变长了。所以说「性能提升了 2 倍」必须说明是哪一种。
而对于时间,时间本身也有两种定义:
| 口径 | 包含哪些时间 | 什么时候该用它 |
|---|---|---|
| 挂钟时间(wall-clock time),也叫响应时间或已用时间(elapsed time) | 完成一项任务的全部延迟:外存访问、存储器访问、输入/输出活动、操作系统开销,全都算在内 | 用户真正感受到的就是这个。评价「一个任务多久能出结果」时必须用它 |
| CPU 时间 | 只算处理器真正在做计算的时间,不含等待 I/O 的时间,也不含运行其他程序的时间 | 评价处理器本身、或排除 I/O 与多任务干扰时用它 |
基准测试程序(benchmark):一组约定好的程序,让不同机器跑同样的东西,从而得到可比较的性能数字。
最好的基准就是实际应用程序本身。而历史上人们尝试过一些比实际应用简单得多的替代品,全都留下了隐患:
| 替代品类别 | 是什么 | 问题 |
|---|---|---|
| 程序内核(kernel) | 从实际应用中抽出来的短小而关键的片段 | 只能评价局部,代表不了整体;代码少,最容易被针对性优化击穿 |
| 玩具程序(toy program) | 为完成编程入门作业而写的小程序,通常不超过 100 行,例如快速排序 | 与真实负载几乎无关 |
| 合成基准程序(synthetic benchmark) | 为「匹配实际应用的特征和行为」而虚构出来的程序,例如 Dhrystone | 名声最差:它本身不做任何有意义的工作,却可以被专门优化 |
运行条件同样是基准有效性的一部分,这里存在两个方面:
编译器参数。用「基准专用」的编译参数能刷高成绩,但这些参数常常会导致对许多程序而言非法的变换,还可能让另一些程序变慢。对策是:要求供应商对所有用同一语言(C / C++)编写的程序,使用同一个编译器和同一组参数。
是否允许修改源代码。这里有三种处理方式:
完全不允许修改源代码;
允许修改,但基本没有修改的可能——例如数据库基准依赖拥有数千万行代码的标准数据库程序,数据库公司几乎不可能为了让某一台机器跑得快而去改它;
允许修改源代码,只要修改后的版本给出的输出结果相同即可。
设计者在这三条之间怎么选?一个判据是:这些修改是否反映了实际做法、能否给用户提供有用的见解,还是反而降低了基准作为「实际性能预测指标」的准确率。
对于领域专用架构师(为一类定义良好的任务专门设计处理器的人)通常采用的正是第三种——允许改代码,只要结果一样。原因不难理解:他们的芯片往往需要程序按新的方式组织计算才能发挥出来,如果一行都不许改,这类芯片根本无法被公平评测。
一般基准测试是「一套」的而不是「一个」的。这是因为一套基准测试可以用来测量处理器在各种应用上的表现,避免「把太多鸡蛋放在一个篮子里」。
比较有名的基准测试:
(1)SPEC 家族
SPEC(标准性能评估机构):在「创建标准化基准应用程序套件」这件事上最成功的尝试之一,它源于 20 世纪 80 年代后期为了更好地对工作站做基准测试而付出的努力。行业一直在变,对基准的需求也在变,所以今天的 SPEC 已经是一大家族,涵盖众多应用领域。
SPEC CPU 是其中最著名的一套,分为整数(CINT)和浮点(CFP)两组,用于评测桌面系统和单处理器服务器的处理器性能。
SPEC 基准是经过修改以便移植、并尽量减轻 I/O 影响的真实应用程序:整数部分从 C 编译器的一部分、国际象棋程序到视频压缩;浮点部分包括分子动力学、射线追踪和天气预测。
桌面基准分为两大类**:**处理器密集型和图形密集型(不过许多图形基准里也包含大量处理器行为,两者并非沾不上边)。
SPEC 最初开发的就是一个面向处理器性能的基准集,最早叫 SPEC89,之后逐代演变:SPEC89 → SPEC92 → SPEC95 → SPEC2000 → SPEC2006 → SPEC CPU2017,到 CPU2017 已经是第 6 代。SPEC CPU2017 由 10 个整数基准(CINT2017)和 17 个浮点基准(CFP2017)组成。
值得注意的是,SPEC2017 的应用程序「可能是真实的,但并没有启发性」,理由:
这些程序与 1.1 节提到的现代编程语言、运行环境以及 Google Translate 这类应用没什么共同点;
其中将近一半是用 Fortran 编写的;
它们甚至是静态链接的,而现实世界中的大多数程序是动态链接的。
这告诉我们,基准测试永远滞后于真实工作负载**。** 用它比较机器是可以的,用它断言「这台机器跑我的业务更快」则很危险。
SPEC 远不只有 CPU 一套,在 云、图形与工作站、Java 客户端 / 服务器 等领域也有对应的基准测试。
(2)TPC
TPC(事务处理性能委员会)由一群工程师在 20 世纪 80 年代中期组建,目标是为 TP 建立独立于供应商的客观公平基准。
TPC 主要测试一个系统处理事务(包括数据库访问与更新)的能力。最典型的简单例子是航空订票系统和银行 ATM 系统;更高级的事物处理系统涉及复杂数据库与决策制定。
TPC 的基准是一个不断演化的家族,了解它们的分工有助于理解「不同业务需要不同基准」:
| 基准 | 诞生 / 定位 | 模拟的场景 |
|---|---|---|
| TPC-A | 1985 年,第一个 TPC 基准 | 已被后来的基准取代 |
| TPC-C | 1992 年创建 | 复杂的查询环境 |
| TPC-H | 决策支持 | 查询之间没有关联,不能用过去查询的知识优化将来的查询 |
| TPC-DI | 数据集成(DI,也叫 ETL) | 数据仓库的重要组成部分 |
| TPC-E | 联机事务处理(OLTP) | 模拟代理公司的客户账户 |
| TPCx-HS / TPC-DS | 回应关系数据库与 NoSQL 之争 | TPCx-HS 测运行 MapReduce 程序的 Hadoop 文件系统;TPC-DS 测决策支持系统(可基于关系数据库或 Hadoop) |
| TPC-VMS / TPCx-V | 虚拟化 | 虚拟化系统上的数据库性能 |
| TPC-Energy | 能耗附加指标 | 为所有现有 TPC 基准增加能耗测量 |
TPC 基准有三条共同规则:
性能一律以每秒完成的事务数来衡量(吞吐量指标);
同时包含响应时间要求——只有在满足响应时间限制的前提下,吞吐量成绩才算有效。这条设计非常聪明:它防止用「无限排队」的方式刷吞吐量。
必须把系统成本包含在内,以便准确对比性价比;TPC 还统一了定价规格,使公布的价格可以被验证。此外,事务率更高的系统必须对应更大型的系统——既指更多用户,也指更大的数据库,防止用小数据集刷分。
需要注意的是,基准会「老化」,核心原因是它抵抗不了「基准测试工程 / 基准测试技巧」——一旦某项基准变得标准化并流行,厂商就面临巨大压力,要通过有针对性的优化、或者对运行规则做出对自己有利的解读来提高成绩。把时间集中花在少量代码上的小型内核或程序,尤其容易被针对。
报告性能结果时,必须列出其他试验者再现该结果所需要的全部信息,确保可再现性。
跑完一套基准(比如 SPEC 的几十个程序)会得到几十个结果。怎么合成一个总分?
分数的目标在于比较两个机器的性能,单纯地把各项分数进行线性的计算是不科学的,因为某一项的权重如果发生变化,会使得两个机器的分数相差发生变化,并且会产生不同的结论。
比较科学的方法是采用比值,两个被测机器都计算和参考机器的比值分数,对比这两个参考机器的时候就可以直接计算比值。采用比值后,汇总多个分数就需要使用几何平均而不是加权平均:
几何平均 =(所有 n 个比值相乘)的 n 次方根
几何平均有这样一个性质:
A 的几何平均 ÷ B 的几何平均 =(A 与 B 各项逐项比值)的几何平均
即:先算比值再取几何平均,与先取几何平均再算比值,结果完全一样。
直接后果是:换参考机器不会改变两台机器的相对排名(换参考机相当于给所有比值同乘一个因子,这个因子在比较中被约掉)。
但几何平均存在缺点:
它不再对应任何真实的执行时间——你不能用它预测「跑完这一套程序总共要多久」。如果你关心总时间,就该回到加权算术平均,并明确说明权重来自什么假设(同时接受前面提到的偏差风险)。
在几何平均中,一个极端异常的程序仍然会显著扭曲总分。看基准成绩必须同时看各项分布。
比如:SPEC2006 整数套件(Cint2006)的实测数据:参考机是 Sun Ultra 5,被测机是一台 AMD A10 和一台 Intel Xeon E5-2690。其中 libquantum 一项的表现极其异常,原因是 Intel 编译器自动把这段代码并行到 22 个核上运行,并使用比特打包(bitpacking)优化存储器——把多个取值范围很窄的整数打包在一起,节省存储空间从而节省存储带宽。这导致这一项把总分从「快 1.5 倍」抬到了「快 2.0 倍」,而 1.5 倍才更接近两台机器的真实相对性能。
| 项目 | AMD A10 | Intel Xeon E5-2690 |
|---|---|---|
| 除 libquantum 外各项 SPECRatio 的范围 | 16 ~ 52 | 22 ~ 78 |
| libquantum 一项的 SPECRatio | 246.08(约 250 倍) | 7295.77(约 7300 倍) |
| 全部程序的几何均值 | 31.91 | 63.72 |
| 剔除 libquantum 后的几何均值 | 26.5 | 41.4 |
| Intel 相对 AMD 的倍数 | 含 libquantum:2.0 倍 → 剔除后:1.5 倍 |
几乎所有计算机都有一个以固定频率运行的时钟,这些离散的时间事件叫时钟周期。设计人员既可以用周期的持续时间(例如 1 ns)来描述它,也可以用频率(例如 1 GHz)来描述——两者互为倒数:
程序的 CPU 时钟周期数由如下决定:
指令数(IC, instruction count):程序执行过程中总共执行了多少条指令(也叫指令路径长度)。
CPI(clock cycles per instruction):平均每条指令用掉多少个时钟周期 = 程序的 CPU 时钟周期数 ÷ 指令数。
IPC(instructions per clock):每个时钟周期完成多少条指令,它就是 CPI 的倒数。
CPU 的时间由这三个分量组成:
| 分量 | 主要由什么决定 | 通俗解释 |
|---|---|---|
| 时钟周期时间 | 硬件技术与组成 | 工艺能做多快、电路和流水线怎么划分 |
| CPI | 组成(微体系结构)与指令集体系结构 | 流水线深度、缓存命中率、分支预测好不好,以及指令本身复杂不复杂 |
| 指令数 | 指令集体系结构与编译器技术 | 一件事需要几条指令来表达,以及编译器能否用更少的指令完成 |
当程序包含多类指令时需要用到加权形式:
实践中怎么测这三个分量?
执行时间很容易测,时钟速度是已知的;难点是指令数和CPI。
大多数处理器内置了对已执行指令数和时钟周期计数的硬件计数器。定期读这些计数器,还能把执行时间和指令数关联到具体的代码段上——这对程序员理解和调优应用性能非常有帮助。
如果想知道得更深(比如「CPI 为什么是现在这个值」),就要用仿真技术,即用与被设计处理器类似的模型去模拟。
需要注意的是:
每一类指令的 CPI 应当通过测量得出,而不是照抄参考手册后面的表格**。**为什么?因为真实的 CPI 必须包含流水线效应、缓存缺失以及存储系统中任何其他低效情形。手册上写的是「理想情况下这条指令要几个周期」,而你需要的是「在你这个程序里它实际花了几个周期」。
DVFS 会增加这个公式的使用难度,因为在测量程序时时钟速度可能一直在变。测量时需要把这些功能关掉,从而使结果可以再现**。**
Amdahl 定律的核心表述:使用某种快速执行模式所获得的性能改进,受限于可以使用这种模式的时间比例。
先定义加速比:
它回答的问题是,改进后的计算机运行这个任务,比原来快多少倍**。**加速比取决于两个因素:
| 因素 | 定义 | 取值范围与例子 |
|---|---|---|
| 改进比例 | 原计算机的计算时间中,可以被改进的那部分所占的比例 | 总是小于或等于 1。例:程序总执行时间 100 秒,其中 40 秒可改进 → 改进比例 = 40/100 = 0.4 |
| 改进加速比 | 如果整个程序都使用这种改进模式,任务的运行速度会提高多少倍(= 原模式执行时间 ÷ 改进模式执行时间) | 总是大于 1。例:某部分原本需要 40 秒,改进后只需 4 秒 → 改进加速比 = 40/4 = 10 |
改进后的执行时间由两块组成:没被改进的那部分照旧,被改进的那部分除以改进加速比。
比如:
某处理器在 Web 服务应用中的计算速度可以提高到原来的 10 倍。原处理器有 40% 的时间忙于计算,60% 的时间在等待 I/O。总加速比是多少? 改进比例 = 0.4,改进加速比 = 10 总加速比 = 1 ÷ ((1 − 0.4) + 0.4 ÷ 10) = 1 ÷ (0.6 + 0.04) = 1 ÷ 0.64 ≈ 1.56
我们看到,虽然局部快了 10 倍,但整体只快了 1.56 倍**。** 因为那 60% 的 I/O 等待时间完全没动,它现在成了绝对的主导项。
我们可以得到两个推论:
推论一:回报递减规律。如果只改进一部分计算的性能,那么随着改进幅度不断增加,所获得的加速比增量会逐渐减小。
推论二:加速比有上限。若某项改进只对任务的一部分适用,则总加速比存在一个上限:
举例:如果一个程序只有 50% 的部分能并行,那么哪怕用上无限多的处理器(改进加速比 → 无穷),总加速比也永远不会超过 2 倍。这是一条与投入资源多少无关的硬性天花板,也是评估任何优化提案的第一道筛子。
应用 Amdahl 定律时最常见的一个错误:混淆「可改进部分在改进之前所占的时间比例」和「改进部分在改进之后所占的时间比例」。公式里的「改进比例」必须是改进之前测得的比例。如果你测的是改进之后的比例,算出来的结果就是错的。优化会自动改变各部分的占比——被优化的部分变快了,它在新的总时间里占比就下降了。
(1)节省动态功耗
DVFS(动态电压频率调节,Dynamic Voltage-Frequency Scaling):在处理器不需要满速运行时,同时降低它的电压和频率。因为省下的能耗按电压平方计算,收益非常可观。手机在你看小说时把主频降到很低,就是在做这件事。
针对典型情景设计(design for typical case):既然设备大多数时间是空闲的,就重点优化「空闲」这个状态。例如内存和磁盘在空闲时进入低功耗模式,处理器的空闲部件被时钟门控(clock gating,即停掉不用模块的时钟,让它不再产生翻转)。但是只是不翻转了,动态功耗直接归零,而静态功耗还有。
(2)节省静态功耗
以逸待劳(race-to-halt):先用最高速度把活干完,然后立刻进入深度睡眠。「快点干完早点睡」有时比「慢慢悠悠地干」总能耗更低,因为要干的活是一定的,但是干的快的话,苏醒的时间就短了,静态功耗就降低了。
电源门控(power gating)——把暂时不工作的模块整块断电,而不只是让它停下来。断电才能把静态功耗也归零。功耗极低的系统正是靠这一手控住漏电损失。
(3)暗硅
随着晶体管的数量增多,我们已经没有足够的功耗预算来启用所有的晶体管,所以就出现了暗硅这个手段:如果无论如何都只能用掉一部分晶体管,那么最优策略就不再是「做一个能干所有事的大通用核心」,而是「在芯片上放很多个各司其职的专用单元,用到哪个点亮哪个」。
此外,专用单元之所以能便宜几十倍,另一个原因是,他能用刚好够用的数据宽度,以及减少数据搬运。
CPU 时间的三个分量,任意一项改进 10%,CPU 时间就改进 10%——它们的地位完全对等。
但是用于改变这三项特性的基本技术是相互关联的,很难在不改变其他两个的情况下只改变其中一个**。** 例如:
为降低 CPI 而加深流水线,往往需要更多硬件,可能反而拉长时钟周期;
为减少指令数而设计更复杂的复合指令(CISC 思路),往往会提高 CPI——这正是第一部分讲 RISC 时的核心论点;
编译器展开循环可以减少分支指令(降 IC),但可能增加寄存器压力从而增加访存(升 CPI)。
所以绝不能只看单个分量的改善来判断性能,必须算三者的乘积。
好消息是:许多性能改进技术主要影响其中一个分量,对另外两个的影响较小或在可预测范围内,所以这个公式在实践中依然非常好用。
应用程序里有两种并行:
数据级并行(DLP):因为有许多数据项可以同时操作;
任务级并行(TLP):因为创建出的工作任务可以单独执行、主要以并行方式执行。
计算机硬件用四种主要方式去利用上面这两种并行:
指令级并行(ILP):在两个层面利用数据级并行——先借助编译器帮助,用流水线之类的思想适度利用;再借助推测执行之类的思想进一步利用;CPU 的流水线(取指→译码→执行→访存→写回,5 条指令各占一步同时推进)、超标量(一个时钟周期发 2~6 条指令)、乱序执行、分支预测。
向量体系结构、GPU、多媒体指令集:把单条指令并行作用到一组数据上,利用数据级并行;SIMD
线程级并行:在一种紧耦合硬件模型中利用 DLP 或 TLP,允许并行线程之间交互;多核。
请求级并行:利用程序员或操作系统指定的大量解耦任务之间的并行性。多服务器。
并行化存在三个层次:
(1)系统级并行
要提高服务器类吞吐量,最直接的办法是使用多个处理器和多个存储设备,然后把请求分散到它们上面去处理。
系统级并行的重点是可扩展性(scalability):扩展内存、增加处理器和存储设备数量的能力。对服务器来说,这是一项非常有价值的优点——因为业务量增长时,你希望通过「加机器」而不是「换更贵的机器」来应对。
(2)单处理器级并行:流水线
在单个处理器内部,充分利用指令之间的并行是实现高性能的关键,而最简单的实现方式就是流水线。
流水线(pipelining):把指令的执行重叠起来,以缩短完成整个指令序列的总时间。就像工厂流水线,一辆车没造完就可以开始造下一辆。
它为什么可行?关键在于——并非每条指令都依赖它前面的那条指令,所以让这些互不依赖的指令完全或部分并行地执行是有可能的。
流水线是指令级并行(ILP)最为人熟知的例子。
(3)数字设计级并行
比如:
组相联缓存(set-associative cache)使用多体(multiple banks)存储器,通常可以并行查询这些体,同时寻找所需的项,一次查多个候选位置,而不是一个个试
先行进位(carry-lookahead)加法器不再让进位逐位串行地传递,而是并行地计算各位的进位,把求和时间从「线性时间」变为「对数时间」
带宽(bandwidth),也叫吞吐量(throughput),指单位时间内完成的工作总量。比如内存每秒能传多少 MB,磁盘每秒能读多少 MB。
延迟(latency),也叫响应时间(response time),指从发出请求到得到结果所经过的时间。比如访问一次内存要多少纳秒。
微处理器、内存、磁盘、网络这四类部件的里程碑产品,有这样一个对比:
| 部件类别 | 带宽改进倍数 | 延迟改进倍数 | 差距 |
|---|---|---|---|
| 微处理器 | 约 32 000 倍 | 约 50 倍 | 带宽远远领先 |
| 网络 | 约 40 000 倍 | 约 90 倍 | 带宽远远领先 |
| 存储器(DRAM) | 约 400 倍 | 约 9 倍 | 带宽远远领先 |
| 磁盘 | 约 2400 倍 | 约 8 倍 | 带宽远远领先 |
四类部件横跨完全不同的物理原理,结论却完全一致:带宽的改进幅度是延迟改进幅度的几十倍到几百倍。由此我们提炼出一条经验法则:
带宽的增长速度至少是延迟改进速度的平方。也就是说,如果延迟改进了 10 倍,带宽通常已经改进了 100 倍以上。
带宽好提升而延迟难提升,其中的原因有这样几点:
带宽可以靠"加并行"换来,延迟不行。想提高带宽,把部件复制一份、宽度加倍、多开几条通道就行;想降低延迟,你必须让单个操作本身真的变快,而这受物理规律(信号传播速度、电容充放电时间)的硬约束。
延迟的改进往往会被其他环节吃掉。一个操作的延迟由链路上每一环相加,你把最快的一环再优化 2 倍,总延迟可能只降几个百分点。
市场更愿意为带宽付钱。客户往往用"每秒多少帧""每秒多少笔交易"来验收,这些是带宽指标;因此厂商的研发投入自然向带宽倾斜。
特征尺寸(feature size):集成电路上一个晶体管或一根连线所能达到的最小尺寸,用它来标识一代工艺。1971 年为 10 微米,2017 年已缩到 0.016 微米(即 16 纳米)——45 年里缩小了约 625 倍。
特征尺寸缩小带来的效果并不统一:
晶体管方面:由于晶体管数量按尺寸的平方增长(面积是二维的),而单个晶体管的性能按尺寸的倒数改善,所以「更小」对晶体管来说几乎是纯好处——又多又快。
连线方面:情况相反。连线变细之后,单位长度的电阻和电容都变差了。虽然工艺上会加更多的金属层来缓解,但总体趋势是连线延迟的改进远远落后于晶体管性能的改进。
于是出现了一个非常反直觉、但对现代芯片设计至关重要的现象:在一些先进工艺中,横跨整块芯片的一根连线,其信号传播延迟已经可能超过多个时钟周期**。** 换句话说,「让数据从芯片的一头走到另一头」本身就是一件昂贵的事。这直接导致现代设计要求把相关的部件放得尽量靠近(局部化),也是「为什么多核不能简单地无限堆核心、为什么缓存要分级并按块划分」的物理根源。
| 对比 | 倍数 | 这说明什么 |
|---|---|---|
| 32 位浮点加法 vs 8 位整数加法(能耗) | 约 30 倍 | 数据类型的宽度和精度直接决定电费:能用 8 位就别用 32 位 |
| 32 位浮点加法 vs 8 位整数加法(面积) | 约 116 倍 | 同样的硅面积,可以放很多个窄整数单元 |
| 读写 DRAM 32 比特 vs 8 位整数加法(能耗) | 约 21 333 倍 | 访问一次内存的能耗,相当于做两万多次整数加法 |
| 小型 SRAM 的能效 vs DRAM | 约 128 倍 | 把数据留在片上小存储里,比回主存便宜两个数量级 |
由此可见:在现代芯片上,"把数据搬过来"的代价远远超过"把数据算一遍"。这条事实一次性解释了很多设计动机:
为什么要有多级缓存——避免去 DRAM;
为什么要做循环分块、提高数据复用——让搬过来的数据多算几次;
为什么专用加速器往往配备大量窄位宽的运算单元而不是少数几个通用浮点单元——专用处理器可能配置 10~100 个窄整数运算单元,用同样的面积和电预算换取更多的实际算力。
任何一个「加点硬件换点性能」的提案,都必须先问「它要多大面积」。因为面积不是线性地花钱,而是超线性地花钱:
面积大了,切得少;
面积大了,良率还更低(第二步)。
两个因素叠乘,于是成本对面积的敏感度远远超过线性*——大致按面积的平方级别增长。