实习笔记Day11(2022.8.19)

一.总结

学习了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的值变成了累加的值 

三.工作记录 

暂无

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值