上一讲里呢,我进一步为你讲解了CPU里的“黑科技”,分别是超标量(Superscalar)技术和超长指令字(VLIW)技术。python
超标量(Superscalar)技术可以让取指令以及指令译码也并行进行;在编译的过程,超长指令字(VLIW)技术能够搞定指令前后的依赖关系,使得一次能够取一个指令包。算法
不过,CPU里的各类神奇的优化咱们还远远没有说完。这一讲里,我就带你一块儿来看看,专栏里最后两个提高CPU性能的架构设计。它们分别是,你应该经常据说过的 超线程(Hyper-Threading)技术,以及可能没
有那么熟悉的 单指令多数据流(SIMD)技术。数据库
不知道你是否是还记得,在第21讲,我给你介绍了Intel是怎么在Pentium 4处理器上遭遇重大失败的。若是不太记得的话,你能够回过头去回顾一下。数组
那时我和你说过,Pentium 4失败的一个重要缘由,就是它的CPU的流水线级数太深了。早期的Pentium 4的流水线深度高达20级,然后期的代号为Prescott的Pentium 4的流水线级数,更是到了31级。超长的流水
线,使得以前咱们讲的不少解决“冒险”、提高并发的方案都用不上。浏览器
由于这些解决“冒险”、提高并发的方案,本质上都是一种 指令级并行(Instruction-level parallelism,简称IPL)的技术方案。换句话说就是,CPU想要在同一个时间,去并行地执行两条指令。而这两条指令呢,
本来在咱们的代码里,是有前后顺序的。不管是咱们在流水线里面讲到的流水线架构、分支预测以及乱序执行,仍是咱们在上一讲说的超标量和超长指令字,都是想要经过同一时间执行两条指令,来提高CPU的吞吐率bash
然而在Pentium 4这个CPU上,这些方法均可能由于流水线太深,而起不到效果。我以前讲过,更深的流水线意味着同时在流水线里面的指令就多,相互的依赖关系就多。因而,不少时候咱们不得不把流水线停顿下
来,插入不少NOP操做,来解决这些依赖带来的“冒险”问题。多线程
不知道是否是由于当时面临的竞争太激烈了,为了让Pentium 4的CPU在性能上更有竞争力一点,2002年末,Intel在的3.06GHz主频的Pentium 4 CPU上,第一次引入了 超线程(Hyper-Threading)技术。架构
什么是超线程技术呢?Intel想,既然CPU同时运行那些在代码层面有先后依赖关系的指令,会遇到各类冒险问题,咱们不如去找一些和这些指令彻底独立,没有依赖关系的指令来运行好了。那么,这样的指令哪里来
呢?天然同时运行在另一个程序里了。并发
你所用的计算机,其实同一个时间能够运行不少个程序。好比,我如今一边在浏览器里写这篇文章,后台一样运行着一个Python脚本程序。而这两个程序,是彻底相互独立的。它们两个的指令彻底并行运行,而不
会产生依赖问题带来的“冒险”。机器学习
然而这个时候,你可能就会以为奇怪了,这么作彷佛不须要什么新技术呀。如今咱们用的CPU都是多核的,原本就能够用多个不一样的CPU核心,去运行不一样的任务。即便当时的Pentium 4是单核的,咱们的计算机本
来也能同时运行多个进程,或者多个线程。这个超线程技术有什么特别的用处呢?
不管是上面说的多个CPU核心运行不一样的程序,仍是在单个CPU核内心面切换运行不一样线程的任务,在同一时间点上,一个物理的CPU核心只会运行一个线程的指令,因此其实咱们并无真正地作到指令的并行运行。
超线程可不是这样。超线程的CPU,实际上是把一个物理层面CPU核心,“假装”成两个逻辑层面的CPU核心。这个CPU,会在硬件层面增长不少电路,使得咱们能够在一个CPU核心内部,维护两个不一样线程的指令的状态信息。
好比,在一个物理CPU核心内部,会有双份的PC寄存器、指令寄存器乃至条件码寄存器。这样,这个CPU核心就能够维护两条并行的指令的状态。在外面看起来,彷佛有两个逻辑层面的CPU在同时运行。因此,超线
程技术通常也被叫做 同时多线程(Simultaneous Multi-Threading,简称SMT)技术 。
不过,在CPU的其余功能组件上,Intel可不会提供双份。不管是指令译码器仍是ALU,一个CPU核心仍然只有一份。由于超线程并非真的去同时运行两个指令,那就真的变成物理多核了。超线程的目的,是在一个
线程A的指令,在流水线里停顿的时候,让另一个线程去执行指令。由于这个时候,CPU的译码器和ALU就空出来了,那么另一个线程B,就能够拿来干本身须要的事情。这个线程B可没有对于线程A里面指令的
关联和依赖。
这样,CPU经过很小的代价,就能实现“同时”运行多个线程的效果。一般咱们只要在CPU核心的添加10%左右的逻辑功能,增长能够忽略不计的晶体管数量,就能作到这一点。
不过,你也看到了,咱们并无增长真的功能单元。因此超线程只在特定的应用场景下效果比较好。通常是在那些各个线程“等待”时间比较长的应用场景下。好比,咱们须要应对不少请求的数据库应用,就很适合
使用超线程。各个指令都要等待访问内存数据,可是并不须要作太多计算。
因而,咱们就能够利用好超线程。咱们的CPU计算并无跑满,可是每每当前的指令要停顿在流水线上,等待内存里面的数据返回。这个时候,让CPU里的各个功能单元,去处理另一个数据库链接的查询请求就是
一个很好的应用案例。
我这里放了一张个人电脑里运行CPU-Z的截图。你能够看到,在右下角里,个人CPU的Cores,被标明了是4,而Threads,则是8。这说明我手头的这个CPU,只有4个物理的CPU核心,也就是所谓的4核CPU。可是
在逻辑层面,它“装做”有8个CPU核心,能够利用超线程技术,来同时运行8条指令。若是你用的是Windows,能够去下载安装一个CPU-Z来看看你手头的CPU里面对应的参数。
在上面的CPU信息的图里面,你会看到,中间有一组信息叫做Instructions,里面写了有MMX、SSE等等。这些信息就是这个CPU所支持的指令集。这里的MMX和SSE的指令集,也就引出了我要给你讲的最后一个提
升CPU性能的技术方案, SIMD,中文叫做 单指令多数据流(Single Instruction Multiple Data)。
咱们先来体会一下SIMD的性能到底怎么样。下面是两段示例程序,一段呢,是经过循环的方式,给一个list里面的每个数加1。另外一段呢,是实现相同的功能,可是直接调用NumPy这个库的add方法。在统计两段
程序的性能的时候,我直接调用了Python里面的timeit的库。
python >>> import numpy as np >>> import timeit >>> a = list(range(1000)) >>> b = np.array(range(1000)) >>> timeit.timeit("[i + 1 for i in a]", setup="from __main__ import a", number=1000000) 32.82800309999993 >>> timeit.timeit("np.add(1, b)", setup="from __main__ import np, b", number=1000000) 0.9787889999997788 >>>
从两段程序的输出结果来看,你会发现,两个功能相同的代码性能有着巨大的差别,足足差出了30多倍。也难怪全部用Python讲解数据科学的教程里,每每在一开始就告诉你不要使用循环,而要把全部的计算都向量化(Vectorize)。
有些同窗可能会猜想,是否是由于Python是一门解释性的语言,因此这个性能差别会那么大。第一段程序的循环的每一次操做都须要Python解释器来执行,而第二段的函数调用是一次调用编译好的原生代码,所
以才会那么快。若是你这么想,不妨试试直接用C语言实现一下1000个元素的数组里面的每一个数加1。你会发现,即便是C语言编译出来的代码,仍是远远低于NumPy。缘由就是,NumPy直接用到了SIMD指令,可以并行进行向量的操做。
而前面使用循环来一步一步计算的算法呢,通常被称为 SISD,也就是 单指令单数据(Single InstructionSingle Data)的处理方式。若是你手头的是一个多核CPU呢,那么它同时处理多个指令的方式能够叫
做 MIMD,也就是 多指令多数据(Multiple Instruction Multiple Dataa)。
为何SIMD指令能快那么多呢?这是由于,SIMD在获取数据和执行指令的时候,都作到了并行。一方面,在从内存里面读取数据的时候,SIMD是一次性读取多个数据。
就以咱们上面的程序为例,数组里面的每一项都是一个integer,也就是须要 4 Bytes的内存空间。Intel在引入SSE指令集的时候,在CPU里面添上了8个 128 Bits的寄存器。128 Bits也就是 16 Bytes ,也就是说,一个
寄存器一次性能够加载 4 个整数。比起循环分别读取4次对应的数据,时间就省下来了。
在数据读取到了以后,在指令的执行层面,SIMD也是能够并行进行的。4个整数各自加1,互相以前彻底没有依赖,也就没有冒险问题须要处理。只要CPU里有足够多的功能单元,可以同时进行这些计算,这个加法
就是4路同时并行的,天然也省下了时间。
因此,对于那些在计算层面存在大量“数据并行”(Data Parallelism)的计算中,使用SIMD是一个很划算的办法。在这个大量的“数据并行”,其实一般就是实践当中的向量运算或者矩阵运算。在实际的程序开发
过程当中,过去一般是在进行图片、视频、音频的处理。最近几年则一般是在进行各类机器学习算法的计算。而基于SIMD的向量计算指令,也正是在Intel发布Pentium处理器的时候,被引入的指令集。当时的指令集
叫做 MMX,也就是Matrix Math eXtensions的缩写,中文名字就是 矩阵数学扩展。而Pentium处理器,也是CPU第一次有能力进行多媒体处理。这也正是拜SIMD和MMX所赐。
从Pentium时代开始,咱们能在电脑上听MP三、看VCD了,而不用专门去买一块“声霸卡”或者“显霸卡”了。没错,在那以前,在电脑上看VCD,是须要专门买可以解码VCD的硬件插到电脑上去的。而到了今
天,经过GPU快速发展起来的深度学习技术,也同样受益于SIMD这样的指令级并行方案,在后面讲解GPU的时候,咱们还会遇到它。
这一讲,咱们讲完了超线程和SIMD这两个CPU的“并行计算”方案。超线程,实际上是一个“线程级并行”的解决方案。它经过让一个物理CPU核心,“装做”两个逻辑层面的CPU核心,使得CPU能够同时运行
两个不一样线程的指令。虽然,这样的运行仍然有着种种的限制,不少场景下超线程并不必定能带来CPU的性能提高。可是Intel经过超线程,让使用者有了“占到便宜”的感受。一样的4核心的CPU,在有些状况下能
够发挥出8核心CPU的做用。而超线程在今天,也已经成为Intel CPU的标配了。
而SIMD技术,则是一种“指令级并行”的加速方案,或者咱们能够说,它是一种“数据并行”的加速方案。在处理向量计算的状况下,同一个向量的不一样维度之间的计算是相互独立的。而咱们的CPU里的寄存
器,又能放得下多条数据。因而,咱们能够一次性取出多条数据,交给CPU并行计算。
正是SIMD技术的出现,使得咱们在Pentium时代的我的PC,开始有了多媒体运算的能力。能够说,Intel的MMX、SSE指令集,和微软的Windows 95这样的图形界面操做系统,推进了PC快速进入家庭的历史进程。