一.总结
学习了OpenMP基础知识,并进行了有关的基础编程训练
二.学习笔记
1.OpenMP简介
Openmp是一种用于共享内存并行系统的多线程程序设计方案,支持语言C,C++,Fortran,它提供了对并行算法的高层抽象描述,适合于多核CPU上的并行程序设计。编译器根据程序添加的pramga指令,自动将程序并行化,降低了并行编程的难度和复杂度,当编译器不支持OpenMP时,程序退化为普通的串行程序,此时程序中已有的OpenMP指令不会影响程序正常的编译运行
2.OpenMP执行模式
采用fork-join的执行模式,主线程派生出若干分支线程来执行并行任务,并行代码执行完成后,分支线程汇合,把控制权交给主线程
3.具体用法(以实例讲解)
(1)OpenMP介绍
在C/C++中,OpenMP可以通过使用预处理指令来让程序并行化。OpenMP指令使用的格式
#pragma omp 指令 [子句[子句]…]
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i;
#pragma omp parallel for
for (i = 0; i < 10; i++) {
printf("i = %d\n", i);
}
return 0;
}
运行结果为(每次运行出现的顺序可能不同)
i = 5
i = 4
i = 0
i = 3
i = 6
i = 2
i = 8
i = 1
i = 7
i = 9
(2)fork/join并行执行模式的概念
OpenMP并行执行的程序要全部结束后才会运行后面非并行部分的代码,这就是fork/join并行模式
#include <stdio.h>
#include <time.h>
void foo()
{
int cnt = 0;
clock_t t1 = clock();
int i;
for (i = 0; i < 1e8; i++) {
cnt++;
}
clock_t t2 = clock();
printf("Time = %ld\n", t2 - t1);
}
int main(int argc, char* argv[])
{
clock_t t1 = clock();
int i;
#pragma omp parallel for
for (i = 0; i < 2; i++) {
foo();
}
clock_t t2 = clock();
printf("Total time = %ld\n", t2 - t1);
return 0;
}
输出(每次可能不同)
Time = 710775
Time = 714101
Total time = 763793
可以看到Total Time的输出是在两次Time之后的
(3)parallel指令的用法
parallel 是构造并行块的一个指令,同时也可以配合其他指令如for, sections等指令一起使用。在这个指令后面需要使用一对大括号来指定需要并行计算的代码,并行部分创建出了多个线程来完成
#pragma omp parallel [for | sections] [子句[子句]…]
{
//并行部分
}
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
#pragma omp parallel num_threads(6)
//num_threads用于指定并行域内线程的数目
{
printf("Thread: %d\n", omp_get_thread_num());
//API函数omp_get_thread_num返回当前线程号
}
return 0;
}
运行结果(每次可能不同)
Thread: 0
Thread: 4
Thread: 5
Thread: 2
Thread: 1
Thread: 3
(4)for指令的使用方法
for指令的作用是使一个for循环在多个线程中执行,一般for指令会与parallel指令同时使用,即parallel for指令。此外还可以在parallel指令的并行块中单独使用,在一个并行块中可以使用多个for指令。但是单独使用for指令是没有效果的
以下两份代码等价
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
{
int i, j;
#pragma omp parallel for
for (i = 0; i < 5; i++)
printf("i = %d\n", i);
#pragma omp parallel for
for (j = 0; j < 5; j++)
printf("j = %d\n", j);
}
return 0;
}
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
#pragma omp parallel
{
int i, j;
#pragma omp for
for (i = 0; i < 5; i++)
printf("i = %d\n", i);
#pragma omp for
for (j = 0; j < 5; j++)
printf("j = %d\n", j);
}
return 0;
}
运行结果(每次可能不同)
i = 0
i = 4
i = 3
i = 2
i = 1
j = 3
j = 1
j = 4
j = 2
j = 0
(5)sections和section指令的用法
sections语句可以将下面的代码分成不同的分块,通过section指令来指定分块。每一个分块都会并行执行,具体格式
#pragma omp [parallel] sections [子句]
{
#pragma omp section
{
//代码
}
…
}
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
#pragma omp parallel sections
{
#pragma omp section
printf("Section 1 ThreadId = %d\n", omp_get_thread_num());
#pragma omp section
printf("Section 2 ThreadId = %d\n", omp_get_thread_num());
#pragma omp section
printf("Section 3 ThreadId = %d\n", omp_get_thread_num());
#pragma omp section
printf("Section 4 ThreadId = %d\n", omp_get_thread_num());
}
return 0;
}
运行结果(每次可能不同)
Section 4 ThreadId = 20
Section 1 ThreadId = 21
Section 2 ThreadId = 19
Section 3 ThreadId = 15
(6)private子句的用法
private子句用于将一个或多个变量声明成线程私有的变量,变量声明成私有变量后,指定每个线程都有它自己的变量私有副本,其他线程无法访问私有副本。即使在并行区域外有同名的共享变量,共享变量在并行区域内不起任何作用,并且并行区域内不会操作到外面的共享变量
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i = 20;
#pragma omp parallel for private(i)
for (i = 0; i < 10; i++)
{
printf("i = %d\n", i);
}
printf("outside i = %d\n", i);
return 0;
}
运行结果(每次可能不同,但outside一定是20)
i = 0
i = 6
i = 1
i = 3
i = 4
i = 9
i = 8
i = 2
i = 7
i = 5
outside i = 20
(7)firstprivate子句的用法
private声明的私有变量不能继承同名变量的值,但实际情况中有时需要继承原有共享变量的值,OpenMP提供了firstprivate子句来实现这个功能。但在退出并行区域时,共享变量的值仍是原来的
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for firstprivate(t)
for (i = 0; i < 5; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
运行结果(前五行顺序可能不同)
t = 24
t = 20
t = 21
t = 23
t = 22
outside t = 20
(8)lastprivate子句的用法
除了在进入并行部分时需要继承原变量的值外,有时我们还需要再退出并行部分时将计算结果赋值回原变量,lastprivate子句就可以实现这个需求。需要注意的是,根据OpenMP规范,在循环迭代中,是最后一次迭代的值赋值给原变量,因此实例代码的outside总是24;如果是section结构,那么是将程序语法上的最后一个section语句赋值给原变量
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for firstprivate(t), lastprivate(t)
for (i = 0; i < 5; i++)
{
t += i;
printf("t = %d\n", t);
}
printf("outside t = %d\n", t);
return 0;
}
运行结果(前五行顺序可能不同)
t = 22
t = 20
t = 23
t = 24
t = 21
outside t = 24
(9)share子句的用法
Share子句可以将一个变量声明成共享变量,并且在多个线程内共享。需要注意的是,在并行部分进行写操作时,要求共享变量进行保护,否则不要随便使用共享变量,尽量将共享变量转换为私有变量使用
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int t = 20, i;
#pragma omp parallel for shared(t)
for (i = 0; i < 10; i++)
{
if (i % 2 == 0)
t++;
printf("i = %d, t = %d\n", i, t);
}
return 0;
}
运行结果 (顺序可能不同)
i = 9, t = 20
i = 0, t = 22
i = 5, t = 21
i = 4, t = 21
i = 8, t = 21
i = 3, t = 20
i = 1, t = 20
i = 2, t = 23
i = 7, t = 23
i = 6, t = 24
可以看到,i为偶数时,t的值是一直累加的
(10)reduction子句的用法
reduction子句可以对一个或者多个参数指定一个操作符,然后每一个线程都会创建这个参数的私有拷贝,在并行区域结束后,迭代运行指定的运算符,并更新原参数的值
#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
int i, sum = 10;
#pragma omp parallel for reduction(+: sum)
for (i = 0; i < 10; i++)
{
sum += i;
printf("%d\n", sum);
}
printf("sum = %d\n", sum);
return 0;
}
运行结果 (顺序可能不同)
7
0
6
5
2
9
1
3
4
8
sum = 55
可以看到,最后sum的值变成了累加的值
三.工作记录
暂无