这篇文章是90-Minute Guide!的简单阅读笔记
原文是一篇极佳的、点到为止的概述性文章,我推荐直接阅读
而不是看我这种比机翻还差劲,比原文更短但是对读者没有任何帮助的文章
但是,如果你希望有一个10-Minute Guide,可以考虑往下看看
CPU的黑科技
历史上,CPU为了快,发展了一系列的优化技术:
- 单CPU的优化:包括超标量、OOO乱序执行、VLIW超长指令字、分支预测
- 多CPU的优化:包括多核、同步多线程SMT(超线程)技术以及大小核
- 局部性的优化:多级cache技术
- 专用优化:SIMD
这些首先在这里总结了,在后面会有展开讨论
(本文集中在第一部分)
性能指标之频率
哪怕是仅仅作为消费者,也会知道CPU会有一个若干Hz的“主频”(频率)
那它能不能作为唯一决定这颗CPU快不快的基准?如果不是,那它到底意味着什么?
SPECint95 | SPECfp95 | ||
---|---|---|---|
195 MHz | MIPS R10000 | 11.0 | 17.0 |
400 MHz | Alpha 21164 | 12.3 | 17.2 |
300 MHz | UltraSPARC | 12.1 | 15.5 |
300 MHz | Pentium II | 11.6 | 8.8 |
300 MHz | PowerPC G3 | 14.8 | 11.4 |
135 MHz | POWER2 | 6.2 | 17.6 |
上表是一个关于主频与关于整型运算和浮点运算的基准测试的汇总
尽管年份有点久远(直接扒的表),但仍有参考价值
不管怎样,表中并没有直接体现出主频越高,它就在某些运算上越强的规律
至少,我们知道,时钟的频率并不能决定性能的快慢
不过,说来奇怪,时钟频率意味着一段相同时间内能干多少事情(时钟周期),为什么单位时间内干的事情数量上去了,效率却不见得提高?
那就是说,问题出现在“干了什么事情”上
因此,这个话题回到了前面提到的CPU所谓的“黑科技”。各种各样的优化,就是在做正确的事情
性能指标之CPI
CPU终究是执行指令,因此也应有直接与指令挂钩的指标——CPI,cycles per instruction
现在,起码把频率和指令搭上关系了
流水线与CPI
一条指令会怎样被CPU消费?
至少经过4个阶段:
- fetch
- decode
- execute
- writeback
每一个阶段都要花费1个时钟周期的成本
那就是很简单地,CPI = 4
一个直接降低CPI的方法是引入流水线
让各个步骤允许重叠起来,那就能做到CPI降为1
那么,这就是流水线的全部了吗?
我们还可以做超流水线
尝试把每一个阶段拆分得更细
比如把一个execute阶段拆成三个子阶段
既有更多的但又更短的阶段
这种做法可以使得时钟频率更加地高
(理想状态下,因为时钟频率受制于最长最慢的阶段,所以把这个阶段拆分了就能解决频率瓶颈)
不过,这种做法会带来更高的延迟(每个指令需要更多的周期去完成)
但是却使得CPU保留每周期完成1个指令的CPI的同时,拥有了更多的时钟周期
需要注意的是,这是通过拆分更细的阶段来解决瓶颈从而得到的性能提升,并不能一直拆分,因为还需要考虑延迟带来的影响,实际上一般10-30+级流水线的深度,而不会成百上千的深度
多发射,超标量
考虑到execute是个特殊的阶段
不同的功能是会用到不一样的单元来完成execute阶段(比如整型运算,浮点运算等)
因此尝试加强decode阶段,让指令可以同时发射到不同的execute单元,比如每个时钟周期发射3个不同功能的指令,使得不同功能的流水线相互独立以降低延迟,此时的CPI为0.33
这种技术可认为是一种指令级并行
现在的cpu都结合了多发射和超流水线,可以简称为超标量处理器
VLIW
考虑到指令本身,为什么不尝试声明一些指令让它们本身就是并行执行的?这可以避免cpu对指令在decode阶段的依赖检查
这种做法就是超长指令字,可以认为是一组指令占在一起,因此认为是超长的
此时,即使decode不做刚才复杂的多发射也可以做到指令级并行
不过,指令本身需要考虑兼容性
并且该技术并没有广泛商用
延迟和依赖
前面提到延迟问题导致了架构无法进一步激进地设计更深的流水线和更宽的发射宽度
这与延迟有关
延迟是关乎单个指令从被取指到结束的完整生命周期所需要花费的时间成本
可为什么说跟它有关?我们聊性能不是更喜欢聊吞吐量吗?
这其中的联系又与指令间的依赖有关
举个例子
如果一条多发射/超流水线的数据需要分别执行乘法和加法运算才能得到最终结果
而已知乘法需要花费更多的时钟周期去完成(与加法运算相比)
那么,先乘后加的情况下如何做到指令级并行?并行的后果是,加法需要花费更多的时钟周期去等待乘法运算
(等待就是要在流水线中插入bubble,不干实际的工作)
一个结论是,越深的流水线,延迟则越大
另一个问题:分支预测
类似于数据依赖问题,当遇到if-else分支时,CPU并不知道要对哪一边的分支指令进行fetch和decode,更不用提execute了
一种很自然的做法就是靠猜
猜一条分支,不仅fetch和decode,并且execute,只是不做writeback提交
猜的途径有两种:
一种是静态分支预测:编译器提供likely等提示
另一种是相对的运行时分支预测:处理器在运行时通过一定的算法作出判断
有结论是,越深的流水线,分支预测失败的罚时则越大
谓语判断
条件分支在编程上不可避免,能不能通过指令的角度来解决这个问题?
指令间的调度:乱序执行
前面提到,等待就是插入bubble不干活
有丰富打工经验的你肯定知道不会这样就算了
对,就是趁机让你干别的活
将bubble替换成有效的实际指令,这就是乱序执行
同样地,乱序分为两个层面:
编译器级别的生成指令乱序
处理器级别的执行指令乱序
这就没了?
已鸽