openMP并行编程基础

OpenMP基础结构:

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

int main(int argc, char *argv[]){

	int nt, tid;
	int np;
	const int MAX=256;
	char buf[32]={0};

#pragma omp parallel private(nt, tid) num_threads(8)
	{
		tid=114514;
		tid=omp_get_thread_num ();
		printf("Hello world from OpenMP thread %d\n",tid);
		if(tid==0){
			nt=omp_get_num_threads ();
			printf("num of threads = %d\n",nt);
		}
	}


	return 0;
}

  • 需要在编译的时候添加-fopenmp参数:

    image-20201122115952237

    对于Qt, 需要在pro配置文件中添加:

    image-20201122120022467

  • 在编程时, 需要在并行域处使用#pragma制导, 并将后头的程序全部放入代码块内

    clauses用来说明并行域的附加信息

    image-20201122120201331
  • 在并行域结束后会有一个自动的barrier隐式同步

常用の附加信息:

并行块:

parallel

表示下头的程序所有线程都会执行

设置数据为公有或私有:

shared(var1, var2, ...)
    
private(var1, var2, ...)    

设置变量为线程私有或是公共变量

前者就和串行执行时相同, 后者所有线程访问此变量时, 使用的都是同一地址, 在编程时需要注意数据竞争问题

default(shared|none)

在fortran中还可以指定为private

设置所有未声明的变量为指定模式, 为none时需指定所有变量的模式, 否则会报错

OpenMP中, 有几个默认情况:

  • 所有的循环变量(如for), 临时变量, 写变量 通常为私有
  • 数组变量, 只读变量 通常为共有
  • 默认情况为公有

拷贝:

copyin

用于将主进程中threadprivate的数据拷贝到每个线程中, 使得每个线程都获得初始值相同的私有变量

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

int a=114514;
#pragma omp threadprivate(a)

int main(int argc, char *argv[]){

	int nt, tid;

	int np;
	const int MAX=256;
	char buf[32]={0};

#pragma omp parallel private(nt, tid) shared(np) num_threads(4) copyin(a)
	{
		tid=114514;
		tid=omp_get_thread_num ();
		printf("Thread %d: a= %d\n",tid ,a );
	}
	return 0;
}

image-20201122125916835

归约:

reduction(+:pi)

与MPI的MPI_Reduce相似, 会在线程join时执行归约操作:

他有俩参数

  1. 对数据实行的操作, 一定是一个二元运算, 包括min & max
  2. 指定的变量
    reduction中指定的变量无需进行额外的shared() 或 private() 设置
    其会被自动分割为private和shared

例程:

使用Reduction计算PI

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=10000;
const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double pi, startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();

#pragma omp parallel reduction(+:pi)
	{
		int tid=omp_get_thread_num ();

		int rowTime=n/NUM_THREADS;
		double temp=0.0;
		for(int i=0;i<rowTime;++i){
			temp=((i*NUM_THREADS+tid)+0.5)/n;
			pi+=4.0/(1+temp*temp);
		}
		pi/=n;
		printf("Thread %d : data=%lf\n", tid, pi);

	}

	printf("PI = %.14lf\n",pi);
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

任务划分并行制导:

for制导:

相当于OpenMP自动将for划分到各个线程中执行, 无需手动分割任务

#pragma omp for

通常直接放到需要分割的for上头, 嵌套与parallel块中

每次for制导完成后所有线程都会执行一次同步

此时需要注意数据竞争问题

例程:

使用for制导 + reduction归约求PI

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=100000;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double pi, startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();

#pragma omp parallel reduction(+:pi)
	{
		int tid=omp_get_thread_num ();
		double temp=0.0;
#pragma omp for
		for(int i=0;i<n;++i){
			temp=(i+0.5)/n;
			pi+=4.0/(1+temp*temp);
		}
		pi/=n;
		printf("Thread %d : data=%lf\n", tid, pi);
	}

	printf("PI = %.14lf\n",pi);
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122142040883


指定for制导的数据分组

默认情况下, OpenMP会将数据分成差不多这个样子:

image-20201122143434647

如果要分成这个样子(即每个线程每次拿到固定大小的数据块), 需要使用schedule()操作:

image-20201122143504229

schedule(mode [, chunkSize])
  • 第一个为必要参数, 指定schedule的模式:

    可选: static, dynamic, duided, runtime

    最后一个runtime 不是一个模式, 而是使用环境变量OMP_SCHEDULED来选择三种中的某一个, 并且此时如果指定chunkSize是非法的

  • 第二个为数据块的大小, int类型

不同的调度模式:

最标准的静态分配, 与手动分配相同

image-20201122144018733

这玩意相当于各个线程负载均衡, 每次申请的数量仍然是chunkSize, 但执行较快的进程申请的次数也较多, 一定程度上做到了线程负载均衡

image-20201122144115452

进阶的dynamic模式, 如下:

image-20201122144125511

image-20201122144642649

sections制导:

分为两种:

#pragma onp sections

#pragma omp parallel sections

第一种为串行代码, 由主进程进行

第二种为并行代码, 每个进程执行其中不同的代码块, 相当于MPMD

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

static int n=100000;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------

#pragma omp sections
	{
#pragma omp section
		{
			printf("Thread %d : section 1\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 2\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 3\n",omp_get_thread_num ());
		}
	}

#pragma omp parallel sections
	{
#pragma omp section
		{
			printf("Thread %d : section 4\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 5\n",omp_get_thread_num ());
		}
#pragma omp section
		{
			printf("Thread %d : section 6\n",omp_get_thread_num ());
		}
	}
	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122172017096

single制导:

single块内的代码仅由最先到达的进程执行(仅被执行一次), 其他进程会在single块结束处等待所有进程同步

例程:

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp single
		{
			printf("Thread %d : single 1\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 2\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 3\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 4\n",omp_get_thread_num ());
		}
#pragma omp single
		{
			printf("Thread %d : single 5\n",omp_get_thread_num ());
		}
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122172939411

使用nowait可以取消进程同步, 这玩意也可以用在所有含有隐式同步的制导中, 用于取消隐式同步

使用nowait后, 就变成了这样:

image-20201122193612117

master制导

master块内的代码仅由主进程执行

例程

还是上头的程序

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp master
		{
			printf("Thread %d : master 1\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 2\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 3\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 4\n",omp_get_thread_num ());
		}
#pragma omp master
		{
			printf("Thread %d : master 5\n",omp_get_thread_num ());
		}
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122193903035

barrier制导

与MPI_Barrier()相似, 此语句强制所有进程在此处同步, 只有所有进程同步完成后才能进行下头的步骤

例程

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel
	{
#pragma omp single nowait
		{
			printf("Thread %d : single 1\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 2\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 3\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 4\n",omp_get_thread_num ());
		}
#pragma omp barrier
#pragma omp single nowait
		{
			printf("Thread %d : single 5\n",omp_get_thread_num ());
		}
#pragma omp barrier
	}


	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122194105613

flush制导:

#pragma omp flush [(var1, var2, ...)]

flush标记了一个变量同步点, 保证所有进程中的共享变量值相同

强制刷新每个线程的临时视图,使其和内存视图保持一致,即:使线程中缓存的变量值和内存中的变量值保持一致

通常情况下很少使用到flush, 因为其会在大部分#pragma语句中隐式执行

锁:

多线程并发执行必然需要锁来保证安全性

critical锁:

critical译为临界, 其实就是将代码块标记为临界区, 同一时间只能有一个进程在临界区内

#pragma omp critical [(name)]

{

   <临界区代码>

}

后头跟的name参数是临界段的名称, 通常还是推荐加个名字

例程:

每次运行这个程序, 结果都是不一样的

#include <iostream>
#include <omp.h>
#include <cstdio>
#include <bits/stdc++.h>

using namespace std;

const int MAX=10000;
const int NUM_THREADS=4;

int main(int argc, char *argv[]){

	double startTime=0.0, endTime=0.0;
	int sum=0;

	omp_set_num_threads (NUM_THREADS);

	startTime=omp_get_wtime ();
	//----------------------------------------
#pragma omp parallel shared(sum)
	{
#pragma omp for
		for(int i=0;i<MAX;++i){
			sum+=i;
		}
	}
	printf("sum= %d\n",sum);

	//----------------------------------------
	endTime=omp_get_wtime ();
	printf("Used %.14lfs\n",endTime-startTime);

	return 0;
}

image-20201122195556746

image-20201122195600523

加上critical之后:

#pragma omp parallel shared(sum)
{
    #pragma omp for
    for(int i=0;i<MAX;++i){
        #pragma omp critical
        sum+=i;
    }
}
printf("sum= %d\n",sum);

image-20201122195730656

atomic锁:

atomic操作与critical操作作用相似, 但是执行的速度快很多

同一个操作, critical & atomic 的比较:

image-20201122201207781

image-20201122201153610

使用上, atomic仅支持部分语句, 且不支持程序块, 但是critical支持程序块:

image-20201122201317155

所以原则上, 对于数据的更新, 优先使用atomic, 而后在考虑critical

互斥锁:

这部分PPT讲的不是很详细

如需详细学习可以参考这里:

https://www.cnblogs.com/xudong-bupt/p/3574818.html

  • void omp_init_lock(omp_lock *) 初始化互斥器
  • void omp_destroy_lock(omp_lock *) 销毁互斥器
  • void omp_set_lock(omp_lock *) 获得互斥器
  • void omp_unset_lock(omp_lock *) 释放互斥器
  • bool omp_test_lock(omp_lock *) 试图获得互斥器,如果获得成功返回true,否则返回false

这里有点像PV操作

运行库函数:

这里主要介绍常用的OpenMP库函数

其实在之前的例程中都已经用过了

  1. 获取线程数:

    int omp_get_num_threads();
    
  2. 获取线程号:

    int omp_get_thread_num();
    
  3. 设定执行的线程数量:

    void omp_set_num_threads();
    
  4. 获取墙上时间:

    double omp_get_wtime();
    

    这个和MPI相似

环境变量:

本部分就这些:

image-20201122205522960

image-20201122205530959

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: MPI和OpenMP是两种常见的并行程序设计方法。 MPI(消息传递接口)是一种用于在分布式内存系统中编写并行程序的通信库。MPI允许程序员在多个计算节点之间进行消息传递,将计算任务分配给不同的节点并进行通信。MPI的优点在于可以用于任何规模的并行计算,从几个节点到数千个节点都可以支持。MPI编程需要程序员显式地管理通信和同步操作,因此在编写程序时需要考虑到数据分割、通信、同步等因素。MPI通常用于高性能计算环境中,如超级计算机和集群系统。 OpenMP(开放多处理器)是一种用于共享内存系统中编写并行程序的编程模型。OpenMP使用基于指令的并行化方法,在程序中使用特定的指令来标识并行区域,并指定任务的并行执行方式。OpenMP的优点在于它以简单的方式提供了编写并行程序的能力,程序员只需在代码中加入几个pragma指令即可实现并行化。OpenMP适用于单个计算节点上的共享内存系统,如多核处理器或多线程系统。 《MPI与OpenMP并行程序设计》PDF是一本介绍如何使用MPI和OpenMP编写并行程序的教材或指南。这本书可能会从基础概念开始介绍MPI和OpenMP的原理和特点,然后示范如何使用这两种方法进行程序设计。它可能包含示例代码、实际应用案例和编程技巧等内容。这本书的目标可能是帮助读者理解并行计算的概念和使用MPI和OpenMP来提高程序性能的方法。通过学习这本书,读者可以获得关于MPI和OpenMP并行程序设计的知识,从而能够应用在自己的项目中,提高程序的计算效率和性能。 ### 回答2: MPI和OpenMP是两种不同的并行程序设计模型。 MPI(Message Passing Interface)是一种面向消息传递的并行编程模型。它主要用于集群和分布式系统中的并行计算,通过消息的发送和接收实现不同节点之间的通信和数据传输。MPI的特点是可以在多个进程之间进行并行计算,并且可以在不同的计算节点之间传递数据。在编写MPI程序时,需要定义进程数量和进程通信方式,并使用相应的消息传递函数进行数据的传输。MPI程序适用于需要在分布式系统中进行大规模计算的情况,例如天气模拟、分子动力学模拟等。 OpenMP是一种面向共享内存的并行程序设计模型。它主要用于多核和多处理器系统中的并行计算,通过在代码中插入指令来实现并行化。OpenMP的特点是简单易用,可以通过添加几行指令就可以实现并行计算。在编写OpenMP程序时,可以使用预处理器指令和编译器指令来标识需要并行化的代码段,并指定并行执行的方式。OpenMP程序适用于需要在共享内存系统中进行并行计算的情况,例如矩阵计算、图像处理等。 MPI和OpenMP有各自的优点和适用场景。MPI适用于分布式系统中大规模的并行计算,可以处理更为复杂的通信和数据传输。而OpenMP适用于共享内存系统中的并行计算,可以快速实现并行化,但对于分布式系统的支持较弱。 在实际的并行程序设计中,可以根据任务的特点和系统的资源来选择合适的并行模型。有时候也可以将MPI和OpenMP结合起来使用,例如在集群中使用MPI进行节点之间的通信,然后在每个节点上使用OpenMP进行内部的并行计算,以充分利用系统的资源并提高计算效率。 ### 回答3: MPI和OpenMP是两种常用的并行程序设计模型。MPI(Message Passing Interface)是一种消息传递接口,主要用于在分布式系统中实现进程间的通信。OpenMP是一种共享内存并行编程模型,主要用于在共享内存架构的多核处理器上进行并行计算。 MPI编程模型是基于进程间通信的,并且可以适用于分布式内存系统。在MPI编程中,程序被分为多个并行进程,并且每个进程都有自己的内存空间。进程之间通过消息传递进行通信和数据交换。MPI提供了丰富的通信原语,如发送和接收消息、集合通信和同步操作等,使程序员可以方便地进行进程间通信和数据共享。MPI程序可以在集群、超级计算机等大规模并行系统上运行,并且可以灵活地调整进程的数量和分布。 而OpenMP编程模型是基于共享内存的,并且适用于共享内存架构的多核处理器。在OpenMP编程中,程序被分为多个并行线程,线程之间可以共享同一份内存。OpenMP使用指令编译的方式来指示并行任务的划分和线程共享数据的访问方式。通过使用OpenMP指令,程序员可以方便地将串行代码转化为并行代码。OpenMP程序可以利用多核处理器上的并行计算能力,提高程序的执行速度。 MPI和OpenMP两种并行程序设计模型各有优势和适用场景。MPI适合于大规模并行计算和分布式系统,适用于负载均衡和数据通信较大的应用。OpenMP适合于共享内存多核处理器上的并行计算,适用于大量数据共享和计算密集型的应用。在实际编程中,可以根据应用需求和系统特点选择合适的并行程序设计模型,或者结合两者来实现更高效的并行计算。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值