CPU有很多硬件提升手段,其中较为常见的是超标量(superscalar)、流水线(pipelining)、超线程(Hyper-Threading)、多核(multi-core)、SIMD等,通常较难理解,本文以洗衣服为例尝试通俗理解这些概念.
假如把一个线程任务模拟成洗衣机器操作,那一个线程包含多个指令(处理上衣、处理裤子、处理袜子.....),一个指令又包含多个周期(取衣服、洗衣服、晾衣服......);CPU 就是那洗衣工厂,工厂里有取衣机器 A、洗衣机器 B、晾衣机器 C 、烫衣机器 D、衣服放回机器 E,一开始流程如下:
1.机器A取上衣--机器B洗上衣--机器C晾上衣--机器D烫上衣--机器E放回上衣
2.机器A取裤子--机器B洗裤子--机器C晾裤子--机器D烫裤子--机器E放回裤子
3.机器A取袜子--机器B洗袜子--机器C晾袜子--机器D烫袜子--机器E放回袜子
4.机器A取鞋子--机器B洗鞋子--机器C晾鞋子--机器D烫鞋子--机器E放回鞋子
5...
最开始 CPU 就是顺序执行如上1-2-3-.....步骤的,现在有好几筐(衣服、裤子、鞋子......)要洗,如何提升速度?
超标量(superscalar)
最开始朴素想法就是工厂内多买机器,譬如取上衣机器买 2 台,洗衣机器买 2 台..........
洗衣工厂就变成
1.机器A1取上衣--机器B1洗上衣--机器C1晾上衣--机器D1烫上衣--机器E1放回上衣
机器A2取裤子--机器B2洗裤子--机器C2晾裤子--机器D2烫裤子--机器E2放回裤子
2.机器A1取袜子--机器B1洗袜子--机器C1晾袜子--机器D1烫袜子--机器E1放回袜子
机器A2取鞋子--机器B2洗鞋子--机器C2晾鞋子--机器D2烫鞋子--机器E2放回鞋子
3.....
这样时间就能减半了
流水线(pipelining)
仔细看从取衣
到放回
一个流程里虽然每个机器都用到了,但任何一个时刻都只有一个机器在工作.
譬如:机器 A1/A2取完衣服后就空闲者一直到E1/E2放回衣服才继续工作,大部分时候都空闲着。
流水线就是不让任何一个机器空闲,没必要一个指令周期走完,可以继续执行其他指令。
T1 时刻:机器A1取上衣
T2 时刻:机器B1洗上衣+机器A1取裤子
T3 时刻:机器C1晾上衣+机器B1洗裤子+机器 A1 取袜子
T4 时刻:机器D1烫上衣+机器C1晾裤子+机器 B1 洗袜子+机器 A1 取鞋子
........
纵向看仍然是一个指令的完整周期(取衣服、洗衣服、晾衣服.....), 横向看会发现 A1 一直在工作被利用起来了,只要一直运行时间会发现 A1 ~ E1 各个机器都会被利用起来持续工作。
超线程(Hyper-Threading)
超标量(superscalar)和流水线(pipelining)都是线程里的优化方案,能让单线程程序跑的更快,而超线程(Hyper-Threading)就是不同线程间的优化方案了。
因为有的线程涉及和外部的数据交换,譬如需要等数据从内存乃至更慢的网络、磁盘装载到 L1、L2 chache,这时候可以让 CPU处理另一个线程。
继续类比就类似于洗衣工厂发现某箩筐 A 里的衣服较脏需要等洗衣液送过来才能继续洗,那可以保存箩筐A 的进度转而洗那些较干净无需等外部的某箩筐B里的衣服,等外部的洗衣液送过来后再处理箩筐A 里的衣服。
所以越是 IO 密集型应用,超线程效果就越大,譬如DBA 一般建议MySQL 的thread_running
参数设置为 cpu 核数*2
,这就是把超线程当成真正的核来用了。
超线程对 CPU 密集型应用就无用了,CPU 核数是多少就只能开多少任务。
日常应用来说超线程能提升 30%的吞吐量, 而这 30%的提升只是增加了 5%的额外电路,看起来性价比很高;但开启多线程会牺牲单线程的性能,而且现在大部人日常应用根本用不满CPU的那么多核和线程,所有有些 CPU(如MacBook的 M 系列 CPU)就没有超线程技术。
多核(multi-core)
多核(multi-core)很容易理解了,还是以洗衣服为例几个核就是额外再建几个洗衣工厂,当然成本也是跟着翻几倍。
SIMD
上面的例子都是假设机器一次只能处理一件任务,那能不能改造下机器(譬如增加机械臂)让机器 A 一次取 10 件而不是 1 件衣服,让机器 B 一次洗 10 个鞋子而不是 1 个?
SIMD就是类似思路,事实上这就是 SIMD 被叫做单指令流多数据流的原因,一次处理大量类似的数据,譬如多媒体数据、数据分析/AI领域 的矩阵数据。
譬如Python的Numpy库就是用了SIMD指令集 AVX 系列(AVX、AVX2乃至AVX512)来处理数据,所以哪怕没用GPU速度也很快。