并行计算(MPI + OpenMP)

article/2025/10/22 21:29:01

文章目录

  • 并行计算
  • MPI(进程级并行)
    • 基本结构
    • 数据类型
    • 点对点通信
      • 阻塞
      • 非阻塞
      • 非连续数据打包
    • 聚合通信
    • Communicator & Cartisen Grid
  • OpenMP(线程级并行)
    • 简介
    • 基本制导语句
    • worksharing construct
      • Sections
      • Single
      • For
    • 临界区 & 原子操作
    • Task

并行计算

并行类型:

  • 进程级并行:网络连接,内存不共享
  • 线程级并行:共享内存,同构 vs 异构
  • 线程内并行:指令级并行(流水线、多发射),向量化(SIMD,AVX)

基本方法:

  1. 分解。数据划分、任务定义
  2. 协调。通信、同步、任务调度

基本原则:

  1. 平衡。处理器之间负载平衡、众核之间负载平衡
  2. 压榨。尽量使得各个部件同时运行

任务划分,

  • 按数据划分:按输入数据(列主序)、按输出数据(行主序)、按输入输出数据(棋盘式)、按中间数据(矩阵乘,列行外积)
  • 递归划分:归并排序、快速排序
  • 探索式划分:对状态空间搜索,每个线程一种搜索策略,并不知道下一步会有多少任务
  • 猜测式划分:并行处理不同时间的事件,如果时间来到某个事件改变了状态,那么就回滚
  • 混合划分

并行模式,

  • 数据并行模式:矩阵计算、数据处理等
  • 任务图模式:使用任务依赖图
  • 主从模式:主线程产生任务,分配给各个工作线程
  • 流水线 / 生产者-消费者模式:每个处理部件完成一个阶段的任务,多个部件同时处理不同数据的不同阶段(GPU stream)
  • 混合模式

MPI(进程级并行)

Message Passing Interface(MPI):一种基于信息传递的并行编程技术,定义了一组具有可移植性的编程接口标准(并非一种语言或者接口)。支持点对点通信和广播。MPI 的目标是高性能、大规模性、可移植性,在今天仍为高性能计算的主要模型。OpenMPI 函数库,微软 MPI 文档

基本结构

程序结构(C语言版)

MPI_Init(&argc, &argv); //初始化MPI
MPI_Comm_size(MPI_COMM_WORLD, &nprocs); //设置进程数
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); //获取进程ID
... ...
MPI_Finalize(); //结束MPI

编译:

mpicc -o test ./test.c
mpicxx -o hello++ hello++.cxx
mpif90 -o pi3f90 pi3f90.f90

运行:

mpirun –np 4 –host HOST1,HOST2,HOST3,HOST4	
mpirun -np 4 -hostfile hosts ./test 

数据类型

MPI 数据类型C语言数据类型
MPI_INTint
MPI_FLOATfloat
MPI_DOUBLEdouble
MPI_SHORTshort
MPI_LONGlong
MPI_CHARchar
MPI_UNSIGNED_CHARunsigned char
MPI_UNSIGNED_SHORTunsigned short
MPI_UNSIGNEDunsigned
MPI_UNSIGNED_LONGunsigned long
MPI_LONG_DOUBLElong double
MPI_BYTEunsigned char
MPI_PACKED

点对点通信

阻塞

阻塞式消息发送

int MPI_Send(const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);
  • buf:发送缓冲区的首地址
  • count:需要发送的数据项个数
  • datatype:每个被发送元素的数据类型
  • dest:目标进程的进程号(rank)
  • tag:消息标识(接收端要使用同样的标号,否则无法传递)
  • comm:通信域(Communicator,指出哪些进程参与通信)
  • 返回值:函数成功时返回 MPI_SUCCESS,否则返回错误代码

阻塞式消息接收

int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status);
  • buf:接收缓冲区的首地址
  • count:接收缓冲区最多存放多少个数据项
  • datatype:每个被接收元素的数据类型
  • source:发送进程的进程号(若设为 MPI_ANY_SOURCE,则可以传递任意进程的消息)
  • tag:消息标识(若设为 MPI_ANY_TAG,则可以传递任意标号的消息)
  • comm:通信域
  • status:函数返回时,存放发送方的进程号、消息 tag 等
    • status->MPI_source
    • status->MPI_tag
    • status->MPI_error
  • 返回值:函数成功时返回 MPI_SUCCESS,否则返回错误代码

同时发送与接收

如果两个进程先执行 MPI_Send 后执行 MPI_Recv,那么有可能出现死锁

int MPI_Sendrecv(const void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status);
int MPIAPI MPI_Sendrecv_replace(void *buf, int count, MPI_Datatype datatype, int dest, int sendtag, int source, int recvtag, MPI_Comm comm, MPI_Status *status);

前者分别设置发送缓冲区、接收缓冲区,后者的发送和接收缓冲区是同一个。

非阻塞

非阻塞消息发送

int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request);
  • 大部分参数与 MPI_Send 一样
  • request:未完成的 MPI (发送)请求的句柄

非阻塞消息接收

int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request);
  • 大部分参数与 MPI_Recv 一样
  • request:未完成的 MPI (接收)请求的句柄

等待完成(阻塞)

int MPI_Wait(MPI_Request *request, MPI_Status *status);
  • request:未完成的 MPI (发送 / 接收)请求的句柄
  • status:函数返回时,存放发送方的进程号、消息 tag 等

检验完成(非阻塞)

int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);
  • request:未完成的 MPI (发送 / 接收)请求的句柄
  • flag:函数返回时, 非零值指示请求已完成
  • status:函数返回时,存放发送方的进程号、消息 tag 等

非连续数据打包

数据打包

int MPI_Pack(void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm);
  • inbuf:输入缓冲区
  • incount:输入数据项个数
  • datatype:输入数据项的类型
  • outbuf:输出缓冲区
  • outsize:输出缓冲区字节长度
  • position:缓冲区当前字节位置;函数返回时,存放缓冲区新的位置
  • comm:通信域

数据解包

int MPI_Unpack(void *inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm);
  • inbuf:输入缓冲区
  • insize:输入缓冲区字节长度
  • position:缓冲区当前字节位置;函数返回时,存放缓冲区新的位置
  • outbuf:输出缓冲区
  • outcount:输出数据项个数
  • datatype:输出数据项的类型
  • comm:通信域

聚合通信

为了更高效地完成多个进程间的消息传递,可以使用组通信。

基本组播

int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm);

在这里插入图片描述

分散

int MPI_Scatter(void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcnt, MPI_Datatype recvtype, int root, MPI_Comm comm);

在这里插入图片描述

归约

int MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

在这里插入图片描述

全部归约

int MPI_Allreduce (void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm);

在这里插入图片描述

聚集

int MPI_Gather (void *sendbuf, int sendcnt, MPI_Datatype sendtype,void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm);

在这里插入图片描述

全部聚集

 int MPI_Allgather (void *sendbuf, int sendcount, MPI_Datatype sendtype,void *recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm);

在这里插入图片描述

全到全

 int MPI_Alltoall(void *sendbuf, int sendcount, MPI_Datatype sendtype,void *recvbuf, int recvcnt, MPI_Datatype recvtype, MPI_Comm comm);

在这里插入图片描述

Communicator & Cartisen Grid

将若干结点自动排布成多维网格 dims

int MPI_Dims_create(int nnodes, int ndims, int dims[]);

创建一个笛卡尔拓扑结构的通信域 comm_cart

int MPI_Cart_create(MPI_Comm comm_old, int ndims, const int dims[], const int periods[], int reorder, MPI_Comm *comm_cart);

得到当前进程的笛卡尔坐标 coords

int MPI_Cart_coords(MPI_Comm comm, int rank, int maxdims, int coords[]);

得到当前笛卡尔空间通信域的信息 dims, periods, coords

int MPI_Cart_get(MPI_Comm comm, int maxdims, int dims[], int periods[], int coords[]);

得到某个笛卡尔坐标上进程的进程号 rank

int MPI_Cart_rank(MPI_Comm comm, const int coords[], int *rank);

将一个通信域按照颜色 color 分裂成多个通信域 newcomm,自己在新通信域中的进程号是 key

int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm);

用途:通用矩阵乘法(General Matrix Multiply, GEMM)的多进程并行。特殊的矩阵向量乘法可以:按列分割按行分割棋盘式分割

在这里插入图片描述

OpenMP(线程级并行)

OpenMP 是用于共享内存并行系统的多处理器程序设计的一套指导性编译处理方案。程序员通过在源代码中加入专用的 #pragma 来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。OpenMP 官网

简介

OpenMP 的运行模式为:Fork and Join

在这里插入图片描述

支持 C/C++,其编译指令为

gcc -fopenmp somepcode.c -o somepcode_openmp

OpenMP基本制导语句:

  • Omp sections
  • Omp parallel
  • Omp parallel for
  • Omp critical
  • ……

运行时函数:

  • omp_get_thread_num
  • omp_set_thread_num
  • omp_get_num_threads

环境变量:

  • OMP_NUM_THREADS

基本制导语句

#pragma omp parallel [clause[ [,] clause] ... ]
{//SPMD
}

clause 有如下的常用选择:

  • if([parallel :] scalar-expression):表达式为真,才会作用于此段代码;if 作用于 parallel 语句
  • num_threads(integer-expression)指定线程数
  • default(shared | none):缺省情况下数据都是 shared,设为 none 表示必须指定数据是否 shared
  • private(list)私有数据列表
  • firstprivate(list):私有数据继承前面的值
  • shared(list)共享数据列表
  • copyin(list)
  • reduction([reduction-modifier ,] reduction-identifier : list):对此数据做规约操作
  • proc_bind(master | close | spread):与 CPU 核的绑定方式
  • allocate([allocator :] list):选择 allocator

worksharing construct

共享工作结构:被一组 threads 恰好执行一次。For C/C++, worksharing constructs are for, sections, and single.

Sections

#pragma omp sections
{#pragma omp section//one calculation...#pragma omp section//another calculation
}

Single

#pragma omp single
{...
}

For

#pragma omp for [clause[ [,] clause] ... ]
for-loops{...
}

clause 有如下的常用选择:

  • private(list)私有变量列表
  • firstprivate(list):私有数据继承前面的值
  • lastprivate([lastprivate-modifier:] list):私有数据,最后一次迭代结果写回原变量
  • linear(list[: linear-step])
  • reduction([reduction-modifier,]reduction-identifier : list):归约
  • schedule([modifier [, modifier]:]kind[, chunk_size]):调度方式
  • collapse(n)合并 n 层循环后,再分配任务给各线程(注意,合并前后的 parivate(list) 不一定相同)
  • ordered[(n)]
  • nowait循环末尾不用等待其他线程(否则,默认在每轮循环末尾加 barrier 同步各线程)
  • allocate([allocator :]list)
  • order(concurrent)

这条 #pragma omp for 标记将会在编译时,派生N个线程,每个线程有自己的上下文(私有数据、共享数据)

  1. 循环变量是私有数据
  2. 其他数据缺省均为共享(必要时手动设置 private(list)

一般地,可以将 parallel 与 for 合用

//分开写
void simple(int n, float *a, float *b) {int i; #pragma omp parallel num_threads(5){#pragma omp for nowaitfor (i = 1; i < n; i++) /* i is private by default */b[i] = (a[i] + a[i – 1]) / 2.0;}
}//合用
void simple(int n, float *a, float *b) {int i; #pragma omp parallel for num_threads(5) nowait private(i)for (i = 1; i < n; i++) /* i is private by default */b[i] = (a[i] + a[i – 1]) / 2.0;
}

schedule() 可以有 static, dynamic, guided 等多种调度方式,

在这里插入图片描述

临界区 & 原子操作

Critical:临界区内的操作是原子的,无竞争

int i,j;
int b[N][M];
int x = 0;
#pragma omp parallel for private(j)
{for(i = 0; i < N; i++){  int m = i * i;for(j = 0; j < M, j++) b[i][j] = m*j;#pragma omp critical {x += b[i][j]; //临界区内,至多只有一个线程}}
}

Reduce:归约(类似 MPI),性能比 Critical 好

int i,j;
int b[N][M];
int x = 0;
#pragma omp parallel for private(j) reduction(+: x)
{for(i = 0; i < N; i++){ int m = i * i;for(j = 0; j < M, j++) x += m*j;}
}

Atomic:原子操作

#pragma omp atomic [clause[[[,] clause] ... ] [,]] atomic-clause [[,] clause [[[,] clause] ... ]]
expression-stmt

Atomic-clause 可以是 read, write, update, capture

Atomic 仅作用于制导语句下面的语句。与临界区相比,不要求仅有一个线程进入,只需保证所修饰语句操作的原子性。例如:

int x[n];
int i;
#pragma omp parallel for shared(x, y, index, n)
for (i=0; i<n; i++) {#pragma omp atomic updatex[index[i]] += work1(i); //多个threads可以同时访问数组x[n]y[i] += work2(i);
}

Task

使用 task 制导语句,定义任务,实现更加灵活的并行方式

  • task 内可以再派生 task
  • 可以自定义 task 的调度方式、优先级、任务间依赖关系
  • Included task / undeterred task:串行化的任务
  • 可以对任务加更多限制:mergeable,untied,final
int fib(int n){int i, j;if (n<2)return n;else{#pragma omp task shared(i)i=fib(n-1);#pragma omp task shared(j)j=fib(n-2);#pragma omp taskwaitreturn i+j;}
}int main(){#pragma omp parallel #pragma omp masterfib(n); //只能由一个线程生成task,任务分给各个线程执行
}

Taskgroup:产生一组任务,在结构结尾加上 barrier 同步所有任务

#pragma omp taskgroup [clause[[,] clause] ...]
structured-block

clause 有如下的常用选择:

  • task_reduction(reduction-identifier : list):任务归约
  • allocate([allocator :] list)
//递归生成task
void compute_tree(tree_type tree)
{if (tree->left){#pragma omp taskcompute_tree(tree->left);}if (tree->right){#pragma omp taskcompute_tree(tree->right);}#pragma omp taskcompute_something(tree);
}int main()
{int i;tree_type tree;init_tree(tree);#pragma omp parallel#pragma omp single{#pragma omp taskstart_background_work();for (i = 0; i < max_steps; i++) //由一个线程产生task{#pragma omp taskgroup{#pragma omp taskcompute_tree(tree);} //wait on tree traversal in this stepcheck_step();}} //only now is background work required to be completeprint_results();return;
}

http://chatgpt.dhexx.cn/article/vkv9P1v2.shtml

相关文章

算法5:普里姆算法

目录 1. 应用场景-修路问题2. 最小生成树3. 普里姆算法介绍4. 代码实现 1. 应用场景-修路问题 有7个村庄(A, B, C, D, E, F, G) &#xff0c;现在需要修路把7个村庄连通各个村庄的距离用边线表示(权) &#xff0c;比如 A – B 距离 5公里问&#xff1a;如何修路保证各个村庄都能…

使用粒子群PSO算法实现MPPT-M语言仿真

在Octave以及Matlab上&#xff0c;仿真了使用粒子群PSO实现MPPT的过程。粒子数为4。太阳能电池为4个串联。 2019年4月24日更新matlab代码。 目录 1.1 先绘制出PV曲线&#xff08;Octave&#xff09; 1.2 PSO算法&#xff08;Octave&#xff09; 2.1 绘制PV曲线&#xff08…

MPP概述

什么是MPP MPP (Massively Parallel Processing)&#xff0c;即大规模并行处理&#xff0c;在数据库非共享集群&#xff08;传统的单节点不属于集群&#xff0c;双机热备或Oracle RAC等&#xff0c;均是基于共享存储的&#xff09;中&#xff0c;每个节点都有独立的磁盘存储系…

粒子群算法(PSO)光伏发电 MPPT实现多峰值寻优,阴影遮蔽光伏发电算法 使用s函数编写粒子群算法,阴影遮蔽,实现多峰值寻优

粒子群算法&#xff08;PSO&#xff09;光伏发电 MPPT实现多峰值寻优&#xff0c;阴影遮蔽光伏发电算法 使用s函数编写粒子群算法&#xff0c;阴影遮蔽&#xff0c;实现多峰值寻优&#xff0c;解决经典mppt算法会形成局部最优的问题&#xff0c;追踪到最大峰值功率输出。 粒子群…

基于PSO粒子群算法的MPPT最大功率跟踪Simulink仿真,PSO采用S函数实现

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 MPPT控制器的全称是“最大功率点跟踪”&#xff08;Maximum Power Point Tracking&#xff09;太阳能控制器&#xff0c;是传统太阳能充放电控制器的升级换代产品。MPPT控制器能够实时侦测太阳能…

理解MP算法

转载&#xff1a;http://blog.csdn.net/u010103202/article/details/50932936 2&#xff0e;MP算法 作为一类贪婪算法&#xff0c;MP算法的基本思路是在迭代中不断找寻最有测量矩阵列来逼近被表示向量&#xff0c;继而寻得最优的稀疏逼近&#xff0c;使得x与y的残差最小。对于…

matlab simulink光伏发电系统MPPT算法

1、内容简介 略 553-可以交流、咨询、答疑 2、内容说明 世界各国能源需求的不断增长&#xff0c;以及传统能源资源的消耗和对环境的不良影 响&#xff0c;促使社会寻找替代能源。因此光伏发电成为研究热点之一&#xff0c;在对光伏电池的 研究中最大功率点追踪 (Maximum Pow…

MP算法与OMP算法

稀疏编码的一般最优化公式为&#xff1a; 其中的零范数为非凸优化。那么如何解这么一个非凸优化问题呢&#xff1f;其中一个常用的解法就是MP算法。 MP算法 MP算法是一种贪心算法&#xff08;greedy&#xff09;&#xff0c;每次迭代选取与当前样本残差最接近的原子&#xff0…

光伏并网MPPT算法控制解析

01 MPPT介绍 太阳能光伏发电是当前利用新能源的主要方式之一&#xff0c;光伏并网发电的主要问题是提高系统中太阳能电池阵列的工作效率和整个系统的工作稳定性&#xff0c;MPPT&#xff08;Maximum power point tracking,最大功率点跟踪&#xff09;是太阳能光伏发电系统中的…

MPC算法

MPC算法 一. 引言 在工程技术方面&#xff0c;MPC全称可指Model Predictive Control模型预测控制&#xff08;又称RHC, Receding Horizon &#xff09;。 模型预测控制算法 一种进阶过程控制方法&#xff0c;自1980年以来开始在化工炼油等过程工业得到应用&#xff0c;并在经…

MP算法和OMP算法及其思想与实现

主要介绍MP(Matching Pursuits)算法和OMP(Orthogonal Matching Pursuit)算法[1]&#xff0c;这两个算法虽然在90年代初就提出来了&#xff0c;但作为经典的算法&#xff0c;国内文献(可能有我没有搜索到)都仅描述了算法步骤和简单的应用&#xff0c;并未对其进行详尽的分析&…

MP算法

MP算法 MP算法是一种贪心算法&#xff08;greedy&#xff09;&#xff0c;每次迭代选取与当前样本残差最接近的原子&#xff0c;直至残差满足一定条件。 求解方法 首先解决两个问题&#xff0c;怎么定义“最接近原子”&#xff0c;怎么计算残差&#xff1f; 选择最接近残差的原…

MP算法和OMP算法及其思想

主要介绍MP(Matching Pursuits)算法和OMP(Orthogonal Matching Pursuit)算法[1]&#xff0c;这两个算法虽然在90年代初就提出来了&#xff0c;但作为经典的算法&#xff0c;国内文献(可能有我没有搜索到)都仅描述了算法步骤和简单的应用&#xff0c;并未对其进行详尽的分析&…

学习笔记2 光伏MPPT算法

目录 前言1. 光伏电池的分类1.1 按照电池结构分类1.2 按照电池材料分类: 2. 光伏电池模型及光伏特性曲线2.1 光伏电池模型2.2 光伏特性曲线 3. 影响光伏电池输出特性曲线的两个主要因素3.1 光照的影响3.1.1 光照对I-V曲线的影响3.1.2 光照对P-V曲线的影响3.1.3 光照对P-I曲线的…

光伏发电最大功率点跟踪MPPT(粒子群算法)

光伏电池作为太阳能发电的核心部件&#xff0c;实现了太阳能到电能的转换&#xff0c;但是由于光伏电池器件本身的复杂性以及现如今光电材料的限制&#xff0c;光伏电池的转换效率总体来说还是比较低&#xff0c;而且其输出还是非线性的&#xff0c;并且光照强度和外界温度对其…

光伏逆变器MPPT基本算法介绍-李星硕

前言 在上一个话题中&#xff0c;我们阐述了光伏MPPT基本原理&#xff1a;从本质上来说&#xff0c;MPPT算法均是通过DC-DC的占空比d来进行控制的。至于如何计算占空比d的值&#xff0c;则取决于具体的MPPT算法。那么在本话题中&#xff0c;我们将介绍两种基本的MPPT算法&#…

MPPT算法(恒定电压、扰动观察、电导增量)介绍与实现过程

目录 1、太阳能板的特性曲线 2、固定电压法 3、MPPT-P&O算法 4、电导增量算法 5、系统实现方案 1、太阳能板的特性曲线 太阳能板也叫光伏电池。是通过光电效应&#xff0c;把光能转换为电能的设备。 先介绍太阳能板的特性。太阳能的额定参数是在地面光伏组件标准测试…

嵌入式怎么入门,嵌入式应该先学习什么

嵌入式到底是什么&#xff0c;很多对这个概念都很迷糊&#xff0c;许多人都认为这是工程师的代名词。 嵌入式工程师可以说是目前涵盖面最广、最火的职业之一&#xff0c;那么到底什么是嵌入式呢&#xff1f; 狭义上嵌入式系统由硬件和软件组成&#xff0e;是能够独立进行运作的…

嵌入式通用学习路线整理

大家好&#xff0c;我是小麦。 从事嵌入式相关行业&#xff0c;差不多快有10年时间了&#xff0c;走过很多弯路&#xff0c;踩过很多坑。 很多人会问&#xff0c;嵌入式真的没有前途吗&#xff1f;这个我其实也无法回答。用发展的眼光来看&#xff0c;万物都有周期。 这个和嵌入…

嵌入式学习(一)嵌入式c语言

第一章.c数据类型及语句 1.01 第一个c程序的编写 下载好VScode并配置好环境&#xff0c;可以开始进行第一个c程序的编写。 #include <stdio.h>int main(int argc,char *argv[]) {printf("Hello World!\n");return 0 ; } 需要注意的几点&#xff1a; 1.#inclu…