什么是openMP?
OpenMp是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受的,用于共享内存并行系统的多处理器程序设计的一套指导性的编译处理方案(Compiler Directive)。OpenMP支持的编程语言包括C语言、C++和Fortran;而支持OpenMp的编译器包括Sun Compiler,GNU Compiler和Intel Compiler等。OpenMp提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。
OpenMp 和 Pthread 一样都是共享内存编程的API但是,他们有许多本质的不同。简单来说Pthread 是比较偏底层的编程方式,由程序员分别管理每一个进程的行为与姿态。相反的,OpenMp 则要求编译器支持某些操作,所以完全有可能本地的编译器并不能把程序编译成并行程序。
代价就是,每个线程行为的每一个细节都需要我们自己定义,他被明确的设计为可以用来对已有串行程序进行增量式的并行化。
hello 程序例程
#include<stdio.h> #include<stdlib.h> #include<omp.h> void hello(void); int main(int argc,char **argv){ int thread_count = strtol(argv[1],NULL,10); #pragma omp parallel num_threads(thread_count) hello(); return 0; } void hello(void){ int my_rank = omp_get_thread(); int thread_count = omp_get_num_threads(); printf("hello"); }
程序使用宏来启动每一个线程,类似pthread 明显,OpenMP 比Pthread 层次更高。 parallel 是一条指令,表明结构化代码块。 注意:标准输出被所有的线程共享,每一个线程都可以执行输出语句,对于标准输出的访问没有调度,因此线程打印他们结果的实际顺序是不确定的。 错误检查相关: step 1:检查传参是否正确。 step2:编译器支持OpenMp
#ifdef _OPENMP
#include<omp.h>
#endif
OPENMP 的互斥指令
#pragma omp critical
exp.....
指令下的程序只能被互斥的执行。 归约操作符:是一个二元操作,归约就是将相同的归约操作符重复的应用到操作数序列来得到一个计算结果。另外,所有操作的中间结果保存在同一个变量里:归约变量。OpenMP 创建一个临界区,并且在临界区中,将存储在私有变量中的值进行相加。
reduction(<operator>:<variable list>)
操作符是 +, —, *, |,|| ,& ,&& ,^ 中的任意一个。 parallel for 指令:生成一组线程来执行后面的结构化代码块。然而在parallel for 指令后的结构块必须是for 循环。运用parallel for 指令,系统通过线程在线程之间划分循环迭代并行化for 循环。线程间的缺省划分方式由系统决定。大部分系统会粗略的使用块划分。在一个被parallel for 指令循环并行化循环中,循环变量的缺省作用是私有的,在我们的代码中,每个线程组中的线程拥有自己的I的副本。(如下,approx 是一个归约变量)
#pragma omp parallel for num_threads(thread_count) \
reduction(+:approx)
for(i = 1;i <= n-1;i++){
approx += f(a + i*h);
approx = h * approx;
注意:只能并行化for 循环。不能处理while do_while 循环。并且不能并行化带有其他出口的for循环。 要点: OpenMp不检查被parallel for指令并行化的循环所包含的迭代间的依赖关系。而是由程序员来识别这依赖关系。 一个或者给更多的迭代结果依赖于其他迭代的循环,一般不能被OpenMp 争取的并行化。一般是数据依赖和循环依赖。 循环调度: 系统会粗略的优化循环,大体使用块划分的方式,但是我们有时需要自己规定线程的调度方式。API为我们提供了指令schedule 指令帮助我们自定义循环的调度。
#pragma omp parallel for num_threads(thread_num) \
reduction(+:sum) schedul(static,1)
for( i = 0;i < = n;i++)
sum += f(i)
一般式:
schedule (<type> [,<chuncksize>])
例如: 若函数f(x) 的执行实现随着参数的复杂而增大,那么粗暴的块划分就是没有用的,我们需要使用自定义循环调度方式。 如一般式所表达结果: type 可以是以下的任意一个: static 迭代可以在循环结束前分配给进程。 dynam 和 guided 迭代在循环执行的时被分配给线程,因此在一个线程完成了它的当前迭代集合后,他能从运行时系统请求更多。 auto 编译器和操作系统决定调度方式 runtime 调度在运行时决定 chuncksize 是一个正整数,迭代次数依赖这个数字,只有static dynamic guided 调度有chunksize .虽然确定了调度的细节,但是准确的解释还是依赖于type.
查看原文:http://zmrlinux.com/2017/01/28/openmp%e5%b9%b6%e8%a1%8c%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e5%9f%ba%e7%a1%801/