您的位置:首页 > 编程语言

Intel 平台编程总结----SIMD技术

2016-06-12 11:56 176 查看
SIMD是指单指令多数据技术,它已经成为Intel处理器的重要性能扩展。目前Intel处理器支持的SIMD技术包括MMX,SSE,AVX.
MMX提供了8个64bit的寄存器进行SIMD操作,SSE系列提供了128bit的8个寄存器进行SIMD指令操作。而最新的AVX指令则支持256bit的SIMD操作。
目前SIMD指令可以有四种方法进行使用分别是汇编语言,C++类,编译器Intrisincs和自动矢量化。我们用下面的求一个整数数组的和作为例子:

<n;i++)

<n;i++)

int SumAarray(int *buf,int N)

{

int i,sum=0;

for(i=0;i<N;i++)

   sum+=buf[i];

return sum;

}

方法一:自动矢量化

Intel编译器支持自动矢量化,通过选择特定的处理器选项/Q[a]{T|P|N|W|K},上面的代码可以采用下来命令进行编译:

icl /QxP/c /Fa test_vec.c

test_vec.c(4):(col.1)remark:LOOP WAS VECTORIZED.从上面的报告中可以看到第四行的循环自动向量化,还可以通过生成的汇编代码,看到使用了SIMD指令。

方法二:C++类

Intel C++编译器和C++类库中提供了一些新的C++类,这些类对应那些可以直接使用的SIMD指令(支持MMX,SSE,SSE2,不支持SSE3)的数据类型,使用时需要包含如下头文件:

#include <ivec.h>//MMX

#include <fvec.h>//SSE(also
include ivec.h)

#include <dvec.h>//SSE2(also
include fvec.h)

这些支持SIMD的向量类型采取下面的命名规则:前面用I和F分别表示是支持浮点还是整数SIMD指令,接下来是数字取值为8,16,32,64,表示组向量的基本元素大小。然后后面为字符串vec,最后的数组取值为8,4,2,1,表示组成向量的基本元素的个数。使用64bit的MMX技术的整数类包括I64vec1,I32vec2,I16vec4和I8vec8,而使用128bit的XMM寄存器的浮点类则包括F32vec4,F32vec1,F64vec2。SSE2中使用128bit的XMM寄存器,整数类包括:I128vec1,I64vec2,I32vec4,I16vec8,I8vec16,为了进一步区分封装的是有符号整数还是无符号整数,在那些整数之后也可以包含一个符号标志s或者u,比如Iu32vec4.

通过类的封装,程序员无须关心那些对于类的运算到底使用了哪些汇编指令或者SIMD intrinsic函数,应用易于阅读和编码,并且没有直接使用SIMD代码,在不同的处理器之间不需要任何改动,但是其缺点是无法访问所有的指令和数据类型的组合。

下面的代码给出了SumAarray采用C++类的实现。

点击(此处)折叠或打开

#include

int SumAarray(int *buf,int
N)

{

int i;

I32vec4 *vec4 = (I32vec4 *)buf;

I32vec4 sum(0,0,0,0);

for(i=0;i<N;i++)

 sum += vec4[i];

 return sum[0]+sum[1]+sum[2]+sum[3];

}

方法三:使用intrinsics

SIMD intrinsics有些类似于C语言中的函数,可以被其它的代码直接调用。可以像其它函数调用一样给它传递参数,Intel C++编译器支持SIMD intrinsics,并且可以针对intrinsics函数进行内联等优化。intrinsics能够被直接映射到一条或者多条SIMD指令以及其它汇编指令。至于寄存器分配、指令调度和寻址模式,则留给编译器处理。因此,相比汇编语言来说更容易使用。但是,对于生成指令的控制能力则更小。为了使用与SIMD技术相关的intrinsics,首先需要包含那些定义了数据类型和函数的头文件。

#include <mmintrin.h>//mmx

#include <xmmintrin.h>//sse

#include <emmintrin.h>//sse2

#include <pmmintrin.h>//sse3

这些头文件定义了一些数据类型对应于那些SIMD指令要使用的封装浮点数和整数,这些数据类型名以两个下划线开始:

__m64用于表示一个MMX寄存器,表示封装了8个8bit,4个16bit,2个32bit,1个64bit的整数;_

_m128用于SSE,表示封装了;4个32bit的单精度浮点数;

_m128d可以封装2个64bit的双精度浮点数;

_m128i用于表示128bit SIMD整数运算的XMM寄存器。

__m128,__m128d,__m128i的内存变量位于16byte的边界;

int SumArray(int *buf,int N)

{

 int i;

__m128i *vec128 = (__m128i *)buf;

__m128 sum;

sum = _mm_sub_epi32(sum,sum);

for(i=0;i<N/4;i++)

   sum = _mm_add_epi32(sum,vec128[i]);

sum = _mm_add_epi32(sum,_mm_srli_si128(sum,8));

sum = _mm_add_epi32(sum,_mm_srli_si128(sum,4));

return _mm_cvtsi128_si32(sum);

}

}

我们再来看一段代码,注意在循环体有一个条件语句,使用/QxP选项进行编译会发现Intel编译器并不会进行自动矢量化。

点击(此处)折叠或打开

#define SIZE 128

__decspec(align(16)) short int aa[SIZE],bb[SIZE],cc[SIZE],dd[SIZE];

void Branch_Loop(short int g)

{

  int i;

   for(i=0;i<SIZE;i++)

   {

     aa[i] = (bb[i]>0)?(cc[i]+2):(dd[i]+g);

   }

}

我们可以手工来实现上面的函数,通过使用SIMD指令来消除循环上的分支,同时可以一次完成8个处理,减少循环次数。__mm_cmpgt_epi16()函数可以在一次执行时完成8个比较操作,如果其中某个组元素大于0,则返回的XMM寄存器中对应的比特位全为1,否则全为0,。获得这些掩码之后就可以通过下面代码中的三条操作完成分支赋值了。

代码如下:

点击(此处)折叠或打开

#include <emmintrin.h>

#define TOVectorAddress(x) ((__m128i *)&(x))

void Branch_Loop(short int g)

{

  __m128i a,b,c,d,mask,zero,two,g_broadcast;

  int i;

 zero = _mm_set1_epi(16);

 two = _mm_set1_epi16(2);

 g_broadcast = _mm_set1_epi16(g);

 for(i=0;i<SIZE;i+=8)

 {

  b = _mm_load_si128(ToVectorAddress(bb[i]));

  c = _mm_load_si128(ToVectorAddress(cc[i]));

  d = _mm_load_si128(ToVectorAddress(dd[i]));

  c = _mm_add_epi16(c,tw0);

  d = _mm_add_epi16(d,g_broadcast);

  maks = _mm_cmpgt_epi16(b,zero);

 //注意下面三行代码,它完成了之前代码中的分支赋值操作,从而便于软件流水执行

  a = _mm_and_si128(c,mask);

  mask = _mm_andnot_epi16(mask,d);

  a = _mm_or_si128(a,mask);

   _mm_store_si128(ToVectorAddress(aa[i]),a);

 }

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  SIMD