一:名词解释
Flynn分类法
Flynn于1972年提出了计算平台的Flynn分类法,主要根据指令流和数据流来分类。按照Flynn分类法,计算平台共分为四种类型。
1.单指令流单数据流机器(SISD)
2.单指令流多数据流机器(SIMD)
3.多指令流单数据流机器(MISD)
4.多指令流多数据流机器(MIMD)
标量处理器
标量处理器是一种最简单的计算机处理器类型。这类处理器在同一时间内只处理一条数据(整数或浮点数)。标量处理器是一种单指令流单数据流(SISD)处理器。
向量处理器
当今大多数商业 CPU 都包括一些向量处理器指令,较为典型的是 SIMD。从运算上说,标量机只是一个数一个数地进行计算,而向量机则能够对一批数据同时进行加工处理。因此,向量机比标量机的运算速度快,更适合于演算数据量多的大型科学、工程计算问题。
并行
不同于并发的概念,并发偏重于多个任务交替执行,而多个任务之间有可能还是串行。真正意义上的“同时执行”。同时做很多事情。
SIMD
读做(sim-dee),单指令流多数据流(英语:Single Instruction Multiple Data,缩写:SIMD)是一种采用一个控制器来控制多个处理器,同时对一组数据(又称“数据向量”)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。实现这种架构的并行处理机,每次都执行相同的指令,对不同的数据进行处理。这种计算机适合处理矩阵计算。
keypoints:
1.SIMD结构的CPU有多个执行部件,但都在同一个指令部件的控制之下,中央控制器向各个处理单元发送指令,整个系统只要求有一个中央控制器,只要求存储一份程序,所有的计算都是同步的。
2.以加法指令为例,单指令流单数据流(SISD)型CPU对加法指令译码后,执行部件先访问主存,取得第一个操作数,之后再一次访问主存,取得第二个操作数,随后才能进行求和运算;而在SIMD型CPU中,指令译码后,几个执行部件同时访问主存,一次性获得所有操作数进行运算。
3.SIMD不是一种具体的指令格式,经历了mmx->sse->avx这样的发展趋势,但它们都属于SIMD,SIMD更像是一种满足某种规范的技术架构总称。
SSE
Intel在1999年引入了SSE指令,SSE是"Streaming SIMD Extention"(流SIMD扩展)的缩写,SSE是Streaming SIMD Extensions(流SIMD扩展)的缩写。是由 Intel公司,在1999年推出Pentium III处理器时,同时推出的新指令集。SSE是一种SIMD指令集。SSE有8个128位寄存器,XMM0 ~XMM7。这些128位元的寄存器,可以用来存放四个32位的单精确度浮点数。SSE的浮点数运算指令就是使用这些寄存器。
keypoints:
-Intel的初代SIMD指令集是MMX,Multi-Media Extension, 即多媒体扩展,因为它的首要目标是为了支持MPEG视频解码。后来Intel进一步实现了SSE, SSE2~SSE4指令集,给了他们单独的寄存器,之后MMX就停掉了。
-SSE包含70条新的指令,其中大部分指令用于单精度 浮点数据。当对多个数据对象执行完全相同的操作时,SIMD指令可以大大提高性能。
-SSE随后被英特尔扩展为SSE2,SSE3,SSSE3和SSE4。-SSE功能历经几代,最新的版本为AVX(advanced vector extension)
-SSE中大部分指令要求地址是16byte对齐的。-要能够使用 Intel 的 SIMD 指令集,不仅需要当前 Intel 处理器的硬件支持,还需要编译器的支持。
-由于SIMD指令有多个版本,每个版本支持的指令集不同。所以如果你的软件要支持更多的CPU,就要在使用SIMD指令之前知道当前指令运行所在的CPU是否支持这条指令。
-SSE发展到SSE5戛然而止。这里有故事,2007年8月,AMD抢先宣布了SSE5指令集(之前从SSE到SSE4均为Intel制定),Intel火大,转而另起炉灶,2008年4月,Intel公布了AVX指令集规范,随后开始不断进行更新。
二:C++如何开发SIMD友好的代码?
使用SIMD技术的开发方法业内有如下几种:
1.使用著名的IPP库,IPP的全称是Intel Integrated Performance Primitives
2.方法是使用编译器的自动向量化(Auto-vectorization)支持。
3.使用编译器指示符(compiler directive),如使用英特尔的C/C++编译器(ICC)编译如下代码,那么ICC便会对#pragma simd指示符下面的for循环做向量化
4.使用Cilk技术。
5.使用编译器的内建函数(intrinsic),例如要使用要使用SSE3,#include,如果不关心使用那个版本的SSE指令,则可以包含所有#include
6.直接使用汇编语言编写汇编函数,C++调用汇编函数
编写高效程序需要做到以下几点:
第一,必须选择一组适当的算法和数据结构。
第二,必须编写出编译器能够有效优化以转换成高效可执行代码的源代码。
参考:http://sci.tuomastonteri.fi/programming/sse
三:从编译到执行,发生了什么事情?
我们刚才提到了C++代码,也提到了指令集。
提到了如何写SIMD友好的C++代码和SSE指令有多神奇。
可是,这一切是怎么发生的呢?
从C++源代码编译生成可执行代码,到CPU运行load到内存的这些代码。
这中间,发生了什么事情?
我们来聊聊CPU指令集和GCC编译的那点儿事儿。
先来温习一下计算机体系结构的基础知识:
指令 = 操作码 + 操作数
操作码:表示指令要完成的工作,如存数,取数。
操作数:操作对象的内容或者所在的单元格地址。
计算机工作的过程实际上是快速的执行指令的过程,当计算机在工作时,有两种信息流在流动。一种是数据流,通常是各种原始数据、中间结果等。一种是控制流,是由各种控制指令构成的。
指令的执行过程:
1.取指令,从内存储器取出指令到指令寄存器。
2.分析指令,对指令寄存器中存放的指令进行分析,由译码器对操作码进行译码。将指令的操作码转换成相应的控制电信号,并由地址码确定操作数的地址。
3.执行指令。由操作控制线路发出的完成该操作所需的一系列控制信息。用以完成该指令所需的操作。
4.为执行下一条指令做准备。形成下一条指令的地址。指令计数器指向存放下一条指令的地址,最后控制单元将执行结果写入内存。
上述完成一条指令的执行过程叫做一个“机器周期”;计算机在运行时,CPU从内存读取一条指令到CPU内执行,指令执行完,再从内存读取下一条指令到CPU执行。CPU不断的取指令、分析指令、执行指令,再取下一条指令,这就是程序的执行过程。
再看高级语言编译过程,是将源代码转换为机器可认识代码的过程。编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。
C/C++源代码编译成相应平台下的可执行文件需要经过如下步骤:
1、预处理
2、编译
3、汇编
4、链接
接下来我们探索一下C++编译的机器码如何和SIMD挂上了钩的?
首先,需要被执行的机器码先要被OS调度到内存之中, 程序执行时, 机器码依次经过了Memory--Cache--CPU fetch, 进入CPU流水线, 接着就要对它进行译码了, 译码工作生成的是CPU内部数据格式, 微码。特别需要注意的是:CPU不需要任何形式的存储介质去存储指令集, 因为"译码"这个步骤就是在对指令集里规范的机器码做解码。具体的指令编程机器码后就会变成数字电路的开关信号。其中某几段会作为控制信号,控制其他部分的数据走不同的电路以执行运算。
关键点就在这里了,从汇编到机器码这步是汇编程序翻译的。汇编程序知道某条指令要翻译成什么样的机器码。汇编的前提是一定要有格式,支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 其实都是指令集规范的内容, 如果写错了, 就无法翻译成机器码。
比如C++编译后的汇编文件中某条支持SSE的某条汇编语句:
movaps %xmm1, %xmm4
翻译成如下的机器码:
26 66 c7 84 c8 44 33 22 11 78 56
到真正执行的层面,CPU将指令(一个由0,1构成的字符串)输入译码电路,译码电路根据指令集的描述,生成各种控制信号。
控制整体晶体管逻辑电路开始工作。这个机器码就是SIMD的机器码,在单个时钟周期里能够并行的从内存中读取和计算数据。
FAQ:
1.ClickHouse怎样用到SIMD技术?
目前Clickhouse通过编译器的内建函数(intrinsic)开发的方式来利用到SIMD技术。
2. ClickHouse为什么不用更牛逼的MIMD ?
MIMD和SIMD只是体系结构的两种概念,并不是说采用SIMD支持的指令集就无法使用MIMD操作。实现SIMD概念的SSE3(举例),就支持某些MIMD操作。另外,目前笔者也没有发现有C++能够支持MIMD机器的优化。
注:由于每个执行单元的指令流都是相同的,SIMD模式将指令的获取时间均摊到每一个执行单元。但是,当指令流出现分支,指令就会被序列化。而MIMD模式的设计主要是为了处理不同指令流,当指令流出现分支,它不需要对线程进行阻塞。然而它需要更多指令存储以及译码单元,这就意味着硬件需要更多的硅,同时,为了维持多个单独的指令序列,它对指令带宽的需求也非常的高。一般使用SIMD与MIMD的混合模式才是最好的方案。用MIMD的模式处理控制流,用SIMD的模式处理大数据,在CPU上使用SSE/MME/AVX指令扩展集时就是采用的SIMD与MIMD的混合模式
3.如何编写可以自动矢量化的代码?
参考:http://www.jianshu.com/p/186339c16e8c
这个例子说明,现在处理器具备相当的计算能力,但是我们需要按照特定程式化的方式来编写程序,就可以将这些能力诱发出来。
题外话:
C++语言之父Bjarne在“2016 C++及系统软件技术大会”上。做了题为《What C++ is and what it will become》的演讲。
what is C++ --
他用两行话描述了C++是什么:
1.“Direct map to hardware” -- 直接映射到硬件
2.“Zero overhead abstraction” -- 0负担抽象
对于1,C++的字节对齐以及多指令集的支持,使之能够直接翻译对应到CPU的指令。C++的数据类型也可以直接对应到CPU支持的数据类型。无需进行转换。可谓效率奇高。
对于2,今天的JAVA、C#都有抽象,但有些语言为了抽象付出的代价很大,资源消耗多,运行速度慢,C++基本0负担,无额外开销。
what it will become --
提到了10项内容,第8项提到了在语言层面支持SIMD向量和并行化算法。
期待语言层面的SIMD支持和并行算法封装。这样,在编写能够支持SIMD优化代码时,可能就不用关注太多的细节了。
作者:_金科
链接:https://www.jianshu.com/p/6b1bbbefbb70
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。