MPI中常用的通信方法

  • 问题描述
  • 梯形法求解积分的串行程序
  • 使用MPI并行化
    • 使用树形结构优化通信
    • 使用MPI_Reduce优化通信
    • 使用MPI_Bcast优化通信
    • 使用派生数据类型优化通信
    • 使用结构体优化通信(类似于派生数据类型)

本文总结了MPI中常用的通信方法,不包含 MPI_ScatterMPI_Gather,且不涉及对这些通信方法的详细解释,只是记录,详细解释可以参考 《并行程序设计导论》

问题描述

为了求解函数 f ( x ) f(x) f(x) 在区间 [ a , b ] [a,b] [a,b] 上的积分,我们可以使用梯形法得到该问题的一个近似解。

梯形法求解积分的串行程序

下面的代码给出了求解该问题的串行程序,其主要思路是:将区间 [ a , b ] [a,b] [a,b] 划为 n n n 个子区间,每个子区间上的积分值近似为一个梯形的面积,对这些梯形的面积求和即可得到最终的近似解。

#include <stdio.h>
//待积分的函数,这里为x*x
double f(double x){return x * x;
}// 读取输入
// [a, b]为积分区间,n为划分的子区间个数
void get_input(double* a, double* b, int* n){printf("[a, b]为积分区间\n");printf("n为划分的子区间个数\n");scanf("%lf%lf%d", a, b, n);
}// 梯形法求积分
// 积分区间为[a, b],划分的子区间个数为interval_count
// 每个子区间长度为interval_size,理论上该值等于(b-a)/interval_count
// f为待积分函数
double do_integral(double a, double b, int interval_count, double interval_size, double (*f)(double)){int i;double left, right, res = 0.0;for(i = 0; i < interval_count; ++i){left = a + i*interval_size;right = left + interval_size;double f_a = f(left), f_b = f(right);res = res + (f_a + f_b)*interval_size/2;}return res;
}int main(){int n; // 划分的子区间数double a, b; // 积分区间为[a, b]get_input(&a, &b, &n);double interval_size = (b-a)/n;double result = do_integral(a, b, n, interval_size, f);printf("f(x)在区间[%f,%f]上的积分是:%f\n", a, b, result);return 0;
}

使用MPI并行化

其主要思路是,将区间 [ a , b ] [a, b] [a,b] 划分为 n n n 个子区间,然后将这 n n n 个子区间分配给comm_sz个进程分别去积分,最后将它们的结果汇总起来得到最终的近似解

为了将各个进程的结果汇总,这里采用的方法是:0号进程负责接收其他所有进程发送过来的结果,并将它们以及0号进程自己计算出来的解相加,得到最终结果

#include <stdio.h>
#include "mpi.h"//待积分的函数,这里为x*x
double f(double x){return x * x;
}// 读取输入
// [a, b]为积分区间,n为划分的子区间个数
void get_input(int my_rank, int comm_sz, double* a, double* b, int* n){if(my_rank == 0){printf("[a, b]为待积分区间\n");printf("n为划分的子区间个数\n");printf("请依次输入a, b, n\n");scanf("%lf%lf%d", a, b, n);int dest;for(dest = 1; dest < comm_sz; ++dest){MPI_Send(a, 1, MPI_DOUBLE, dest, 0, MPI_COMM_WORLD);MPI_Send(b, 1, MPI_DOUBLE, dest, 0, MPI_COMM_WORLD);MPI_Send(n, 1, MPI_INT, dest, 0, MPI_COMM_WORLD);}}else{MPI_Recv(a, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);MPI_Recv(b, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);MPI_Recv(n, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);}
}// 梯形法求积分
// 每个进程负责对区间[local_a, local_b]进行积分,该区间已经被划分为interval_count个子区间
// 每个子区间长度为interval_size,理论上该值等于(local_b-local_a)/interval_count
// f为待积分函数
double do_integral(double local_a, double local_b, int interval_count, double interval_size, double* local_integral, double (*f)(double)){int i;double left, right, res = 0.0;for(i = 0; i < interval_count; ++i){left = local_a + i*interval_size;right = left + interval_size;double f_a = f(left), f_b = f(right);res = res + (f_a + f_b)*interval_size/2;}*local_integral = res;return res;
}// 该函数用来将各个进程得到的结果汇总起来,以得到最终的近似解
void aggregate_all_results(int my_rank, int comm_sz, double *all_result, double *local_result){// Use process 0 to receive all local results and add them.if(my_rank == 0){*all_result = *local_result;int i;for(i = 1; i < comm_sz; ++i){MPI_Recv(local_result, 1, MPI_DOUBLE, i, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);*all_result += (*local_result);}}else{MPI_Send(local_result, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);}
}int main(){// my_rank标识当前进程,comm_sz为总进程个数,n表示划分的子区间个数int my_rank, comm_sz, n;double a, b; // 积分区间是[a, b]MPI_Init(NULL, NULL);MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);get_input(my_rank, comm_sz, &a, &b, &n);// 为了简化,认为n为comm_sz的整数倍if(n % comm_sz){if(my_rank == 0)printf("进程数需要整除子区间个数\n");MPI_Finalize();return 0;}double local_integral = 0, all_integral = 0;double local_a, local_b, interval_size = (b-a)/n;int local_interval_count = n / comm_sz;local_a = a + my_rank*interval_size*local_interval_count;local_b = local_a + local_interval_count*interval_size;do_integral(local_a, local_b, local_interval_count, interval_size, &local_integral,f);// 将各个线程的结果汇总aggregate_all_results(my_rank, comm_sz, &all_integral, &local_integral);if(my_rank == 0){printf("f(x)在区间[%f,%f]上的积分是:%f\n", a, b, all_integral);}MPI_Finalize();return 0;
}

使用树形结构优化通信

使用树型结构可以降低通信的开销,树形结构的原理图如下所示,具体原理可以参照《并行程序设计导论》

这里用该方法优化aggregate_all_results函数中的通信,其余代码不变,修改后的aggregate_all_results如下所示

编译时需要加-lm选项

#include <math.h>
// 该函数用来将各个进程得到的结果汇总起来,以得到最终的近似解
void aggregate_all_results(int my_rank, int comm_sz, double *all_result, double *local_result){// Use Tree structure to communicateint level = (int)ceil(log2(comm_sz));int i;for(i = 1; i <= level; ++i){int gap = (1 << i), half_gap = gap >> 1;int mod = my_rank % gap;if(mod == half_gap){int send_to = my_rank - half_gap;if(send_to < 0)continue;MPI_Send(local_result, 1, MPI_DOUBLE, send_to, 0, MPI_COMM_WORLD);}else if(mod == 0){double recv_res;int recv_from = my_rank + half_gap;if(recv_from >= comm_sz)continue;MPI_Recv(&recv_res, 1, MPI_DOUBLE, recv_from, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);*local_result += recv_res;}}if(my_rank == 0)*all_result = *local_result;
}

使用MPI_Reduce优化通信

可以使用集合通信来代替点到点通信

// 该函数用来将各个进程得到的结果汇总起来,以得到最终的近似解
void aggregate_all_results(int my_rank, int comm_sz, double *all_result, double *local_result){MPI_Reduce(local_result, all_result, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
}

使用MPI_Bcast优化通信

使用广播来优化get_input函数中的通信,修改后的代码如下所示:

// 读取输入
// [a, b]为积分区间,n为划分的子区间个数
void get_input(int my_rank, int comm_sz, double* a, double* b, int* n){if(my_rank == 0){printf("[a, b]为待积分区间\n");printf("n为划分的子区间个数\n");printf("请依次输入a, b, n\n");scanf("%lf%lf%d", a, b, n);}MPI_Bcast(a, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);MPI_Bcast(b, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);MPI_Bcast(n, 1, MPI_INT, 0, MPI_COMM_WORLD);
}

使用派生数据类型优化通信

使用派生数据类型优化get_input函数中的通信,修改后的代码如下所示:

// 读取输入
// [a, b]为积分区间,n为划分的子区间个数
void get_input(int my_rank, int comm_sz, double* a, double* b, int* n){if(my_rank == 0){printf("[a, b]为待积分区间\n");printf("n为划分的子区间个数\n");printf("请依次输入a, b, n\n");scanf("%lf%lf%d", a, b, n);}int blocks_length[] = {1, 1, 1};MPI_Aint a_addr, b_addr, n_addr, displacements[3];MPI_Get_address(a, &a_addr);displacements[0] = 0;MPI_Get_address(b, &b_addr);displacements[1] = b_addr - a_addr;MPI_Get_address(n, &n_addr);displacements[2] = n_addr - a_addr;MPI_Datatype types[3] = {MPI_DOUBLE, MPI_DOUBLE, MPI_INT};MPI_Datatype mpi_my_type;MPI_Type_create_struct(3, blocks_length, displacements, types, &mpi_my_type);MPI_Type_commit(&mpi_my_type);MPI_Bcast(a, 1, mpi_my_type, 0, MPI_COMM_WORLD);MPI_Type_free(&mpi_my_type);
}

使用结构体优化通信(类似于派生数据类型)

该方法的思路其实和使用派生数据类型差不多,而且个人觉得使用起来比派生数据类型方便太多,但是这样写会不会有什么隐患不太确定,仅供参考

typedef struct my_type{double a, b;int n;
} my_type;
// 读取输入
// [a, b]为积分区间,n为划分的子区间个数
void get_input(int my_rank, int comm_sz, double* a, double* b, int* n){if(my_rank == 0){printf("[a, b]为待积分区间\n");printf("n为划分的子区间个数\n");printf("请依次输入a, b, n\n");scanf("%lf%lf%d", a, b, n);}my_type all_input;all_input.a = *a;all_input.b = *b;all_input.n = *n;MPI_Bcast(&all_input, sizeof(my_type), MPI_BYTE, 0, MPI_COMM_WORLD);if(my_rank){*a = all_input.a;*b = all_input.b;*n = all_input.n;}
}

MPI中常用的通信方法相关推荐

  1. 统计计量 | 统计学中常用的数据分析方法汇总

    来源:数据Seminar本文约10500字,建议阅读15+分钟 统计学中常用的数据分析方法汇总. Part1描述统计 描述统计是通过图表或数学方法,对数据资料进行整理.分析,并对数据的分布状态.数字特 ...

  2. 5种JavaScript中常用的排序方法

    5种JavaScript中常用的排序方法 01.冒泡排序 通过相邻数据元素的交换,逐步将待排序序列变为有序序列,如果前面的数据大于后面的数据,就将两值进行交换,将数据进行从小到大的排序,这样对数组的第 ...

  3. 深度学习中常用的误差方法

    深度学习中常用的误差方法有: 标准差(Standard Deviation): 标准差也叫均方差,是方差的算术平方根,反应数据的离散程度 ,标准差越小,数据偏离平均值越小,反之亦然 . 公式为: py ...

  4. VB的一些项目中常用的通用方法-一般用于验证类

    1.VB的一些项目中常用的通用方法: ' 设置校验键盘输入值,数字 Public Function kyd(key As Integer) As Integer '20060728 Dim mycha ...

  5. 光滑噪声数据常用的方法_数据挖掘中常用的数据清洗方法

    是新朋友吗?记得先点蓝字关注我哦- 数据挖掘中 常用的数据清洗方法 在数据挖掘过程中,数据清洗主要根据探索性分析后得到的一些结论入手,然后主要对四类异常数据进行处理,分别是缺失值(missing va ...

  6. lammps和atomsk中常用的建模方法

    文章目录 1.Atomsk生成刃型位错 2.Atomsk生成任意角度晶界 3.Atomsk生成孪晶 4.Atomsk生成非晶 5.合金多晶结构建模 6.Atomsk建立管道类模型 7.Atomsk建立 ...

  7. 夺命雷公狗jquery---18jquery中常用属性(方法)

    <!DOCTYPE html> <html><head><meta charset="utf-8"><title>< ...

  8. JS lodash库在开发中常用到的方法

    目录 一.摘要 二.常用方法 一.摘要 lodash是JS一个开箱即用的库函数,里面对于在日常开发中常用到的方法都是已经封装好的,使用起来非常方便,本篇记录了在日常开发过程总经常用的方法,就大概记录一 ...

  9. 【Arduino串口数据保存到excel中常用三种方法】

    [Arduino串口数据保存到excel中常用三种方法] 1. 前言 2. 利用excel自带Data Streamer读取 2.1 启用 Data Streamer 加载项 2.2 刷写代码并将微控 ...

最新文章

  1. 2016政策与市场协同发力大数据,小公司如何搏杀BAT?
  2. 高级特性-多线程,GUI
  3. java NIO概述
  4. OO实现ALV TABLE 五:ALV的栏位属性
  5. linux 进程管理 ppt,Linux内核结构与进程管理.ppt
  6. J-LINK7 固件修复
  7. java单位数_java – 优化代码以查找给定数量N的阶乘的单位数
  8. 900万注释图像数据集升级了!谷歌开放Open Images V6,首增语音、文本、鼠标轨迹同步注释...
  9. .net企业级架构实战之1——框架综述
  10. Vijos P1911 珠心算测验【序列处理】
  11. DXperience 换肤
  12. 思科命令大全_【汇总】思科网络设备产品型号大全!超全解释~
  13. python第三方库官方文档汇总
  14. input输入框对伪类(after,before)支持情况
  15. 编程技巧│使用 python 操作手机 app 超详细步骤
  16. Amesim学习——弹球仿真
  17. Python3:类和对象-烤地瓜
  18. 设计模式-Facade门面-Mediator调停者
  19. 870-Linux下解决高并发socket最大连接数限制
  20. 信创办公–基于WPS的EXCEL最佳实践系列 (筛选重要数据)

热门文章

  1. TCP在广域网相互通信
  2. 普罗米修斯zookeeper
  3. 卡特尔世界杯来了,只喝精酿啤酒不玩望京扑克,其实也是一种缺失
  4. 【极简版GH60】【GH60剖析】【五】壳和键帽的装配
  5. JDBC浅尝辄止——JAVA如何使用最朴素的方法连通数据库
  6. IDEA使用C3P0连接Mysql数据库
  7. 模糊的正确和精确的错误
  8. linux安装prometheus-webhook-dingtalk(二)
  9. 深度解析 | 俞军的产品方法论
  10. 计算机怎么搜索程序和文件格式,「闪电搜索」一款电脑必备的搜索文件软件,还有Everything...