目录
#pragma omp parallel
函数:
int omp_get_thread_num() //获取线程ID
int omp_get_num_threads() //获取线程数量(只能在并行区域内使用,在并行区域外使用只能得到1)
void omp_set_num_threads() //设置线程数量
export OMP_NU7M_THREADS=N,命令行运行时通过设置环境变量设置线程数量。
double omp_get_wtime() //获得当前时间,start - end获取SPMD算法的运行时间(秒)
编译指令:
#pragma omp barrier //在当前位置设置一个栅栏
#pragma omp critical //critical构造定义了一个以相互排斥的方式执行的代码块(临界区),即一次只有一个线程执行代码,另外的线程有可能在构造的开始处等待,直到轮到自己。当代码块只包含一条语句时,不需要大括号。
图4-11:
#include <stdio.h>
#include <omp.h>
static long num_steps = 100000000;
double step;
int main ()
{
int i, j, nthreads;
double pi, full_sum = 0.0;
double start_time, run_time;
step = 1.0/(double) num_steps;
full_sum = 0.0;
start_time = omp_get_wtime();
#pragma omp parallel
{
int i, id = omp_get_thread_num();
int numthreads = omp_get_num_threads();
double x, partial_sum = 0;
if (id == 0)
nthreads = numthreads;
for (i = id; i < num_steps; i += numthreads) {
x = (i + 0.5) * step;
partial_sum += 4.0 / (1.0 + x * x);
}
#pragma omp critical
full_sum += partial_sum;
} // end of parallel region
pi = step * full_sum;
run_time = omp_get_wtime() - start_time;
printf("\n pi \%f in \%f secs \%d threds \n ",
pi,run_time,nthreads);
}
#pragma omp for //共享工作循环构造
此指令自带栅栏barrier,可以通过使用nowait字句去除栅栏:
图5-8:
double A[big], B[big], C[big];
#pragma omp parallel
{
int id = omp_get_thread_num();
A[id] = big_calc1(id);
#pragma omp barrier
#pragma omp for
for (i = 0; i < N; i++) {
C[i] = big_calc3(i,A);
}
#pragma omp for nowait
for (i = 0; i < N; i++) {
B[i] = big_calc2(C, i);
}
A[id] = big_calc4(id);
}
#pragma omp parallel for //组合式并行共享工作循环构造
子句:
循环中可能会存在循环携带依赖性:即在任何给定循环的迭代中计算的值,都依赖前面迭代产生的值。
归约是一种常见的循环模式,它涉及将一个迭代中的一系列值进行组合(通常是求和、求积等),以生成一个聚合结果。
在归约操作中,每个迭代步骤都会更新聚合结果,直到迭代完成并得到最终的聚合值。由于每个迭代步骤的结果依赖于前一次迭代的结果,因此归约操作中存在循环携带依赖性。
reduction(op:list) //跨组内线程的值归约
图5-5:
int i;
double ave = 0.0, A[N];
InitA(A, N);
#pragma omp parallel for reduction (+:ave)
for (i = 0; i < N; i++) {
ave + = A[i];
}
ave = ave/N;
图5-9:
#include <stdio.h>
#include <omp.h>
#define NTHREADS 4
static long num_steps = 100000000;
double step;
int main()
{
double x, pi, sum = 0.0;
double start_time, run_time;
int i;
step = 1.0 / (double) num_steps;
omp_set_num_threads(NTHREADS);
start_time = omp_get_wtime();
#pragma omp parallel
{
double x;
#pragma omp for reduction(+:sum)
for (i = 0; i < num_steps; i++) {
x = (i + 0.5) * step;
sum += 4.0 / (1.0 + x * x);
}
}
pi = step * sum;
run_time = omp_get_wtime() - start_time;
printf("pi is %f in %f seconds \n", pi, run_time);
}
schedule(static,chunk) //静态调度
schedule(dynamic,chunk) //动态调度
schedule(static,1)这种情况实际上是循环迭代的周期分配。如图4-6,
schedule(static)实际上是块状分配。如图4-7.
循环级并行策略:
假设起点是一个串行程序或MPI程序,要添加openmp以在“一个节点”上利用并行性:
第一步:
找到计算密集型的循环(书中建议使用openmp概要分析工具OpenMP Compilers & Tools - OpenMP)
第二步:
检查是否可以并行执行,大多数情况下会存在循环携带依赖,需要改造循环(寻找和利用归约,将用增量更新的变量替换成由循环控制索引计算除了的变量,将只读、共享的数据私有化,使循环迭代独立):
图5—-10:
// Sequential code with loop dependence
int i, j, A[MAX];
j = 5;
for (i = 0; i < MAX; i++) {
j += 2;
A[i] = big(j);
}
// Parallel code with loop dependence removed
int i, A[MAX];
#pragma omp parallel for
for (i = 0; i < MAX; i++) {
int j = 5 + 2*(i+1);
A[i] = big(j);
}
测试循环迭代是否真正独立的一个有用的技术三通过交换起始条件和结束条件来反向执行循环。如果反向和正向的结果相同,那这个循环很可能没有循环携带的依赖性。
如果循环没有循环携带依赖,就可以添加openmp指令(#pragma omp parallel for)如果有归约添加归约子句。
最后一步:
尝试用不同的线程数量和循环调度来优化程序,
密切关注隐式栅栏,尝试使用nowait子句安全地关闭。