SIMD

来源:互联网 发布:dnf如果合约到期知乎 编辑:程序博客网 时间:2024/06/06 07:38

一、SIMD

SIMD单指令流多数据流(SingleInstruction Multiple Data,SIMD)是一种采用一个控制器来控制多个处理器,同时对一组数据(又称数据向量)中的每一个分别执行相同的操作从而实现空间上的并行性的技术。在微处理器中,单指令流多数据流技术则是一个控制器控制多个平行的处理微元,例如IntelMMXSSEAVX以及AMD3D Now!技术,本文只介绍IntelSIMD技术。

二 、MMX

MMX是由英特尔开发的一种SIMD多媒体指令集,共有57条指令。它于1996年集成在英特尔奔腾(Pentium) MMX处理器上,以提高其多媒体数据的处理能力。

优点是增加了处理器关于多媒体方面的处理能力,缺点是占用浮点数寄存器进行运算(64MMX寄存器实际上就是浮点数寄存器的别名)以至于MMX指令和浮点数操作不能同时工作。为了减少在MMX和浮点数模式切换之间所消耗的时间,程序员们尽可能减少模式切换的次数,也就是说,这两种操作在应用上是互斥的。后来英特尔在此基础上发展出SSE指令集。现在新开发的程序不再仅使用MMX来优化软件执行效能,而是改使用如SSEAVX等更容易优化效能的新一代多媒体指令集,不过目前的处理器仍可以执行针对MMX优化的较早期软件。

MMX寄存器,称作MM0MM7,实际上就是处理器内部80比特字长的浮点寄存器栈st(0)st(7)的尾数部分(64比特长)的复用。由于浮点栈寄存器的高16位未被MMX技术使用,因此这16位都置为1,因此从栈寄存器的角度看,其浮点值为NaNInfinities,这可用于区分寄存器是处于浮点栈状态还是MMX状态.作为MMX寄存器都是直接访问。利用了装配数据类型(packeddata type)的概念,每个MMX寄存器的64比特字长可以看作是232位整数、或者416位整数、或者88位整数,从而可以执行整数SIMD运算。这对于1990年代中期的2D3D计算的加速还是很有意义的,因为当时的计算机的图形处理器(GPU)还很不发达。但现在MMX整数SIMD运算对于图形运算来说是多余的技术了。不过MMX的饱和算术运算(saturationarithmeticoperations)对于一些数字信号处理应用还是有用的

 

三、 SSE

MMX技术之后,Intel又于1999年在Pentium-III处理器上推出SSE技术,引入了新的128比特宽的寄存器集 (register file),称作XMM0XMM7。这些XMM寄存器用于4个单精度浮点数运算的SIMD执行,并可以与MMX整数运算或x87浮点运算混合执行。 2001年在Pentium 4上引入了SSE2技术,进一步扩展了指令集,使得XMM寄存器上可以执行8/16/32位宽的整数SIMD运算或双精度浮点数的SIMD运算。这使得 SIMD技术基本完善。

SSE(StreamingSIMD Extensions)是英特尔在其计算机芯片Pentium III中引入的指令集,是继MMX的扩充指令集。SSE指令集提供了 70条新指令

 

 SSE寄存器

SSE 加入新的 8 28 位缓存器(XMM0XMM7)。除此之外还有一个新的 32 位的控制/状态缓存器(MXCSR)。不过只能在 64位的模式下才能使用额外8个缓存器。

每个缓存器可以容纳 432位单精度浮点数,或是2 64位双精度浮点数,或是 432位整数,或是 816位短整数,或是 16个字符。整数运算能够使用正负号运算。而整数 SIMD运算可能仍然要与 864MMX缓存器一起执行。

因为操作系统必须要在进程切换的时候保护这些128位的缓存器状态,除非操作系统去启动这些缓存器,否则默认值是不会去启用的。这表示操作系统必须要知道如何使用FXSAVEFXRSTOR指令才能储存x87 SSE缓存器的状态。而在当时IA-32的主流操作系统很快的都加入了此功能。

由于 SSE加入了浮点支持,SSE就比MMX更加常用。而SSE2加入了整数运算支持之后让SSE更加的有弹性,当MMX变成是多余的指令集,SSE指令集甚至可以与MMX并行运作,在某些时候可以提供额外的性能增进。

第一个支持 SSE CPU Pentium III,在FPUSSE之间共享执行支持。当编译出来的软件能够交叉的同时以FPUSSE运作,PentiumIII并无法在同一个周期中同时执行FPUSSE。这个限制降低了指令管线的有效性,不过XMM缓存器能够让SIMD与纯量浮点运算混合执行,而不会因为切换MMX/浮点模式而产生性能的折损。

 

数据类型

Intrinsic使用的数据类型和其寄存器是对应,有

·        64 MMX指令集使用

·        128 SSE指令集使用

·        256 AVX指令集使用

甚至AVX-512指令集有512位的寄存器,那么相对应Intrinsic的数据也就有512位。
具体的数据类型及其说明如下:

1.   **__m64** 64位对应的数据类型,该类型仅能供MMX指令集使用。由于MMX指令集也能使用SSE指令集的128位寄存器,故该数据类型使用的情况较少。

2.   **__m128 / __m128i / __m128d**这三种数据类型都是128位的数据类型。由于SSE指令集即能操作整型,又能操作浮点型(单精度和双精度),这三种数据类型根据所带后缀的不同代表不同类型的操作数。__m128是单精度浮点数,__m128i是整型,__m128d是双精度浮点数。

3.   256512的数据类型和128位的类似,只是存放的个数不同,这里不再赘述。

 

知道了各种数据类型的长度以及其代码的意义,那么它的表现形式到底是怎么样的呢?看下图


__m128i yy;

yy__m128i型,从上图可以看出__m128i是一个联合体(union),根据不同成员包含不同的数据类型。看其具体的成员包含了8位、16位、32位和64位的有符号/无符号整数(这里__m128i是整型,故只有整型的成员,浮点数的使用__m128)。而每个成员都是一个数组,数组中填充着相应的数据,并且根据数据长度的不同数组的长度也不同(数组长度 = 128 / 每个数据的长度(位))。在使用的时候一定要特别的注意要操作数据的类型,也就是数据的长度,例如上图同一个变量yy当作432位有符号整型使用时其数据是:0010241024;但是当做64位有符号整型时其数据为:04398046512128,大大的不同

 

Intrinsic 函数的命名

Intrinsic函数的命名也是有一定的规律的,一个Intrinsic通常由3部分构成,这个三个部分的具体含义如下:

1.   第一部分为前缀_mm,表示是SSE指令集对应的Intrinsic函数。_mm256_mm512AVX,AVX-512指令集的Intrinsic函数前缀,这里只讨论SSE故略去不作说明。

2.   第二部分为对应的指令的操作,如_add_mul_load等,有些操作可能会有修饰符,如loadu将未16位对齐的操作数加载到寄存器中。

3.   第三部分为操作的对象名及数据类型,_ps packed操作所有的单精度浮点数;_pd packed操作所有的双精度浮点数;_pixxxx为长度,可以是8163264packed操作所有的xx位有符号整数,使用的寄存器长度为64位;_epixxxx为长度)packed操作所有的xx位的有符号整数,使用的寄存器长度为128位;_epuxx packed操作所有的xx位的无符号整数;_ss操作第一个单精度浮点数。....

将这三部分组合到以其就是一个完整的Intrinsic函数,如_mm_mul_epi32对参数中所有的32位有符号整数进行乘法运算。

SSE指令集对分支处理能力非常的差,而且从128位的数据中提取某些元素数据的代价又非常的大,因此不适合有复杂逻辑的运算。

 

后续版本

SSE2 IntelPentium 4处理器的最初版本中引入的。SSE2指令集添加了对64位双精度浮点数的支持,以及对整型数据的支持,也就是说这个指令集中所有的MMX指令都是多余的了,同时也避免了占用浮点数寄存器。这个指令集还增加了对CPU快取的控制指令。Intel后来在其Intel 64架构中也增加了对x86-64的支持。

SSE3 IntelPentium 4处理器的 Prescott核心中引入的第三代SIMD指令集。这个指令集扩展的指令包含寄存器的局部位之间的运算,例如高位和低位之间的加减运算;浮点数到整数的转换,以及对超线程技术的支持

SSSE3Intel针对SSE3指令集的一次额外扩充,最早内建于Core2 Duo处理器中。

SSE4IntelPenryn核心的Core 2 DuoCore2 Solo处理器时,新增的47条新多媒体指令集,并且现在更新至SSE4.2

AVXSandy BridgeLarrabee架构新指令集,Intel的微架构也进入了全速发展的时期,在20104月结束的IDF峰会上Intel公司就发布了2010年的RoadMap20111Intel发布全新的处理器微架构Sandy Bridge,其中全新增加的指令集也将带来CPU性能的提

升。AVX(AdvancedVector Extensions)IntelSSE延伸架构,如IA16IA32般的把缓存器XMM 128bit提升至YMM 256bit,以增加一倍的运算效率。此架构支持了三运算指令(3-OperandInstructions),减少在编码上需要先复制才能运算的动作。在微码部分使用了LES LDS这两少用的指令作为延伸指令Prefix



SSE sample vec_add.cpp

#include<stdio.h>#include<stdlib.h>#include<math.h>#include<time.h>#include<sys/time.h>#include<stdbool.h>#include<xmintrin.h>//SSE指令需要的头文件#define N 4*100000float op1[N] __attribute__((aligned(32)));//内存对齐float op2[N] __attribute__((aligned(32)));float result1[N] __attribute__((aligned(32)));float result2[N] __attribute__((aligned(32)));void init(){    for(int i = 0;i < N; i++){        op1[i] = (float)rand()/(float)RAND_MAX;        op2[i] = (float)rand()/(float)RAND_MAX;    }}void checkResult(int debug){    bool isSame = true;    for(int i = 0;i < N; i++)    {        if (debug){            printf("%lf     %lf\n", result1[i], result2[i]);        }        else{            if (fabs(result1[i] - result2[i]) > 0.000001){                isSame = false;                break;            }        }    }    if (!debug) {        if (isSame)            printf("Result is Same\n");        else            printf("Result is not same\n");    }}void add1(){    for(int i = 0; i < N;i++)        result1[i] = op1[i] + op2[i];}void add2(){    __m128  a;    __m128  b;    __m128  c;    for(int i = 0; i < N;i = i + 4)    {//从op1+i执向的内存中,加载4个float数到寄存器        a = _mm_load_ps(op1 + i);         b = _mm_load_ps(op2 + i);        c = _mm_add_ps(a, b);//并行计算加4个float数据//将寄存器中的 4个float数存储到c指向内存中        _mm_store_ps(result2 + i, c);     }}int main(int argc, char* argv[]){    init();    srand((unsigned int)time(NULL));    printf("Add a vector:\n");    struct timeval start;    struct timeval end;    gettimeofday(&start,NULL);    add1();    gettimeofday(&end,NULL);    float time_use=1000.0*(float)(end.tv_sec-start.tv_sec)+(float)(end.tv_usec-start.tv_usec)/1000.0;    printf("add a vector Time use =%f(ms)\n",time_use);    printf("\n");    printf("Add a vector with SSE instructions:\n");    gettimeofday(&start,NULL);    add2();    gettimeofday(&end,NULL);    time_use=1000.0*(float)(end.tv_sec-start.tv_sec)+(float)(end.tv_usec-start.tv_usec)/1000.0;    printf("add a vector with sse Time use =%f(ms)\n",time_use);    printf("\n");    checkResult(0);    //test();    return 0;}

编译:

g++ vec_add.cpp -msse -o vec_add

执行结果:


 

四、自动向量化

使用SSE、AVX的指令可以提高CPU的计算性能,但是学习和使用SIMD指令都需要成本。自动向量话只需要在编译时加入自动向量话指令,当然性能和使用SIMD指令要稍差些。

自动向量化编译选项:(gcc编译器)

          -O3(使用O3默认打开自动向量化)

         -ftree-vectorize(打开自动向量化)

         -ftree-vectorizer-verbose=n(编译时输出向量化信息,n是整数,3和9常用)

 

 

 

 

 

参考:

1https://software.intel.com/sites/landingpage/IntrinsicsGuide/#

2http://blog.csdn.net/conowen/article/details/7255920

3https://gcc.gnu.org/projects/tree-ssa/vectorization.html#using


0 0
原创粉丝点击