使用Intel 向量化编译器优化性能(1)
本文节选翻译自Intel网站
使用Intel c++编译器的时候,怎么也找不到相关的资料可以参考,在Intel的网站找到了一些教程,我C++的水平很一般,对这个编译器的使用也是刚入门,大家看了请多指正,这是第一部分,余下的我会在这几天继续整理完.
学习如何增强c/c++以及Fortran代码在windows和Linux下的性能,使用Intel的向量化编译器来为特定的循环操作自动产生SIMD代码,这些SIMD指令包括MMX/SIMD Extensions /SIMD Extensions 2.
1. 什么是向量器
向量器是intel c++/Fortran编译器的一个特性,使用intel向量编译特性通过自动生成SIMD代码加速你的代码中特定的循环, 这些SIMD指令包括MMX/SIMD Extensions /SIMD Extensions 2.
2. 什么是向量化程序
a) 主要改善性能的地方
当函数包含大量循环时通过使用向量化编译器使用SIMD指令能够获取较大的性能上的提高,一段以向量化循环为特征的程序足以快过那些同样运行方式的一般的程序或类库.对比一个向量化的循环和一个同样方式的标量循环,向量化会提供等同于使用底层汇编实现这循环的性能(通常使用在使用Streaming SIMD Extensions时会有25%-40%的性能提高).向量器也可能打开这些循环插入一些预取和 streaming store(不知道怎么翻译) 代码在你的循环中,这也可能获得一些额外的性能上的提高.
b) 使用Intel的编译器可以使你从单调乏味的优化工作中解脱出来,编译器可以节省你很多时间,可以代替你做更多的工作.
c) Intel编译器从4.5版本开始支持向量化,5.0版本增强了向量化的功能包括performing simple statement reordering automatically(这是什么,也不知道)和对SIMD2的支持
3. 为什么要使用向量器
a) 提高性能, 比如向量化一个有浮点操作的并被频繁调用的循环将会极大提高程序的性能
b) 编写单一版本的代码, 减少使用汇编使编码工作简化,较少的汇编意味着会大大减少你为特定的系统编程的工作,你的程序将很容易升级并使用于最新的主流系统而不必重新编写那些汇编代码.
4. 什么样的循环是可以向量化的
a) 对于一个循环,如果编译器认为循环内的每一个语句都没有依赖于另一个语句并且没有循环的依赖关系,那么这个循环就是向量的.换句话说,每一个语句必须能独立执行,读写数据的操作必须中立于循环的每次迭代
看这个循环
for (int i=0; i<1000; i++) {
s1: a[i] = b[i] * T + d[i] ;
s2: b[i] = a[i] + b[i])/2;
s3: c = c + b[i];
}
如果等价于下面的操作
for (int i=0; i<1000; i++) a[i] = b[i] * T + d[i] ;
for (int i=0; i<1000; i++) b[i] = (a[i] + b[i])/2;
for (int i=0; i<1000; i++) c = c + b[i];
那么就认为这个循环是可以被向量化的.
再看一个例子
for (int i=1; i<1000; i++)
{
s1: a[i] = a[i-1] * b[i];
}
无论如何,这个循环是不能被向量化的,因为a[i]在每次迭代中都读取了前一次迭代中的a[i-1].我们称这是一个交叉迭代的数据依赖或者”flow dependence”,这样的循环不能被编译器向量化.
假定在第一个例子中使用的是浮点数据-向量器能够通过使用SIMD Extensions同时操作4个浮点数,在这里,为了能够向量化,这个循环的次数必须大于4.
b) 向量器只能作用在最内层的循环
在一个嵌套的循环中,向量器只能尝试向量化最内层的循环,查看向量器的输出信息可以知道循环是否能被向量化以及原因,如果影响性能的关键循环没有向量化,你可能需要做一些更深层的打算,比如像下面描述的内容那样来帮助向量器做出正确的决定.
c) 向量化不是并行化
看这个例子
for (int i=0; i<1000; i++)
{
s1: a[i] = b[i] * T + d[i] ;
s2: b[i] = (a[i] + b[i])/2;
s3: c = c + b[i];
}
这个向量化程序依次s1,s2,s3执行所有的循环迭代.
并行化意味在语句的不同的迭代循环中可能被另一个处理过程所干扰,如果语句请求共享资源(比如读写同一个数据),那这个输出就不能保证正确,所以这个循环是不能正确向量化的.
5. 如何打开向量器
参数
Windows*
Linux*
在编译器指定cpu类型时打开向量器
/Qx[M,K,W]
-x[M,K,W]
打开向量器并自动检测cpu类型,编译器能生成向量化的代码为最新的IA-32处理器,但同时也生成非向量化的代码供旧型号的处理器使用,这样使执行文件可以运行在多种处理器上
/Qax[M,K,W]
-ax[M,K,W]-W 打开对P4的Streaming SIMD Extensions 2的支持, -K打开对Pentium® III process的支持,-M是对MMX技术提供支持
6. 关于#pragma ivdep和restrict的使用
a) 为了向量化一个包含或可能包含依赖关系的循环,加上#pragma ivdep (ignore vector dependencies),如果需要的话.
Void foo(int k)
{
#pragma ivdep
for(int j=0; j<1000; j++)
{
a[j] = b[j+k] * c[j];
b[j] = (a[j] + b[j])/2;
b[j] = b[j] * c[j];
}
}
当向量化这个循环时.编译器会认为数组b依赖了交叉迭代,原因就是是使用了变量k,如果你知道k不会干涉数据访问,加上#pragma ivdep向量器忽略向量的依赖性并尝试进行向量化,根本说,你必须知道这个依赖性是什么,并确信他们不会成为问题
b) 使用指针的循环
使用指针的循环可能造成依赖性,如果为了向量化这样的循环,可以使用restrict关键字,如果需要的话
Void foo(float *restrict a, float *restrict b, float * restrict c)
{
for(int j=0; j<1000; j++)
{
a[j] = b[j] * c[j];
b[j] = (a[j] + b[j])/2;
b[j] = b[j] * c[j];
}
}
注意,使用了restrict关键字旧需要使用/ Qrestrict开关.如果不使用restrict关键字,编译器会认为数组的引用可能有交叉迭代的依赖性
这是因为指针在循环中用来访问数据,编译器无法知道是否指向了相同的地址(一般就是别名),为了安全必须阻止这样程序向量化, restrict关键字告诉编译器指针指向的地址是受限的,只能通过这个指针访问,换句话说,这里没有别名
7. Verbose 模式参数
在windows下使用/Qvec_report[0,1,2,3]参数来产生详细的向量化报告,在Linux下使用-vec_report[0,1,2,3]参数, [0,1,2,3]指定输出信息的详细等级,在最详细的等级3,编译器会指出向量器是产生代码的具体信息.
下面是一次开启了向量器的编译操作的部分输出信息:
C:/TEMP/Text1.cpp(16) : (col. 1) remark: LOOP WAS VECTORIZED.
Below is an example of vectorizer output using the level 3 switch.
char *p;
p = "aeiou";
while (*p++) {
cout << *p;
}
C:/TEMP/Text1.cpp(20) : (col. 1) remark: loop was not vectorized: nonstandard loop is not a vectorization candidate.
float x[100], y[100];
int z[100];
for(int j=0; j<100; j++)
{
x[j] = y[j] + 3;
z[j] = z[j+1];
}
C:/TEMP/Text1.cpp(29) : (col. 5) remark: loop was not vectorized: mixed data types.
float x[100], y[100];
int z[100];
for(int j=0; j<100; j++)
{
x[j] = y[j] + 3;
y[j+1] = z[j+1];
}
C:/TEMP/Text1.cpp(26) : (col. 1) remark: loop was not vectorized: existence of vector dependence.
注意这里有2处问题,2处循环都有向量依赖性,还有一个循环有数据类型不一致的问题,向量器会根据检测到的这些问题的优先等级给出信息.
待续………………….