参考文献:
1. 一起来学OpenMP(1)——初体验 系列好文,精读! 尤其 一起来学OpenMP(4)——数据的共享与私有化
2. OpenMP编程指南 系列好文,精读!
当前多核多线程CPU大行其道,如果不能充分利用岂不是太可惜了!特别在图像处理领域,简直是为并行计算而生的!在网上看了不少文章,还是自己总结一下吧。
一
二 开始体验并行吧!
- #include
<iostream> -
- using
namespace std; - int
main() - {
- #pragma
omp parallel -
for (int i=0; i<10; i++) -
{ -
cout << i; -
} -
-
return 0; - }
就是这一行代码,#pragma omp parallel, 至于#pragma是什么如果不明白的话,建议GOOGLE一下, omp表示这个指令是OpenMP的指令,事实上所有OpenMP的指令都带有omp关键字
在我的机器上4核CPU,运行结果:000011112222333344445555
咋一看程序好像抽风了,产生了4个线程同时执行了for循环。通常这不是我们想要的,我们想要的是把for中的任务等分成4份,分别由4个线程各执行其中的一份。这样做其实很简单,只要在parallel后面加上for就可以了。
- #include
<iostream> -
- using
namespace std; - int
main() - {
- #pragma
omp parallel for -
for (int i=0; i<10; i++) -
{ -
cout << i; -
} -
-
return 0; - }
现在的运行结果是:3068417952。什么?乱七八糟的!然而这是正确的,因为在同一段并行代码中,我们并不能保证各线程执行的先后顺序。扩散开来,在并行代码中,各线程的工作不能有依赖性,比如如果一个线程的输入和另一个线程的输出相依赖,那么此程序不适合并行计算。
三 OpenMP API函数
- //
设置并行线程数 - _OMPIMP
void _OMPAPI omp_set_num_threads(int _Num_threads); -
- //
获取当前并行线程数 - _OMPIMP
int _OMPAPI omp_get_num_threads(void); -
- //
获取当前系统最大可并行运行的线程数 - _OMPIMP
int _OMPAPI omp_get_max_threads(void); -
- //
获取当前运行线程的ID,注意和操作系统中的线程ID不同 - _OMPIMP
int _OMPAPI omp_get_thread_num(void); -
- //
获取当前系统中处理器数目 - _OMPIMP
int _OMPAPI omp_get_num_procs(void); -
- _OMPIMP
void _OMPAPI omp_set_dynamic(int _Dynamic_threads); -
- _OMPIMP
int _OMPAPI omp_get_dynamic(void); -
- _OMPIMP
int _OMPAPI omp_in_parallel(void); -
- _OMPIMP
void _OMPAPI omp_set_nested(int _Nested); -
- _OMPIMP
int _OMPAPI omp_get_nested(void); -
- _OMPIMP
void _OMPAPI omp_init_lock(omp_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_destroy_lock(omp_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_set_lock(omp_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_unset_lock(omp_lock_t * _Lock); -
- _OMPIMP
int _OMPAPI omp_test_lock(omp_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_init_nest_lock(omp_nest_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_destroy_nest_lock(omp_nest_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_set_nest_lock(omp_nest_lock_t * _Lock); -
- _OMPIMP
void _OMPAPI omp_unset_nest_lock(omp_nest_lock_t * _Lock); -
- _OMPIMP
int _OMPAPI omp_test_nest_lock(omp_nest_lock_t * _Lock); -
- _OMPIMP
double _OMPAPI omp_get_wtime(void); -
- _OMPIMP
double _OMPAPI omp_get_wtick(void);
四 for循环
在(二)中已经给出了一个for循环并行化的例子。其实OpenMP中for循环并行化指令有两种形式:
- #include
<iostream> - #include
<omp.h> -
- using
namespace std; -
- int
main() - {
- //for循环并行化声明形式1
- #pragma
omp parallel -
{ -
cout << "OK" << endl; - #pragma
omp for -
for (int i = 0; i < 4; ++i) -
{ -
cout << i << endl; -
} -
} -
- //for循环并行化声明形式2
- #pragma
omp parallel for -
//cout << "ERROR" << endl; -
for (int j = 0; j < 4; ++j) -
{ -
cout << j << endl; -
} -
return 0; - }
形式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
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会跳转到循环体外,那么会编译不通过。
下面来看看在并行程序中各个线程工作的分配,运行下面的程序:
- void
TestAPIs() - {
-
cout << "Num of Procs: " << omp_get_num_procs() << endl; -
cout << "Max Threads: " << omp_get_max_threads() << endl; -
cout << "Set Num of Threads = 2 " << endl; -
omp_set_num_threads(2); -
-
#pragma omp parallel -
cout << "Get Thread Num: " << omp_get_thread_num() << endl; -
-
-
omp_set_num_threads(omp_get_num_procs() -1); -
#pragma omp parallel -
{ -
cout << "OPENMP\n" ; -
} - }
输出结果为:
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等份分别交给每一个线程运行完成。
五
六 一个有意义的并行程序
- #include
<iostream> - #include
<math.h> - #include
<omp.h> -
- using
namespace std; - int
main() - {
-
const int NUMBER = 100; -
int* dataA = new int[NUMBER]; -
int* dataB = new int[NUMBER]; -
for (int i= 0; i < NUMBER; i++) -
{ -
dataA[i] = i+1; -
dataB[i] = 2*(i+1); -
} -
long double sum = 0.0; -
-
omp_set_num_threads(4); - #pragma
omp parallel for reduction(+:sum) -
for (int i = 0; i < NUMBER; i++) -
{ -
sum += dataA[i] + dataB[i]; -
} -
cout<<sum<<endl; -
-
delete [] dataA; -
delete [] dataB; -
-
return 0; - }
上面代码中我们首先开辟了2块的缓冲区,长度均为100个int,并且初始化为1,2,3~100和2,4,6,8~200。在并行段执行前,设置线程数为4。接下来是并行指令:
- #pragma
omp parallel for reduction(+:sum)
指令中#pragma omp parallel for上文已经讲了,而后面reduction(+:sum)起什么左右的呢?要解决这个问题,首先读懂for循环中代码,可以看出只不过是把dataA和dataB中的元素相加,然后赋值给sum,这样循环结束后