使用Intel编译器(1)并行化(1)自动并行化基础

来源:互联网 发布:国密算法的优缺点 编辑:程序博客网 时间:2024/05/29 17:01

参考手册:

http://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/index.htm


说明:本系列文章为个人笔记,如有不正确之处,请参考官方相关文档,如果错误发现,我会尽量更新修改。另外,以下内容不保证对于所有版本的编译器都正确,编译器的实现也可能有一些变化之处,具体参考官方文档。


更多说明请参考http://blog.csdn.net/gengshenghong/article/details/7034748中补充说明部分。


(1) 什么是自动并行化

Intel编译器的自动并行化功能可以自动的将串行程序的一部分转换为线程化代码。进行自动向量化主要包括的步骤有,找到有良好的工作共享(worksharing)的候选循环;对循环进行数据流(dataflow)分析,确认并行执行可以得到正确结果;使用OpenMP指令生成线程化代码

说明:由于是使用OpenMP指令生成线程化代码的,所以,自然适用于多核或多处理器的共享内存系统。

另外,自动并行化分析器会分析循环的数据流,只会给在并行情况下能安全并高效的执行的代码生成多线程代码。

(2)自动并行化的选项

/Qparallel:允许编译器进行自动并行化

/Qpar-reportn:n为0、1、2、3,输出自动并行化的报告

说明:/Qparallel必须在使用O2/3选项下有效,Od/O1下会被忽略。

默认情况下,不会输出自动并行化报告,在命令行下编译,自动并行化报告输出到stdout中。

(3)简单的例子:

// file: test0.cpp// icl test0.cpp /Qparallel /Qpar-report3#include <stdio.h>#define N1000000int main(){int a[N];int i = 0;for(i = 0;i < N; i++){a[i] = i;}printf("%d\n",a[N/2]);// Must add this, or, O2 may optimize above code.return 0;}
说明:这里的最后一个printf是必须的,总之,要“引用”一下数组a,否则,由于O2选项的优化,上面的循环可能都被优化掉了,因为不printf的话,这个循环被认为没有意义。

通过修改不同的N,分析生成的自动并行化报告。对于上面的代码,会得到类似下面的报告:

C:\tempLab\auto_para\test.cpp(11): (col. 2) remark: LOOP WAS AUTO-PARALLELIZED.
修改N为100,重新编译,得到报告如下:

C:\tempLab\auto_para\test.cpp(11): (col. 2) remark: loop was not parallelized: insufficient computational work.
可见循环并没有被并行化,提示说计算任务不充分,这是由于循环太小,不适合进行线程化,这也是编译器“智能化”的表现。

(4)由上面的例子想到的问题

问题一:什么样的计算是充分的计算任务呢?

在运行完上面的例子之后,一个很容易想到的问题是:什么是"充分"的计算任务,也就是说,上面的程序,在N多大的时候才会进行自动向量化?

经过我手动修改N,在我的机器上,N达到3750及以上就一定会被并行化,否则就不会.如果修改代码,比如增加另一个同样的数据b,在循环中添加b[i] = i,会发现这个能进行自动并行化的N会减小,经过测试,在我的机器上,发现N的临界值为1875,为上面的3750的一半(代码如下)。

// file: test1.cpp// icl test1.cpp /Qparallel /Qpar-report3#include <stdio.h>#define N1875int main(){int a[N];int b[N];int i = 0;for(i = 0;i < N; i++){a[i] = i;b[i] = i;}printf("%d\n",a[N/2]);// Must add this, or, O2 may optimize above code.printf("%d\n",b[N/2]);return 0;}
总结:个人认为,编译器会对循环中的计算任务进行分析,显然,这里的分析是静态分析,不需要运行时分析的,所以说,对于一个固定的循环代码,这个能进行自动并行化的“临界值”是一个固定值,编译器会根据循环中的任务复杂程度,来决定这个“临界值”。至于如何决定的,不得而知。理论上猜测,应该是根据处理器以及数目等有关,稍微测试了一下,发现处理器数目并没有影响。(PS:如果能找到相关的说明,就更新这里的描述,这里只是个人根据自己的理解猜测的。)总之,这个对于使用自动并行化并没有影响。

问题二:自动并行化会生成多少个线程?

同样,上面的例子,还可以很容易的提出一个问题,既然自动并行化会生成OpenMP的指令,那么如果在代码中加入了OpenMP的相关设置线程数量的函数,是否会影响自动并行化的线程数呢,或者说,自动并行化生成多少个线程是如何决定的?

在上面的代码中,加入omp_set_num_threads(10);来设置线程数量,通过设置不同的值,分别使用VTune进行并行分析,发现,实际运行的时候,自动并行化的区域,生成的线程数目(OpenMP线程)是和omp_set_num_threads等函数有关系的。

总结:

这里的两个问题应该是很容易想到的问题,当然,这两个问题只需要有一个基本理解即可,对于我们使用来说,并不需要真正的去关注编译器是如何来决定多大的计算任务会被并行化以及一段代码最终等价的OpenMP代码是如何的,而且,自动并行化还可能和编译器的其它选项相关联的,下面会介绍一部分。

(5)同时使用OpenMP和自动并行化

如果代码中使用了OpenMP进行多线程编程,那么能否使用自动并行化功能呢,或者说,两者一起用的效果会如何?

首先的是,上面已经说明了,自动并行化的循环区域生成的线程数目和OpenMP的线程环境是有关的。

然后的问题是,如果是下面的代码编译器会如何处理呢?

// file: test2.cpp// icl test2.cpp /Qparallel /Qpar-report3 /Qopenmp#include <stdio.h>#define N100000int main(){int a[N];int i = 0;#pragma omp parallel forfor(i = 0;i < N; i++){a[i] = i;}printf("%d\n",a[N/2]);// Must add this, or, O2 may optimize above code.return 0;}
上面的代码很容易理解,编译后,得到的自动并行化报告如下:

C:\tempLab\auto_para\test.cpp(12): (col. 2) remark: loop was not parallelized: insufficient computational work.
可见,上面的循环在使用了OpenMP指令之后不会再次被自动并行化,当然,这也是必然的结果。

(6)关于自动并行化的处理器相关性

由于是自动并行化,生成OpenMP线程化代码,所以在Intel处理器和非Intel处理器上都能实现并行化,当然,并行化也可能会其它选项影响,不同的选项生成的代码不一样,也可能会影响自动并行分析器的结果。

(7)自动并行化和自动向量化

首先,自动向量化是DLP范畴的,自动并行化是TLP范畴的。在代码中,常见的循环可能是多层循环,而不是都是一层循环,那么Intel编译器的自动并行化是如何处理多层循环的呢?用下面的双层循环来说明。

// file: test3.cpp// icl test3.cpp /Qparallel /Qpar-report3 /Qvec-report3#include <stdio.h>#define N1000int main(){int a[N][N];int i = 0;int j = 0;for(i = 0;i < N; i++){for(j = 0;j < N; j++)a[i][j] = i*j;}printf("%d\n",a[N/2][N/2]);// Must add this, or, O2 may optimize above code.return 0;}
对于上面的代码,编译后得到报告(说明,选项中/Qvec-report3用于输出自动向量化报告,而且O2下自动向量化是默认打开的,关于自动向量化的选项,不在此讨论)如下:

C:\tempLab\auto_para\test.cpp(13): (col. 2) remark: LOOP WAS AUTO-PARALLELIZED.C:\tempLab\auto_para\test.cpp(15): (col. 3) remark: loop was not parallelized: insufficient inner loop.C:\tempLab\auto_para\test.cpp(15): (col. 3) remark: LOOP WAS VECTORIZED.C:\tempLab\auto_para\test.cpp(13): (col. 2) remark: loop was not vectorized: not inner loop.C:\tempLab\auto_para\test.cpp(16): (col. 4) remark: loop was not vectorized: subscript too complex.C:\tempLab\auto_para\test.cpp(13): (col. 2) remark: loop was not vectorized: not inner loop.
可以看到,其中,外层循环被自动并行化了,而内层循环并没有被自动并行化,内层循环被会自动向量化。即使添加选项/Qvec-来禁止自动向量化,编译器也不会对内层循环进行并行化。

总结:自动向量化一般会针对内层循环进行向量化,自动并行化一般会针对外层循环进行自动并行化。编译器的这两个选项,可以综合使用,从而使得程序尽量优化。

(8)自动向量化和KMP_STACKSIZE环境变量

KMP_STACKSIZE环境变量是Intel编译器中OpenMP程序的扩展,用于设置OpenMP的线程的私有堆栈的大小(可参考http://software.intel.com/sites/products/documentation/hpc/composerxe/en-us/cpp/lin/optaps/common/optaps_par_var.htm查看OpenMP环境变量)。

使用/Qparallel选项的时候,可能需要为KMP_STACKSIZE设置一个合适的大小,从而允许并行化。再次看到,自动向量化和OpenMP的相关环境是有关的,这里就不测试这个环境变量可能带来的效果了。

(9)和自动向量化相关的pragma扩展

1. #pragma parallel和#pragma noparallel

这两个选项分别用于在代码中禁止和允许部分循环的自动并行化,要注意,其有效的前提是使用了/Qparallel允许编译器进行自动并行化,否则,程序是肯定不会自动并行化的。另外,这两个指令后面必须紧接一个循环,且只会紧接的循环设置有效。如下面的例子:

// icl test4.cpp /Qparallel /Qpar-report3#include <stdio.h>#define N100000int main(){int a[N];int i = 0;#pragma noparallelfor(i = 0;i < N; i++){a[i] = i;}#pragma parallelfor(i = 0;i < N; i++){a[i] = i;}printf("%d\n",a[N/2]);// Must add this, or, O2 may optimize above code.return 0;}
第一个循环不会被自动并行化,第二个循环会被自动并行化,当然,第二个循环的#pragme可以被省略。

2. #pragma parallel的扩展

#pragma parallel,这个指令只是告诉编译器忽略可能存在的会阻止进行自动并行化的数据依赖、性能降低等,但是,编译器仍然会决定是否真的忽略,如果编译器认为数据依赖或者并行化带来的性能降低等是不可忍受的,还是不会进行自动并行化,这有点类似于c++中inline关键字,只是建议内联,具体实现还是编译器决定。

#pragma parallel always,这个指令会强制编译器对循环进行并行化,忽略编译器自己的经验。当然,使用此指令的前提是我们确定并行化的结果对我们没有影响,并且要自己为并行化可能带来的性能降低负责。

#pragma parallel always asert,这个指令会生成错误诊断信息。

下面的例子可以用于理解#pragma parallel always的效果:

// file: test5.cpp// icl test5.cpp /Qparallel /Qpar-report3#include <stdio.h>#define N100int main(){int a[N];int i = 0;#pragma parallel alwaysfor(i = 0;i < N; i++){a[i] = i;}#pragma parallel alwaysfor(i = 0;i < N; i++){printf("%d\n",i);}printf("%d\n",a[N/2]);// Must add this, or, O2 may optimize above code.return 0;}
上面的两个循环都会被并行化,但是如果去掉always,那么都不会被并行化,因为它们都不符合并行化的条件,其中,第一个循环由于N太小,并行化性能不一定有提升,但是结果正确,第二个循环是由数据依赖的,如果并行化,结果就会不正确,可见,always是强制的并行化,只有在特殊的情况下可能会使用。