使用英特尔编译器进行自动向量化

使用英特尔编译器进行自动向量化

自动向量化是英特尔编译器提供的一个可以自动的使用SIMD指示的功能。 在处理数据时, 编译器自动选择MMX™, Intel® Streaming SIMD 扩展(Intel® SSE, SSE2, SSE3 和SSE4)等指令集, 对数据进行并行的处理。 使用编译器提供的自动向量化功能是提高程序性能的一个非常有效的手段。自动向量化在IA-32和Intel® 64的平台上均提供很好的支持。

英特尔编译器提供的自动向量化相关的编译选项如下所示。”/Q”开头的选项是针对Windows平台的, “-“开头的选项是针对Linux*和Mac平台的。

-x, /Qx
按照该选项指定的处理器类型生成相应的优化代码。 比如-xSSE3, 该选项指定编译器生成Intel? SSE3指令的代码。 又比如-xSSE3_ATOM, 该选项针对Intel? Atom? 处理器进行优化。

-ax, /Qax
如果指定该选项, 在生成的单一目标文件中, 不但会生成专门针对指定的处理器类型进行优化的代码, 同时也生成通用的IA-32架构的代码。 该选项主要是为了生成代码的兼容性考虑。

-vec, /Qvec
打开或者关闭编译器的向量化优化。 默认情况下自动向量化是打开的。

-vec-report, /Qvec-report
该选项用户控制在编译过程中产生的向量化消息报告。

编译器提供的自动向量化优化默认情况下是打开的。 在编译过程中我们可以使用-vec-report选项来打开向量化诊断消息报告。 这样编译器可以告诉我们有哪些循环被向量化了, 有哪些循环没有被向量化已经无法向量化的原因。

在编译程序的过程中, 有时候我们会发现编译器报告说某个循环无法被向量化。 很多时候无法向量化的原因都是因为循环中存在的变量依赖关系。 有时候我们可以修改程序来消除这种依赖关系,有的时候我们可以使用编译器提供的一些编译指示来显示的告诉编译器如何处理这种依赖关系。 即使在某个循环已经可以被自动向量化的时候, 使用编译器提供的对向量化的语言支持和编译指示还可以提高编译器向量化的效率, 提高程序执行的性能。

下面我们来详细解释一下编译器提供的编译指示以及这些指示对编译器编译的影响。

在Intel编译器中, 我们提供下面这样一些对自动向量化的语言支持和编译指示。

__declspec(align(n))
指导编译器将变量按照n字节对齐
__declspec(align(n,off))
指导编译器将变量按照n字节再加上off字节的编译量进行对齐
restrict
消除别名分析中的二义性
__assume_aligned(a,n)
当编译器无法获取对齐信息时,假定数组a已经按照n字节对齐

#pragma ivdep
提示编译器忽略可能存在的向量依赖关系
#pragma vector {aligned|unaligned|always}
指定向量化的方式
#pragma novector
指定不做向量化

下面我们就这些编译指示做一些详细的了解。

  • __declspec(align(n))

首先我们来看看如何使用__declspec(align(n))来进行自动向量化。该指示主要是用来告诉编译器一个变量的对齐方式。 当一个变量的对齐方式为16字节对齐时, 生成的向量化指令是最高效的。 当编译器不知道一个变量的对齐方式的时候, 他可能没有办法对该变量的使用进行向量化, 或者编译器会不得不生成条件语句来进行有条件的向量化。 我们来看一下下面这个例子。

File: vec1.c
char a[];
void vec1(void)
{
int i;
for(i=0;i<1024;i++)
a[i] = 1;
}

使用下面命令编译该程序:
icl vec1.c -O2 -c /Qvec-report:3
这时编译器输出:

vec1.c
C:/test/vec1.c(10) (col. 2): remark: LOOP WAS VECTORIZED.

我们可以看到编译器对上面这个程序进行了自动向量化。 但是实际上, 由于编译器不知道a的对齐字节数, 所以他对变量a数据进行了一些条件处理。 我们可以在编译的时候加上-S选项来查看编译器生成的汇编代码。 实际上, 编译器对该程序做了如下处理:

temp = a&0x0f;
if(temp != 0)
{
temp = 16-temp;
for(i=0;i<temp;i++) a[i] = 1;
}
/*下面是对齐的访问*/
for(i=temp;i<1024;i++) a[i] = 1;

如果我们指定了a的对齐方式, 比如我们使用__declspec(align(16))来定义变量a, 如下所示:

__declspec(align(16)) char a[];
void vec1(void)
{
int i;
for(i=0;i<1024;i++)
a[i] = 1;
}

对于上面这段程序, 编译器做了自动向量化。 如果我们检查生成的汇编代码可以发现,对于a的对齐方式的条件处理代码已经不见了。
正确使用__declspec(align(n))以及其他align相关编译指示可以帮助编译器进行自动向量化以及提高编译器自动向量化的效率。

  • restrict

使用restrict关键字可以显式的告诉编译器取消指针之间的二义性。 我们还是用下面这个例子来说明restrict的使用。

File: vec2.c

void vec2(char* a, char* b, int n)
{
int i;
for(i=0;i<n;i++)
a[i] = b[i];
}

使用以下命令进行编译:
icl vec2.c -O2 -c /Qvec-report:3
编译器输出:

vec2.c
C:/test/restrict.c(5) (col. 2): remark: LOOP WAS VECTORIZED.
C://test/restrict.c(5) (col. 2): remark: loop skipped: multiversioned.

此时编译器输出multiversioned向量化代码, 对同一个循环产生了多个版本的实现。实际上编译器对代码进行了条件处理, 如下所示:

void vec2(char* a, char* b, int n)
{
int i;
if(a+n<b || b+n<a)
for(i=0;i<n;i++) a[i] = b[i]; /*向量化循环*/
else
for(i=0;i<n;i++) a[i] = b[i]; /*串行循环*/
}

可以看出,由于编译器不知道指针a 和b之间是否存在重叠, 所以他对循环进行了条件处理。 如果我们从语义上能够保证a和b是不会发生重叠的, 那么我们就可以使用restrict关键字来显式的告诉编译器这一点。

void vec2(char* restrict a, char* restrict b, int n)
{
int i;
for(i=0;i<n;i++)
a[i] = b[i];
}

此时编译器输出没有multiversioned。

restrict.c
C:/test/restrict.c(5) (col. 2): remark: LOOP WAS VECTORIZED.

注意C90里面不支持restrict关键字, 在windows平台编译的时候我们需要加上选项/Qrestrict来让编译器认识restrict关键字。

  • #pragma vector

使用#pragma vector编译指示来显式告诉编译器使用某种方式来进行自动向量化。 比如在下面这个例子中, 我们可以保证变量a的地址是16字节对齐的,那么我们就可以使用#pragma vector aligned来显式的告诉编译器不需要考虑非对齐的情况。这样产生的向量化代码是最高效的。

File: vec3.c
#include <malloc.h>
void vec3(void)
{
int i;
char* a=_aligned_malloc(1024, 16);
#pragma vector aligned
for(i=0;i<1024;i++)
a[i] = 1;
}

在上面这个例子中, 如果我们不加上#pragma vector aligned, 编译器会对循环进行条件处理后的向量化。 大家可以对比一下两种情况下生成的汇编代码的区别。请注意我们在使用#pragma vector aligned的时候需要保证被处理变量的对齐方式是16字节对齐的, 否则在使用了该编译指示后可能会产生错误的结果。在上面这个例子中, 我们稍作如下修改:

#include <malloc.h>
void vec3(void)
{
int i;
char* a=_aligned_malloc(1024, 16);
char* b = a+1;
#pragma vector aligned
for(i=0;i<1023;i++)
b[i] = 1;
}

循环中的b的地址不是16字节对齐的, 但是如果我们还是按照aligned的方式来做向量化, 产生的结果是编译出来的可执行程序运行时异常。 在上面的例子中, 我们需要使用#pragma vector unaligned 编译指示来正确的引导编译器。

  • #pragma ivdep

使用#pragma ivdep显式的告诉编译器忽略向量之间可能存在的依赖关系。参考下面这个例子。

File: vec4.c
void vec4(int* a, unsigned int n, unsigned int m)
{
int i;
for(i=0;i<n;i++)
a[i] = a[i+m];
}

使用编译器编译该程序:
icl vec4.c –O2 -c /Qvec-report:3
编译器输出:

vec4.c
C:/test/vec4.c(6) (col. 2): remark: loop was not vectorized: existence of
vector dependence.
C:/test/vec4.c(7) (col. 3): remark: vector dependence: assumed ANTI dependence between a line 7 and a line 7.
C:/test/vec4.c(7) (col. 3): remark: vector dependence: assumed FLOW dependence between a line 7 and a line 7.
C:/test/vec4.c(7) (col. 3): remark: vector dependence: assumed FLOW dependence between a line 7 and a line 7.
C:/test/vec4.c(7) (col. 3): remark: vector dependence: assumed ANTI dependence between a line 7 and a line 7.

如果我们可以保证a[0]到a[n-1]和a[m]到a[m+n-1]之间不存在重叠, 那么我们就可以使用#pragma ivdep来显式的告诉编译器可以忽略a[i]和a[i+m]之间的依赖关系, 修改程序如下:

void vec4(int* a, unsigned int n, unsigned int m)
{
int i;
#pragma ivdep
for(i=0;i<n;i++)
a[i] = a[i+m];
}

这样程序中的循环就可以被自动向量化了。

英特尔编译器提供了强大的自动向量化支持,并且对自动向量化的优化也一直在进行之中。 在新版本的编译器中, 我们可以看到更多的自动向量化有关的支持。

转载于:https://www.cnblogs.com/blockcipher/archive/2010/09/17/2913004.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值