OpenMP概述
1.OpenMP应用编程接口API(Application Programming Interface)是在共享存储体系结构上的一个编程模型。
2.包括编译制导(Compiler Directive)、运行库例程(Runtime Library)和环境变量(Environment Variables)
3.支持增量并行化(Incremental Parallelization)
注意:OpenMP不是建立在分布式存储系统上的/不是在所有的环境下都一样/不是能让多数共享存储器均能有效利用
并行编程模型
基于线程的并行编程模型
Fork-Join并行执行模型
线程分配实例
//gcc -fopenmp -o test xxx.c
#include<stdio.h>
#include"omp.h"
int main ()
{
int nthreads,tid;
int nprocs;
char buf[32];
omp_set_num_threads(8);
/* Fork a team of threads*/
#pragma omp parallel private(nthreads,tid)
{
/* Obtain and print thread id */
tid=omp_get_thread_num();
printf("Hello World from OMP thread %d\n", tid);
/* Only master thread does this */
if (tid==0) {
nthreads = omp_get_num_threads();
printf("Number of threads %d\n", nthreads);
}
}
return 0;
}
编译制导
#pragma omp: 前缀,对所有的OpenMP语句都需要这样的前缀
其作用域:静态扩展(在一个编译制导语句之后被封装到一个结构块中)
孤立语句(一个OpenMP的编译制导语句不依赖于其他的语句)
动态扩展(包括静态范围-for语句 和 孤立语句-sections/critical语句)
共享任务结构(并行for循环/并行sections语句/串行执行):
for编译制导语句
for语句指定紧随它的循环语句必须由线程组并行执行;
语句格式:
#pragma omp for [clause[[,]clause]…] newline
[clause]= Schedule(type [,chunk])
:schedule字句描述如何将循环的迭代划分给线程组中的线程
如果没有指定chunk大小,迭代会尽可能的平均分配给每个线程
type为static,循环被分成大小为chunk的块,静态分配给线程
type为dynamic,循环被动态划分为大小为chunk的块, 动态分配给线程
ordered
private (list)
firstprivate (list)
lastprivate (list)
shared (list)
reduction (operator: list)
nowait
sections编译制导语句
sections编译制导语句指定内部的代码被划分给线程组中的各线程
不同的section由不同的线程执行
#pragma omp sections [ clause[[,]clause]…] newline
{
[#pragma omp section newline]
…
[#pragma omp section newline]
…
}
clause=
private (list)
firstprivate (list)
lastprivate (list)
reduction (operator: list)
nowait
在sections语句结束处有一个隐含的路障,使用了 nowait子句除外
实例:
//sections并行实例
#include<stdio.h>
#include <omp.h>
#define N 1000
int main ()
{ int i;
float a[N], b[N], c[N];
/* Some initializations */
for (i=0; i < N; i++)
a[i] = b[i] = i * 1.0;
#pragma omp parallel shared(a,b,c) private(i)
{
#pragma omp sections nowait
{
#pragma omp section
for (i=0; i < N/2; i++)
c[i] = a[i] + b[i];
#pragma omp section
for (i=N/2; i < N; i++)
c[i] = a[i] + b[i];
} /* end of sections */
} /* end of parallel section */
printf("前十个元素: \n");
for(i=0;i<10;i++){
printf("%lf \n",c[i]);
}
printf("后十个元素: \n");
for(i=0;i<10;i++){
printf("%lf \n",c[N-10+i]);
}
return 0;
}
single编译制导语句
single编译制导语句指定内部代码只有线程组中的一个线程执行。
线程组中没有执行single语句的线程会一直等待代码块的结束,使用nowait子句除外
parallel for编译制导语句
#include<stdio.h>
#include <omp.h>
#define N 1000
#define CHUNKSIZE 100
int main ()
{
int i, chunk;
float a[N], b[N], c[N];
/* Some initializations */
for (i=0; i < N; i++)
a[i] = b[i] = i * 1.0;
chunk = CHUNKSIZE;
#pragma omp parallel for shared(a,b,c,chunk) private(i) schedule(static,chunk)
for (i=0; i < n; i++)
c[i] = a[i] + b[i];
return 0;
}
同步结构
master 制导语句 | master制导语句指定代码段只有主线程执行 |
---|---|
critical制导语句 | critical制导语句表明域中的代码一次只能执行一个线程,其他线程被阻塞在临界区 |
#include <omp.h>
main()
{
int x;
x = 0;
#pragma omp parallel shared(x)
{
#pragma omp critical
x = x + 1;
}
/* end of parallel section */
}
barrier制导语句 | barrier制导语句用来同步一个线程组中所有的线程/先到达的线程在此阻塞,等待其他线程/barrier语句最小代码必须是一个结构化的块 |
---|---|
atomic制导语句 | atomic制导语句指定特定的存储单元将被原子更新 |
flush制导语句 | flush制导语句用以标识一个同步点,用以确保所有的 线程看到一致的存储器视图 |
threadprivate编译制导语句 | threadprivate语句使一个全局文件作用域的变量在并 行域内变成每个线程私有, 每个线程对该变量复制一份私有拷贝 |
ordered制导语句 | ordered制导语句指出其所包含循环的执行/任何时候只能有一个线程执行被ordered所限定部分/只能出现在for或者parallel for语句的动态范围中 |
#include <omp.h>
#include <stdio.h>
int alpha[10], beta[10], i;
#pragma omp threadprivate(alpha)
int main ()
{
//最好设置一下线程
omp_set_num_threads(4);
/* First parallel region */
#pragma omp parallel private(i,beta)
for (i=0; i < 10; i++)
alpha[i] = beta[i] = i;
/* Second parallel region */
#pragma omp parallel
printf("alpha[3]= %d and beta[3]=%d\n",alpha[3],beta[3]);
return 0;
}
数字域属性子句
private子句 | private子句表示它列出的变量对于每个线程是局部的 |
---|---|
shared子句 | shared子句表示它所列出的变量被线程组中所有的线程共享,所有线程都能对它进行读写访问 |
default子句 | default子句让用户自行规定在一个并行域的静态范围中所定义的变量的缺省作用范围,如果传入的是default(shared),那么传入并行区域内的同名变量均是共享变量,如果传入的是default(private),那么传入并行区域内的同名变量均是私有变量,而不是共享变量 |
firstprivate子句 | firstprivate子句是private子句的超集 \对变量做原子初始化 |
lastprivate子句 | lastprivate子句是private子句的超集 \ 将变量从最后的循环迭代或段复制给原始的变量 |
copyin子句 | copyin子句用来为线程组中所有线程的threadprivate变量赋相同的值\主线程该变量的值作为初始值 |
reduction子句 | reduction子句使用指定的操作对其列表中出现的变量进行规约 \初始时,每个线程都保留一份私有拷贝\ 在结构尾部根据指定的操作对线程中的相应变量进行规约,并更新该变量的全局值 |
#include <omp.h>
#include <stdio.h>
int main ()
{
int i, n, chunk;
omp_set_num_threads(8);
float a[100], b[100], result;
/* Some initializations */
n = 100; chunk = 10; result = 10.0;
for (i=0; i < n; i++)
{
a[i] = i * 1.0; b[i] = i * 2.0;
}
#pragma omp parallel for default(shared) private(i) schedule(static,chunk) reduction(+:result)
for (i=0; i < n; i++)
result = result + (a[i] * b[i]);
printf("Final result= %f\n",result);
return 0;
}
理解reduction的工作过程:
1)进入并行区域后,team内的每个新的线程都会对reduction变量构造一个副本,比如上面的例子,有八个线程,进入并行区域的初始化值为:七个并行区域中的result=0.0,只有一个线程为result=10.0.
因为主线程不是一个新的线程,所以不需要再为主线程构造一个副本(应该就是这样工作的,只会有一个线程使用到并行区域外的初始值,其余的都是0)。
(2)每个线程使用自己的副本变量完成计算。
(3)在退出并行区域时,对所有的线程的副本变量使用指定的操作符进行迭代操作,对于上面的例子,即result’ = result0’+result1’+result2’+result3’+…+result7’.
(4)将迭代的结果赋值给原来的变量(result),result=result’.