OpenMP学习笔记

编译命令:gcc –O XXX.c -fopenmp

1.      Multi-thread Programming

1.      首先要import相应的API,因此必须在首行写上#include “omp.h”

2.      对于要并行执行的部分,用#pragma omp parallel{…} 标记并行块范围。

3.      在并行块里面,用int ID = omp_get_thread_num(); 来获取并行线程的ID。注意:Master thread的ID为0。

4.      设定并行线程的数目的方式有两种:1. 如果要设定并行线程的数目在并行块外,调用omp_set_num_threads(intnum)函数。2.也可以在声明并行块的时候同时声明设定并行线程的数目,即使用#pragma omp parallelnum_threads(int num){}代替#pragma omp parallel{ }。如果不设定并行线程的数目,则并行线程数目的缺省值为CUP的核数。

5.      在并行块内,一般会使用int nthrds = omp_get_num_threads()来获得并行线程的总数,这样方便在遍历的时候设立stride。

2.      Synchronization

2.1 critical

在并行块中的关键区域上锁,实行局部的串行操作。

#pragma omp critical{ …}

2.2 atomic

作用同critical,但是仅对某个内存区域的读写操作有效(e.g.如果要调用某个函数,可能就会失效),速度比critical更快。

#pragma omp atomic{ …}

3.      Parallel Loops

3.1自动并行执行for循环(循环子默认为private)

#pragma omp for{ …}

e.g.

#pragma omp parallel for

         for(i = 0; i<N;i++)

a[i] = a[i] + b[i];

等效于

#pragma omp parallel

{

         intid, i, Nthrds, istart, iend;

         id= omp_get_thread_num();

         Nthrds= omp_get_num_threads();

         istart= id * N / Nthrds;

         iend= (id + 1)* N / Nthrds;

for(i=istart;i<iend; i++){

         a[i] = a[i] + b[i];

}

}

本质上就是把循环子i的遍历空间进行划分,让每个线程包办一部分的iterationsiteration次数尽量做到平均。前提是不存在依赖!

:如果标记块中仅含有一个statement,则#pragma ompfor后的大括号可以去掉。如果整个并行块里面只有一个for循环,则可以将声明并行块的#pragma omp parallel{ …}与并行块里面的#pragma omp for{…}简写成#pragma omp parrellel for{ …}

此外,用并行for循环优化时,注意要去掉loop carried dependence,e.g.

int i, j, A[MAX];

j = 5;

for(i=0;i<MAX;i++){

  j +=2;

  A[j] = big(j);

}

改为

int i, j, A[MAX];

#pragma omp parallel for{

for(i=0;i<MAX;i++){

  int j = 5 + 2i;

  A[j] = big(j);

}

实质是将并行块外定义的用j递推公式表示的线程共享变量,替换为并行块内定义的用i通项公式表示的线程私有变量。

3.2自动并行执行reduction

reduction (op:list)

e.g.

double average = 5.0, A[MAX]; int i;

#pragma omp parallel for reduction(+: average)

for(i=0; i<MAX; i++){

         average+= A[i];

}

average = average/MAX;

假设之前有n个线程,则每个线程都会有一个average的独立副本,因为这里是加法归约,所以每个副本的初始值为0,在执行完for循环后,会根据reduction(+: average)的指示来将所有的average副本进行+操作,最终归约成1个average,并与并行块之前的全局变量average进行合并形成最终值,在这里也就是所有A[i]的和加上average最开始赋的初始值5.0。

:reduction(op: list) 里的参数可以是若干个op:variable对,例如上例中要同时求出几何平均数geomeAverage,则将改为reduction(+: average, *: geomeAverage).

4.      Synchronize single masters andstuff

4.1 Barrier

Barrier设立目的是要所有线程都执行完Barrier的前一个statement后允许才能一起执行Barrier后的任务,即让各线程Barrier处同时起步。

#pragma ompbarrier

         在每个worksharingconstruct(例如for和reduction)结束后,设立Barrier是编译器的缺省行为,目的是因为如果上下语句之间存在依赖关系(例如后一句中调用了前一句所得的所有线程算出结果),此时必须得让所有线程先执行完前一句后才能再一起并行执行后面的部分。当然,由于编译器是无法判断上下句之间是否存在依赖关系的,如果人为可判定不存在依赖关系,则可以加上#pragma ompnowait{…}来表明拆除缺省设置的barrier。

         好的编程习惯是在能加nowait的地方都加上!

4.2 MasterConstruct

         表明只有masterthread才能执行的部分。master域结束后有时需要加上一个barrier。

         #pragmaomp master{…}

4.3 Singleworksharing Construct

         表明只有某一个thread能执行的部分,但不一定非要masterthread去执行。single域结束后默认会加上一个barrier。

         #pragmaomp single{…}

4.4 Ordered

         约束比barrier更强,不光要让所有线程先停在这里,还得让所有线程按照id的顺序逐个起步。

         #pragmaomp ordered{…}             #pragmaordered{…}

e.g.

#pragma omp parallel private(temp)

#pragma omp for orderedreduction(+:res)

         for(i=0;i<N; i++){

                   temp= NEAT_STUFF(i);

#pragma ordered

                   res+= consum(temp);

         }

4.5 Lock routines

SimpleLock routines: omp_init_lock(), omp_set_lock(),omp_unset_lock(), omp_test_lock(), omp_destroy_lock().

Nested Locks:omp_init_nest_lock(), omp_set_nest_lock(), omp_unset_nest_lock(), omp_test_nest_lock(),omp_destroy_nest_lock(). (就是在simpellock的名字后面加个_nest修饰)

e.g.

omp_lock_t lck;

omp_init_lock(&lck);        //可以看成是调用omp_lock_t的构造函数(虽然C语言没有class

#pragma omp parallel private(tmp, id)

{

         id =omp_get_thread_num();

         tmp =do_lots_of_work(id);

         omp_set_lock(&lck);        //上锁

                   printf(“%d%d”,id, tmp);

         omp_unset_lock(&lck);   //解锁

}

omp_destroy_lock(&lck);         //可以看成是调用omp_lock_t的析构函数(虽然C语言没有class

:其实整个流程就类似使用2-phaselock其实也可以用critical域代替!

4.6Runtime Library routines

1)        Modify/Check the number ofthreads

omp_set_num_threads(),omp_get_num_threads(), omp_get_thread_num(),omp_get_max_threads()(因为即使你设置的并行线程数,实际分配的线程数也可能并不是你所设的值,可能更少)

2)        Are we in an active region?

omp_in_parallel() (有时候看似并行执行其实系统会由于不会辨别依赖关系等原因串行执行,所以需要调用此函数判断,而且此函数也可以用来debug)

3)        Do you want the system todynamically vary the number of threads from one parallel construct to another?

omp_set_dynamic, omp_get_dynamic()

4)        How many processors in thesystem?

omp_num_procs()(经常最为设定线程数时的输入参数)

e.g.

omp_set_dynamic(0); 表示将系统可动态调整的线程数目设置为0,即不允许动态调整线程数目。

omp_set_num_threads(omp_num_procs());表示将并行线程数设置为CPU的处理器个数。

4.7   Environment Variables

1) 设置默认线程数

OMP_NUM_THREADS ini_literal

2) 设置schedulechunk size

OMP_SCHEDULEschedule[, chunk_size]

5.      Data Environment

5.1 Defaultstorage attributes

大多数变量都默认是shared.(Fortran例外)

1.        Shared Variables:

a)      Global variable: file scoopvariable, static.

b)      Dynamically allocated memory:ALLOCATE, malloc, new. (因为尽管多处理器多线程,但是共享内存)

2.        Private Variables:

a)        Stack variables in functions.

b)        Automatic variables with in astatement block.

e.g.

double A[10];   //global variable

int main(){

int index[10];   //main function variable

#pragma omp parallel

         work(index);

printf(“%d\n”;index[0]);

}

 

extern double A[10];        //extern variable is global

void work(int *index){

double temp[10];     //temp is declared in function, so it’slocal to each thread

static intcount;        //static variable can beconsidered as global variable

}

5.2 Data sharing

shared, private, firstprivate都是针对的construct,而非并行块。firstprivate具有private性质,但是变量初始值继承了并行块执行前masterthread中的全局变量的值。

可以使用DEFAULT(SHARED)DEFAULT(NONE)来设定默认的data sharing属性。

6.      Schedule for loop and sections

6.1 Sections

#pragma omp section{…}

表示一个线程包办这个区域内的task,而且在section域结束后默认设有barrier

6.2 Schedule Clause

a)        schedule(static[,chunk]) 缺省设置

静态的给每个线程分配需要执行的chunk,在compile-time完成scheduling。因此,chunk size较小比较好,会使每个线程的workload相对balanced一些,但是也不可将chunk size设置的太小,否则任务分配的开销太大。

b)        schedule(dynamic[,chunk])

动态的给每个线程分配需要执行的chunk,在run-time完成scheduling。这样可以达到每个线程的workload都很balanced的效果,但是动态分配产生extra overhead

c)        schedule(guided[,chunk])

设定的chunksize是个下界,一开始chunksize比较大,逐渐变小到设定好的chunksize,趋势为downside

d)        schedule(runtime[,chunk])

chunksize由环境变量决定。

7.      Summary

1.        Find dependencies in theoriginal code, and then rule out these areas for parallelism.

2.        Check if shared variables andprivate variables are set correctly.

3.        Loop permutation, unrolling andtiling may be also used in omp parallel for construct.

e.g.

      for (i=0;i<n;i++)

       for(k=0;k<n;k++)

        {

          x[i][k] = b[i][k];

          for (j=0;j<i;j++) x[i][k] =x[i][k] - a[i][j]*x[j][k];

          x[i][k] = x[i][k]/a[i][i];

        }

Since i loop exists dependency, you cannot parallelize the i loop. However,you can parallelize either k loop or j loop. If you parallelize k loop, you canalso permute i loop and k loop.

4.        Try to use dynamic or staticscheduling way to set some variables as local ones, where synchronization maybe needed;

5.        Setting some temporaryvariables or change program structure may also optimize the parallelism.

6.        Set nowait when it works for aworksharing construct.

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页