OpenMP编程速成

参考文献:

1. 一起来学OpenMP(1)——初体验 系列好文,精读!  尤其 一起来学OpenMP(4)——数据的共享与私有化

2. OpenMP编程指南  系列好文,精读!

 

 

当前多核多线程CPU大行其道,如果不能充分利用岂不是太可惜了!特别在图像处理领域,简直是为并行计算而生的!在网上看了不少文章,还是自己总结一下吧。

一  VS2008中怎样开启OpenMP编程?

      Windows平台下,在VS系列编译器中,要想使用OpenMP,只需在工程设置中把C/C++ --> Language --> OpenMP Support设为Yes就可以了。在运行时可能会提示找不到运行库的情况,只要把vcomp90d.dll以及vcomp90.dll复制到工程目录,或系统的Path变量指向的目录下就可以了。在VS2008自带的OpenMP C/C++开发库是2.0版,发行日期为2002年,当前最新的版本为3.1版,可以到官方网站www.openmp.org上下载相关。


二 开始体验并行吧!

     要使程序并行化,没有想象中的那么复杂,OpenMP让一切变得很简单,第一个OpenMP程序,只需添加一行代码!看下面的代码:     

[cpp] view plain copy print ?
  1. #include <iostream>   
  2.   
  3. using namespace std;  
  4. int main()  
  5. {  
  6. #pragma omp parallel     
  7.     for (int i=0; i<10; i++)   
  8.     {  
  9.         cout << i;  
  10.     }   
  11.   
  12.     return 0;  
  13. }  

就是这一行代码,#pragma omp parallel, 至于#pragma是什么如果不明白的话,建议GOOGLE一下, omp表示这个指令是OpenMP的指令,事实上所有OpenMP的指令都带有omp关键字
在我的机器上4核CPU,运行结果:0000111122223333444455556666777788889999

咋一看程序好像抽风了,产生了4个线程同时执行了for循环。通常这不是我们想要的,我们想要的是把for中的任务等分成4份,分别由4个线程各执行其中的一份。这样做其实很简单,只要在parallel后面加上for就可以了。

[cpp] view plain copy print ?
  1. #include <iostream>   
  2.   
  3. using namespace std;  
  4. int main()  
  5. {  
  6. #pragma omp parallel for   
  7.     for (int i=0; i<10; i++)   
  8.     {  
  9.         cout << i;  
  10.     }   
  11.   
  12.     return 0;  
  13. }  

现在的运行结果是:3068417952。什么?乱七八糟的!然而这是正确的,因为在同一段并行代码中,我们并不能保证各线程执行的先后顺序。扩散开来,在并行代码中,各线程的工作不能有依赖性,比如如果一个线程的输入和另一个线程的输出相依赖,那么此程序不适合并行计算。


三 OpenMP API函数

     打开VS2008自带的omp.h,可以发现OpenMP的一些API函数。先从最简单的说起,都很好理解。没有写中文注释的放在后面讲解。

[cpp] view plain copy print ?
  1. // 设置并行线程数   
  2. _OMPIMP void _OMPAPI omp_set_num_threads(int _Num_threads);  
  3.   
  4. // 获取当前并行线程数   
  5. _OMPIMP int  _OMPAPI omp_get_num_threads(void);  
  6.   
  7. // 获取当前系统最大可并行运行的线程数   
  8. _OMPIMP int  _OMPAPI omp_get_max_threads(void);  
  9.   
  10. // 获取当前运行线程的ID,注意和操作系统中的线程ID不同   
  11. _OMPIMP int  _OMPAPI omp_get_thread_num(void);   
  12.   
  13. // 获取当前系统中处理器数目   
  14. _OMPIMP int  _OMPAPI omp_get_num_procs(void);  
  15.   
  16. _OMPIMP void _OMPAPI omp_set_dynamic(int _Dynamic_threads);  
  17.   
  18. _OMPIMP int  _OMPAPI omp_get_dynamic(void);  
  19.    
  20. _OMPIMP int  _OMPAPI omp_in_parallel(void);  
  21.   
  22. _OMPIMP void _OMPAPI omp_set_nested(int _Nested);  
  23.   
  24. _OMPIMP int  _OMPAPI omp_get_nested(void);  
  25.   
  26. _OMPIMP void _OMPAPI omp_init_lock(omp_lock_t * _Lock);  
  27.   
  28. _OMPIMP void _OMPAPI omp_destroy_lock(omp_lock_t * _Lock);  
  29.   
  30. _OMPIMP void _OMPAPI omp_set_lock(omp_lock_t * _Lock);  
  31.   
  32. _OMPIMP void _OMPAPI omp_unset_lock(omp_lock_t * _Lock);  
  33.   
  34. _OMPIMP int  _OMPAPI omp_test_lock(omp_lock_t * _Lock);  
  35.   
  36. _OMPIMP void _OMPAPI omp_init_nest_lock(omp_nest_lock_t * _Lock);  
  37.   
  38. _OMPIMP void _OMPAPI omp_destroy_nest_lock(omp_nest_lock_t * _Lock);  
  39.   
  40. _OMPIMP void _OMPAPI omp_set_nest_lock(omp_nest_lock_t * _Lock);  
  41.   
  42. _OMPIMP void _OMPAPI omp_unset_nest_lock(omp_nest_lock_t * _Lock);  
  43.   
  44. _OMPIMP int  _OMPAPI omp_test_nest_lock(omp_nest_lock_t * _Lock);  
  45.   
  46. _OMPIMP double _OMPAPI omp_get_wtime(void);  
  47.   
  48. _OMPIMP double _OMPAPI omp_get_wtick(void);  


四 for循环

在(二)中已经给出了一个for循环并行化的例子。其实OpenMP中for循环并行化指令有两种形式:

[cpp] view plain copy print ?
  1. #include <iostream>     
  2. #include <omp.h>     
  3.   
  4. using namespace std;    
  5.   
  6. int main()    
  7. {    
  8. //for循环并行化声明形式1     
  9. #pragma omp parallel      
  10.     {    
  11.         cout << "OK" << endl;    
  12. #pragma omp for      
  13.         for (int i = 0; i < 4; ++i)    
  14.         {    
  15.             cout << i << endl;    
  16.         }    
  17.     }    
  18.     
  19. //for循环并行化声明形式2     
  20. #pragma omp parallel for     
  21.     //cout << "ERROR" << endl;   
  22.     for (int j = 0; j < 4; ++j)    
  23.     {    
  24.         cout << j << endl;    
  25.     }    
  26.     return 0;    
  27. }    

形式1和2相比,其优点是在for循环体前可以有其他执行代码,当然在一个#pragma omp parallel块内,可以有多个#pragma omp parallel for循环体。

for循环并行化的约束条件

尽管OpenMP可以方便地对for循环进行并行化,但并不是所有的for循环都可以进行并行化。以下几种情况不能进行并行化:

1. for循环中的循环变量必须是有符号整形。例如,for (unsigned int i = 0; i < 10; ++i){}会编译不通过;

2. for循环中比较操作符必须是<, <=, >, >=。例如for (int i = 0; i != 10; ++i){}会编译不通过;

3. for循环中的第三个表达式,必须是整数的加减,并且加减的值必须是一个循环不变量。例如for (int i = 0; i != 10; i = i + 1){}会编译不通过;感觉只能++i; i++; --i; 或i--;

4. 如果for循环中的比较操作为<或<=,那么循环变量只能增加;反之亦然。例如for (int i = 0; i != 10; --i)会编译不通过;

5. 循环必须是单入口、单出口,也就是说循环内部不允许能够达到循环以外的跳转语句,exit除外。异常的处理也必须在循环体内处理。例如:若循环体内的break或goto会跳转到循环体外,那么会编译不通过。

下面来看看在并行程序中各个线程工作的分配,运行下面的程序:

[cpp] view plain copy print ?
  1. void TestAPIs()  
  2. {  
  3.     cout << "Num of Procs: " << omp_get_num_procs() << endl;  
  4.     cout << "Max Threads: " << omp_get_max_threads() << endl;  
  5.     cout << "Set Num of Threads = 2 " << endl;  
  6.     omp_set_num_threads(2);  
  7.   
  8.     #pragma omp parallel   
  9.     cout << "Get Thread Num: " << omp_get_thread_num() << endl;  
  10.       
  11.   
  12.     omp_set_num_threads(omp_get_num_procs() -1);  
  13.     #pragma omp parallel    
  14.     {  
  15.         cout << "OPENMP\n" ;   
  16.     }     
  17. }  

输出结果为:

ThreadID = 0; i = 0
ThreadID = 0; i = 1
ThreadID = 1; i = 4
ThreadID = 2; i = 8
ThreadID = 3; i = 12
ThreadID = 0; i = 2
ThreadID = 1; i = 5
ThreadID = 2; i = 9
ThreadID = 3; i = 13
ThreadID = 0; i = 3
ThreadID = 1; i = 6
ThreadID = 2; i = 10
ThreadID = 3; i = 14
ThreadID = 1; i = 7
ThreadID = 2; i = 11
ThreadID = 3; i = 15

从输出结果中可以清晰地看出,线程0处理了第0~3次循环,线程1处理了第4~7次循环,线程2处理了第8~1次循环,线程3处理了第12~15次循环,可见for循环的工作量被分成4等份分别交给每一个线程运行完成。


五  数据的可见性


六 一个有意义的并行程序

     在(一)中我们已经完成了第一个并行的程序,但是没有太大意义,下面我们通过一个例子来实现一个有意义的并行程序。

[cpp] view plain copy print ?
  1. #include <iostream>   
  2. #include <math.h>   
  3. #include <omp.h>   
  4.   
  5. using namespace std;  
  6. int main()  
  7. {  
  8.     const int NUMBER = 100;  
  9.     int* dataA = new int[NUMBER];  
  10.     int* dataB = new int[NUMBER];  
  11.     for (int i= 0; i < NUMBER; i++)  
  12.     {  
  13.         dataA[i] = i+1;  
  14.         dataB[i] = 2*(i+1);  
  15.     }      
  16.     long double sum = 0.0;  
  17.   
  18.     omp_set_num_threads(4);  
  19. #pragma omp parallel for reduction(+:sum)   
  20.     for (int i = 0; i < NUMBER;  i++)   
  21.     {  
  22.         sum += dataA[i] + dataB[i];  
  23.     }     
  24.     cout<<sum<<endl;  
  25.   
  26.     delete [] dataA;  
  27.     delete [] dataB;  
  28.      
  29.     return 0;  
  30. }  

上面代码中我们首先开辟了2块的缓冲区,长度均为100个int,并且初始化为1,2,3~100和2,4,6,8~200。在并行段执行前,设置线程数为4。接下来是并行指令:

[cpp] view plain copy print ?
  1. #pragma omp parallel for reduction(+:sum)  

指令中#pragma omp parallel for上文已经讲了,而后面reduction(+:sum)起什么左右的呢?要解决这个问题,首先读懂for循环中代码,可以看出只不过是把dataA和dataB中的元素相加,然后赋值给sum,这样循环结束后

 

你可能感兴趣的:(OpenMP编程速成)