INTEL C++ COMPILER之Intel 编译器支持的语言扩展

提交新文章

2011年12月26日 07:00


4.3 Intel编译器支持的语言扩展

前面介绍的都是利用编译器的优化选项开关来进行性能的优化,一般这些优化选项是针对所有代码或者针对某个源文件的所有函数时,有的时候我们可能只希望只对某个热点函数或者热点的代码块进行优化,这个时候就可以使用Intel编译器提供的语言扩展功能。关键字pragma是属于C语言中的关键字,但是具体的pragma的含义与作用是由具体的编译器来解释的,因此值得注意的是采用pragma进行优化可能会影响应用的移植性。要使用pragma,只要在源代码中的相应位置增加一行:

#pragma <pragma name>

Pragma

描述

#pragma optimize("",on|off)

打开和关闭优化支持

#pragma optimization_level n

控制采用的优化级别

#pragma loop count min=n,max=n,avg=n
#pragma loop count = n
#pragma loop count =n1[,n2]…

告诉编译器循环估计的执行次数

#pragma nounroll

告诉编译器不要展开循环

#pragma unroll
#pragma unroll(n)

告诉编译器循环展开的次数

#pragma distribute point

控制循环分割

#pragma ivdep

告诉编译器没有数据依赖

#pragma novector

告诉编译器不要把循环自动矢量化

#pragma vector
{aligned|unaligned|always}

怎么样自动矢量化,哪些影响矢量化选择的因素可以忽略掉

#pragma vector nontemporal

自动矢量化的代码中采用流式存储


上面的表格列出一些常用的pragma。首先要介绍的第一类pragma是控制函数是否要进行优化,要进行哪种程度的优化。要控制函数是否进行优化,可以通过optimize来说明,#pragma optimize("",on|off)用于打开和关闭优化支持。#pragma optimization_level n用于控制采用的优化级别,n取值为0到3,分别对应着自动化编译选项中的/Od、/O1、/O2、/O3。考虑下面代码,func1不进行优化,而func2要进行/O1级别的优化,func3进行/O2级别的优化:

#pragma optimize("", off)
func1() {
...
}
#pragma optimize("", on)
#pragma optimization_level 1
func2() {
...
}
#pragma optimization_level 2
func3() {
...
}

对循环的优化对于应用的性能至关重要,Intel编译器也提供了pragma来对于循环优化进行控制。首先是#pragma loop_count min=n,max=n,avg=n,用于告诉编译器紧接着的循环估计的最小执行次数、最大执行次数和平均执行次数,显然这个pragma有助于帮助编译器是否要进行循环展开,是否要进行自动矢量化等。其次是对循环展开的控制,#pragma nounroll用于告诉编译器不要对紧接着这个pragma的循环进行循环展开,而#pragma unroll(n)用于告诉编译器紧接下来的这个循环可以进行循环展开,循环展开的次数最多为n次。如果n等于0,则表示不进行循环展开,而采用#pragma unroll表示由编译器决定循环展开的次数。编译器的选项开关中也提供了循环展开的控制(/Qunroll,/Qunroll:n),不过如果循环之前包含#pragma unroll,则pragma优先。下面的代码中,for循环体会展开4次:

void unroll(int a[], int b[])
{
#pragma unroll(4)
for (int i = 1; i < 100; i++) {
b[i] = a[i] + 1;
}
}

另外一个循环控制的pragma是#pragma distribute point,它用于控制循环分割,该pragma可以放在循环之前也可以放在循环体中,如果放在循环体之前,表示由编译器来决定如何对循环进行分割,而放在循环体中,则告诉编译器循环体从pragma所在的位置分割为两个循环。考虑下面的代码

int i;
for (i=0; i< NUM; i++) {
a[i] = a[i] +i;
b[i] = b[i] +i;
#pragma distribute point
x[i] = x[i] +i;
y[i] = y[i] +i;
}

上面的循环就变成:

for (i=0; i< NUM; i++) {
a[i] = a[i] +i;
b[i] = b[i] +i;
}
for (i=0; i< NUM; i++) {
x[i] = x[i] +i;
y[i] = y[i] +i;
}

如果一个循环有数据依赖或者编译器无法确定是否具有数据依赖,为了安全起见编译器会选择不进行自动矢量化。为了处理这种情况,Intel编译器提供了几个pragma来控制是否进行自动矢量化。#pragma novector用于告诉编译器紧接着的循环即便可以自动矢量化也不要进行;#pragma ivdep用于告诉编译器紧接着的循环没有数据依赖,比如下面的代码片断,因为不知道k的取值是否会小于0,从而可能会出现数据依赖,如果你知道在调用func时k一定为会大于0,则可以通过ivdep来告诉没有数据依赖,从而可以把该循环自动矢量化。

void func(int *a, int k, int c)
{
int i ;
#pragma ivdep
for (i = 0; i < MAX; i++)
a[i] = a[i + k] * c;
}

一个循环没有数据依赖并不代表其一定会被自动矢量化,可能还要考虑到自动矢量化的开销、内存访问是否步长、数据是否对齐等情况,#pragma vector用于帮助编译器来决定是否对于没有数据依赖的循环进行自动矢量化。其中#pragma vector always表示只要可以就进行自动矢量化,而不考虑性能方面的因素。#pragma vector {aligned | unaligned}用于告诉紧接着的循环体对于内存的访问是否是对齐的,如果是对齐的,则在其他条件(没有数据依赖)满足的情况下可以进行矢量化。另外_assume_aligned(var,n)也可用来告诉变量var是n比特对齐的。

#pragma vector aligned // assume aa and bb are aligned by 16
//Line above can be replaced as:
// __assume_aligned(aa,16); __assume_aligned(bb,16);
for (i = 0; i < MAX; i++) {
aa[i] = bb[i] + 2.0f;
}

#pragma vector nontemporal用于告诉编译器在生成自动矢量化的代码时进行流式存储,即把数据直接写到内存中而不用通过缓存。

#pragma vector nontemporal 
for (i = 0; i < MAX; i++)
a[i] = 1;