logo资料库

openmp经典教材.pdf

第1页 / 共30页
第2页 / 共30页
第3页 / 共30页
第4页 / 共30页
第5页 / 共30页
第6页 / 共30页
第7页 / 共30页
第8页 / 共30页
资料共30页,剩余部分请下载后查看
OpenMP 编程指南  进入多核时代后,必须使用多线程编写程序才能让各个 CPU 核得到利用。在单核时代,通 常使用操作系统提供的 API 来创建线程,然而,在多核系统中,情况发生了很大的变化,  如 果仍然使用操作系统 API 来创建线程会遇到一些问题。具体来说,有以下三个问题:    1)CPU 核数扩展性问题  多核编程需要考虑程序性能随 CPU 核数的扩展性,即硬件升级到更多核后,能够不修改程 序就让程序性能增长,这要求程序中创建的线程数量需要随 CPU 核数变化,不能创建固定 数量的线程,否则在 CPU 核数超过线程数量上的机器上运行,将无法完全利用机器性能。 虽然通过一定方法可以使用操作系统 API 创建可变化数量的线程,但是比较麻烦,不如 OpenMP 方便。    2)方便性问题    在多核编程时,要求计算均摊到各个 CPU 核上去,所有的程序都需要并行化执行,对计算 的负载均衡有很高要求。这就要求在同一个函数内或同一个循环中,可能也需要将计算分摊 到各个 CPU 核上,需要创建多个线程。操作系统 API 创建线程时,需要线程入口函数,很难 满足这个需求,除非将一个函数内的代码手工拆成多个线程入口函数,这将大大增加程序员 的工作量。使用 OpenMP 创建线程则不需要入口函数,非常方便,可以将同一函数内的代 码分解成多个线程执行,也可以将一个 for 循环分解成多个线程执行。    3)可移植性问题    目前各个主流操作系统的线程 API 互不兼容,缺乏事实上的统一规范,要满足可移植性得自 己写一些代码,将各种不同操作系统的 api 封装成一套统一的接口。OpenMP 是标准规范, 所有支持它的编译器都是执行同一套标准,不存在可移植性问题。                                   
OpenMP 并行程序设计(一)    OpenMP 是一个支持共享存储并行设计的库,特别适宜多核 CPU 上的并行程序设计。、      先看一个简单的使用了 OpenMP 程序  int main(int argc, char* argv[])  {  #pragma omp parallel for            for (int i = 0; i < 10; i++ )            {                    printf("i = %d\n", i);            }            return 0;  }  这个程序执行后打印出以下结果:  i = 0  i = 5  i = 1  i = 6  i = 2  i = 7  i = 3  i = 8  i = 4  i = 9      可见 for  循环语句中的内容被并行执行了。(每次运行的打印结果可能会有区别)  这里要说明一下,#pragma omp parallel for  这条语句是用来指定后面的 for 循环语句变成并 行执行的,当然 for 循环里的内容必须满足可以并行执行,即每次循环互不相干,后一次循 环不依赖于前面的循环。      有关#pragma  omp  parallel  for  这条语句的具体含义及相关 OpenMP 指令和函数的介绍暂时 先放一放,只要知道这条语句会将后面的 for 循环里的内容变成并行执行就行了。        将 for 循环里的语句变成并行执行后效率会不会提高呢,我想这是我们最关心的内容了。 下面就写一个简单的测试程序来测试一下:      void test()  {            int a = 0;            clock_t t1 = clock();            for (int i = 0; i < 100000000; i++) 
          {                    a = i+1;            }            clock_t t2 = clock();            printf("Time = %d\n", t2‐t1);  }      int main(int argc, char* argv[])  {            clock_t t1 = clock();  #pragma omp parallel for            for ( int j = 0; j < 2; j++ ){                    test();            }            clock_t t2 = clock();            printf("Total time = %d\n", t2‐t1);                test();            return 0;  }      在 test()函数中,执行了 1 亿次循环,主要是用来执行一个长时间的操作。  在 main()函数里,先在一个循环里调用 test()函数,只循环 2 次,我们还是看一下在双核 CPU 上的运行结果吧:  Time = 297  Time = 297  Total time = 297  Time = 297      可以看到在 for 循环里的两次 test()函数调用都花费了 297ms,  但是打印出的总时间却只花 费了 297ms,后面那个单独执行的 test()函数花费的时间也是 297ms,可见使用并行计算后 效率提高了整整一倍。                         
OpenMP 并行程序设计(二)    1、fork/join 并行执行模式的概念    OpenMP 是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计 使用的。  前面一篇文章中已经试用了 OpenMP 的一个 Parallel for 指令。从上篇文章中我们也可以发现 OpenMP 并行执行的程序要全部结束后才能执行后面的非并行部分的代码。这就是标准的并 行模式 fork/join 式并行模式,共享存储式并行程序就是使用 fork/join 式并行的。  标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由 主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会 执行串行部分的,如上一篇文章中的以下代码:    int main(int argc, char* argv[])  {            clock_t t1 = clock();  #pragma omp parallel for            for ( int j = 0; j < 2; j++ ){                    test();            }            clock_t t2 = clock();            printf("Total time = %d\n", t2‐t1);                test();            return 0;  }  在没有执行完 for 循环中的代码之前,后面的 clock_t t2 = clock();这行代码是不会执行的,如 果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完,所以这种并行模式 中在主线程里创建的线程并没有和主线程并行运行。    2、OpenMP 指令和库函数介绍    下面来介绍 OpenMP 的基本指令和常用指令的用法,    在 C/C++中,OpenMP 指令使用的格式为                #pragma omp  指令  [子句[子句]…]  前面提到的 parallel for 就是一条指令,有些书中也将 OpenMP 的“指令”叫做“编译指导语 句”,后面的子句是可选的。例如:  #pragma omp parallel private(i, j)  parallel  就是指令,  private 是子句  为叙述方便把包含#pragma 和 OpenMP 指令的一行叫做语句,如上面那行叫 parallel 语句。      OpenMP 的指令有以下一些:                parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行 
              for,用于 for 循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之 间无相关性。                parallel  for,  parallel  和  for 语句的结合,也是用在一个 for 循环之前,表示 for 循 环的代码将被多个线程并行执行。                sections,用在可能会被并行执行的代码段之前                parallel sections,parallel 和 sections 两个语句的结合                critical,用在一段代码临界区之前                single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执 行。                barrier,用于并行区内代码的线程同步,所有线程执行到 barrier 时要停止,直到所 有线程都执行到 barrier 时才继续往下执行。                atomic,用于指定一块内存区域被制动更新                master,用于指定一段代码块由主线程执行                ordered,  用于指定并行区域的循环按顺序执行                threadprivate,  用于指定一个变量是线程私有的。  OpenMP 除上述指令外,还有一些库函数,下面列出几个常用的库函数:                omp_get_num_procs,  返回运行本线程的多处理机的处理器个数。                omp_get_num_threads,  返回当前并行区域中的活动线程个数。                omp_get_thread_num,  返回线程号                omp_set_num_threads,  设置并行执行代码时的线程个数                omp_init_lock,  初始化一个简单锁                omp_set_lock,  上锁操作                omp_unset_lock,  解锁操作,要和 omp_set_lock 函数配对使用。                omp_destroy_lock,  omp_init_lock 函数的配对操作函数,关闭一个锁      OpenMP 的子句有以下一些                private,  指定每个线程都有它自己的变量私有副本。                firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中 的初值。                lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线 程中的对应变量。                reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执 行指定的运算。                nowait,忽略指定中暗含的等待                num_threads,指定线程的个数                schedule,指定如何调度 for 循环迭代                shared,指定一个或多个变量为多个线程间的共享变量                ordered,用来指定 for 循环的执行要按顺序执行                copyprivate,用于 single 指令中的指定变量为多个线程的共享变量                copyin,用来指定一个 threadprivate 的变量的值要用主线程的值进行初始化。                default,用来指定并行处理区域内的变量的使用方式,缺省是 shared      3、parallel  指令的用法 
parallel  是用来构造一个并行块的,也可以使用其他指令如 for、sections 等和它配合使用。  在 C/C++中,parallel 的使用方法如下:  #pragma omp parallel [for | sections] [子句[子句]…]  {                //代码  }  parallel 语句后面要跟一个大括号对将要并行执行的代码括起来。  void main(int argc, char *argv[]) {  #pragma omp parallel    {                              printf(“Hello, World!\n”);  }  }  执行以上代码将会打印出以下结果  Hello, World!  Hello, World!  Hello, World!  Hello, World!  可以看得出 parallel 语句中的代码被执行了四次,说明总共创建了 4 个线程去执行 parallel 语句中的代码。  也可以指定使用多少个线程来执行,需要使用 num_threads 子句:  void main(int argc, char *argv[]) {  #pragma omp parallel num_threads(8)  {                              printf(“Hello, World!, ThreadId=%d\n”, omp_get_thread_num() );  }  }  执行以上代码,将会打印出以下结果:  Hello, World!, ThreadId = 2  Hello, World!, ThreadId = 6  Hello, World!, ThreadId = 4  Hello, World!, ThreadId = 0  Hello, World!, ThreadId = 5  Hello, World!, ThreadId = 7  Hello, World!, ThreadId = 1  Hello, World!, ThreadId = 3  从 ThreadId 的不同可以看出创建了 8 个线程来执行以上代码。所以 parallel 指令是用来为一 段代码创建多个线程来执行它的。parallel 块中的每行代码都被多个线程重复执行。  和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线 程并等待线程执行完。    4、for 指令的使用方法  for 指令则是用来将一个 for 循环分配到多个线程中执行。for 指令一般可以和 parallel 指令合 起来形成 parallel for 指令使用,也可以单独用在 parallel 语句的并行块中。 
#pragma omp [parallel] for [子句]              for 循环语句      先看看单独使用 for 语句时是什么效果:  int j = 0;  #pragma omp for            for ( j = 0; j < 4; j++ ){                    printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());            }  执行以上代码后打印出以下结果  j = 0, ThreadId = 0  j = 1, ThreadId = 0  j = 2, ThreadId = 0  j = 3, ThreadId = 0  从结果可以看出四次循环都在一个线程里执行,可见 for 指令要和 parallel 指令结合起来使 用才有效果:  如以下代码就是 parallel  和 for 一起结合成 parallel for 的形式使用的:  int j = 0;  #pragma omp parallel for            for ( j = 0; j < 4; j++ ){                    printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());            }  执行后会打印出以下结果:  j = 0, ThreadId = 0  j = 2, ThreadId = 2  j = 1, ThreadId = 1  j = 3, ThreadId = 3  可见循环被分配到四个不同的线程中执行。      上面这段代码也可以改写成以下形式:  int j = 0;  #pragma omp parallel    {  #pragma omp for            for ( j = 0; j < 4; j++ ){                    printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());            }  }  执行以上代码会打印出以下结果:  j = 1, ThreadId = 1  j = 3, ThreadId = 3  j = 2, ThreadId = 2  j = 0, ThreadId = 0     
    在一个 parallel  块中也可以有多个 for 语句,如:  int j;  #pragma omp parallel    {  #pragma omp for            for ( j = 0; j < 100; j++ ){                    …            }  #pragma omp for            for (    j = 0; j < 100; j++ ){                    …            }  …  }      for  循环语句中,书写是需要按照一定规范来写才可以的,即 for 循环小括号内的语句要按 照一定的规范进行书写,for 语句小括号里共有三条语句  for( i=start; i < end; i++)      i=start;  是 for 循环里的第一条语句,必须写成  “变量=初值”  的方式。如  i=0  i < end;是 for 循环里的第二条语句,这个语句里可以写成以下 4 种形式之一:  变量  <  边界值  变量  <=  边界值  变量  >  边界值  变量  >=  边界值  如  i>10 i< 10      i>=10 i>10  等等  最后一条语句 i++可以有以下 9 种写法之一  i++  ++i  i‐‐  ‐‐i  i += inc  i ‐= inc  i = i + inc    i = inc + i  i = i –inc      例如 i += 2; i ‐= 2;i = i + 2;i = i ‐ 2;都是符合规范的写法。    5. sections 和 section 指令的用法  section 语句是用在 sections 语句里用来将 sections 语句里的代码划分成几个不同的段,每段 都并行执行。用法如下:  #pragma omp [parallel] sections [子句] 
分享到:
收藏