共享内存编程:OpenMP
共享内存编程:OpenMP(一)
最近在上并行程序设计,我们知道在顺序不影响结果的for循环结构前可以使用OpenMP中的:
#pragma omp parallel [clause ...] if(scalar_expression) num_threads[integer] 来使用多线程进行加速
#pragma omp parallel [clause ...] if(scalar_expression) num_threads[integer] 来使用多线程进行加速
语句说明
pramga: 编译程序指令omp: 用omp这个库来编译程序,需包含头文件omp.h,也可以选择其他库。
parallel for: 针对for的平行方式。
if(scalar_expression): 如果scalar_expression为true,则创建多线程进行并行,若scalar_expression为false,则不创建多线程,即忽略这条语句进行顺序运算。
num_threads[integer]: 指定integer个线程数。
并行原理概况
先介绍一下模型
Fork-Join model: 主线程指定数个次线程,给次线程分配任务,任务完成后返回主线程。上面的pramga就是用来分配线程、回收线程与分配任务的。
Fork-Join model: 主线程指定数个次线程,给次线程分配任务,任务完成后返回主线程。上面的pramga就是用来分配线程、回收线程与分配任务的。

概况:
1. 程序遇见#pragma,开始创建多线程。
2. structure_block中的代码在各线程中被复制并执行。
3. 在平行线程的尾部有一个抽象的屏障。
4. 一个线程异常终止,所有线程终止。
限制
- 并行区域一定要是一个单独的代码块,不要跨越其他代码区域与文件,否则会发生栈溢出等异常情况。
- 在并行区域中不能使用分支语句,但是能调用其他function,但不太建议。
1. 程序遇见#pragma,开始创建多线程。
2. structure_block中的代码在各线程中被复制并执行。
3. 在平行线程的尾部有一个抽象的屏障。
4. 一个线程异常终止,所有线程终止。
限制
- 并行区域一定要是一个单独的代码块,不要跨越其他代码区域与文件,否则会发生栈溢出等异常情况。
- 在并行区域中不能使用分支语句,但是能调用其他function,但不太建议。
线程数
线程数受以下条件顺序影响:
1. 如果if里面条件表达式为false,则顺序执行,即单线程;如果为true,则进行第2条判断。
E.g.:#pragma omp parallel IF(para == true)
2. 通过clause:num_thread设定线程数
E.g.:#pragma omp parallel num_thread(10)
这里注意,线程数可以随意设定,但最多达到计算机所能运行的最大线程数,我们可以通过以下代码查看自己电脑的最大线程数:
#include<iostream> #include<thread> int main(){ unsigned numThreads = std::thread::hardware_concurrency(); std::cout << numThreads << std::endl; return 0; }
- 使用库函数omp_set_num_thread()设定线程,需要在需要平行计算的代码块之前调用。
- 设置环境变量OMP_NUM_THREADS
- 默认,通常设置为一个节点上GUP的数量。
嵌套并行
#pragma omp parallel num_thread(2) { #pragma omp parallel num_thread(3) { cout << "hello parallel"; } }共会输出6个“hello parallel”。
操作函数:
- omp_get_nested(): 检测嵌套并行是否可行。
- omp_set_nested(bool): 设置能否嵌套与OMP_NESTED环境变量。
如果不能嵌套并行,就只能创建一个并行线程。
- omp_get_nested(): 检测嵌套并行是否可行。
- omp_set_nested(bool): 设置能否嵌套与OMP_NESTED环境变量。
如果不能嵌套并行,就只能创建一个并行线程。
例子
来看一个矩阵相乘的例子:
void original(double *A, double *B, double *C, int m, int k, int n) { for (int mi = 0; mi < m; mi++) { for (int ni = 0; ni < n; ni++) { for (int ki = 0; ki < k; ki++) C[mi * n + ni] += A[mi * k + ki] * B[ki * n + ni]; } } } void parallel(double *A, double *B, double *C, int m, int k, int n) { //here I insert #pragma omp parallel for for(int i = 0 ; i < m ; ++i){ for(int ki = 0 ; ki < k ; ++ki){ for(int j = 0 ; j < n ; ++j){ C[i*n+j] += A[i*k+ki] * B[ki*n + j]; } } } }进行1000 乘 1000,1000 乘 1000 的两个矩阵相乘,两者所用时间分别为:

2.99517s
0.61946s
0.61946s
其他
应使用尽量少的#pramga for,因为它也需要时间开销。当有嵌套循环时,一般都在外层循环前加#prama for,如果外层循环数远小于内层循环数,可以在内层循环加#prama for。