配色: 字号:
Intel 平台编程总结----SIMD技术
2015-05-04 | 阅:  转:  |  分享 
  
SIMD是指单指令多数据技术,它已经成为Intel处理器的重要性能扩展。目前Intel处理器支持的SIMD技术包括MMX,SSE,AVX.MMX提供了8个64bit的寄存器进行SIMD操作,SSE系列提供了128bit的8个寄存器进行SIMD指令操作。而最新的AVX指令则支持256bit的SIMD操作。目前SIMD指令可以有四种方法进行使用分别是汇编语言,C++类,编译器Intrisincs和自动矢量化。我们用下面的求一个整数数组的和作为例子:intSumAarray(intbuf,intN){inti,sum=0;for(i=0;i//MMX#include//SSE(alsoincludeivec.h)#include//SSE2(alsoincludefvec.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,比如I?vec4.通过类的封装,程序员无须关心那些对于类的运算到底使用了哪些汇编指令或者SIMDintrinsic函数,应用易于阅读和编码,并且没有直接使用SIMD代码,在不同的处理器之间不需要任何改动,但是其缺点是无法访问所有的指令和数据类型的组合。下面的代码给出了SumAarray采用C++类的实现。#includeintSumAarray(intbuf,intN){inti;I32vec4vec4=(I32vec4)buf;I32vec4sum(0,0,0,0);for(i=0;i?//MMX#include?//SSE(includemmintrin.h)#include?//SSE2(includexmmintrin.h)#include?//SSE3(includeemmintrin.h)这些头文件定了一些数据类型对应于那些SIMD指令要适应的浮点数和整数。这些数据类型名以两个下划线开始:__m64用于表示一个MMX寄存器的内容,用于64bi的MMX整数指令,可以封装正8个8bit,4个16bit,2个32bit和一个64bit的整数。__m128用于SSE,表示封装了四个32bit的单精度浮点数的SSE寄存器。__m128d可以封装2个64bit的双精度浮点数;www.sqxxtsyey.com__m128i用于表示支持128bi的内存变量,位于16B的边界。声明为__m64的内存变量位于8B的边界。注意:定义的intrinsics数据类型,并不是一个标准的C数据类型,所以,这些变量只能用于赋值语句的左边,返回值或者函数调用参数,不能进行加法减法等操作。SIMDintrinsics的SumArray函数的实现:intSumArray(intbuf,intN){inti;__m128ivec128=(__128i)buf;__m128isum;sum=_mm_sub_epi32(sum,sum);//settozero这个方法其它SMD置零操作谁更快呢?for(i=0;i_的格式,函数名以_mm开头,然后表示函数要执行的SIMD指令,比如,上面的add,sub,srli分别表示加法,减法,以为运算,最后是后缀,后缀的一部分给出了药进行运算的函数的数据范围,其中p表示封装操作,s表示变量操作,而ep表示扩展操作,接下来的部分表示要进行运算的数据类型,其中s表示单精度操作,d表示双精度操作,i表示整数操作,u表示无符号整数,数字表示整数的比特数。所以的这些intrinsics函数可以通过Intel提供个一个软件查询,名称是:Intel_Intrinsics_Guide-windows再来看一个例子:下面的这段代码,由于循环体当中有一个条件语句,使用/QxP选项进行编译会发现,Intel编译器并不会进行自动矢量化。

点击(此处)折叠或打开

#defineSIZE128

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

voidBranch_Loop(short?int?g)

{

??int?i;

???for(i=0;i
???{

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

???}

}

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

点击(此处)折叠或打开

#include?

#defineTOVectorAddress(x)?((__m128i?)&(x))



voidBranch_Loop(short?int?g)

{

??__m128ia,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
?{

??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);

?}

}





献花(0)
+1
(本文系3939木目39首藏)