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);
}
}
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);
}
}
相关文章推荐
- YUV转RGB(NV21-ARGB)的Neon优化代码
- YUV转RGB(NV21-ARGB)的Neon优化代码
- NEON在S2平台图像处理中的简单应用
- 利用AVX指令集实现矩阵乘法
- [SIMD]单指令多数据指令集(一)——SIMD简介
- [SIMD]单指令多数据指令集(二)—— SIMD指令集在非对称算法中的应用
- SIMD 编程的优势
- SIMD(MMX/SSE/AVX)变量命名规范心得
- 检查机器cpu是否支持 Intel dpdk 1.7
- ARM Neon 列子 - Vector Add
- ARM Neon 简介
- ARM和NEON指令 very gooooooood.............
- XNA Math Library 及 相关整理
- SSE 加速运算例子详解:乘法、加法、平方、最小值、最大值、与操作
- emmintrin.h文件解析
- YUV转RGB(NV21-ARGB)的Neon优化代码
- 图像算法的工程优化技术
- GCC检测CUP及编译环境是否支持MMX&SSE
- vc2010 sse指令优化效果明显
- mingw(gcc)编译libjpeg-turbo