这一章讲了MPI非阻塞通信的原理和一些函数接口,最后再用非阻塞通信方式实现Jacobi迭代,记录学习中的一些知识。

(1)阻塞通信与非阻塞通信

阻塞通信调用时,整个程序只能执行通信相关的内容,而无法执行计算相关的内容;

非阻塞调用的初衷是尽量让通信和计算重叠进行,提高程序整体执行效率。

整体对比见下图:

(2)非阻塞通信的要素

非阻塞通信调用返回意味着通信开始启动;而非阻塞通信完成则需要调用其他的接口来查询。

要素1:非阻塞通信的调用接口

要素2:非阻塞通信的完成查询接口

理想的非阻塞通信设计应该如下:

非阻塞通信的 发送 和 接受 过程都需要同时具备以上两个要素,“调用+完成”

“调用”按照通信方式的不同(标准、缓存、同步、就绪),有各种函数接口,具体用到哪个就查手册的性质。

这里“完成”是重点,因为程序员需要知道非阻塞调用是否执行完成了,来做下一步的操作。

MPI为“完成”定义了一个内部变量MPI_Request request,每个request与一个在非阻塞调用发生时与该调用发生关联(这里的调用包括发送和接收)。

“完成”不区分通信方式的不同,统一用MPI_Wait系列函数来完成,这里对MPI_Wait函数做一点说明:

1)MPI_Wait(MPI_Request *request),均等着request执行完毕了,再往下进行

2)对于非重复非阻塞通信,MPI_Wait系列函数调用的返回,还意味着request对象被释放了,程序员不用再显式释放request变量。

3)对于重复非阻塞通信,MPI_Wait系列函数调用的返回,意味着将于request对象关联的非阻塞通信处于不激活状态,并不释放request

关于2)3)看后面的代码示例就了解了

(3)非阻塞调用实现Jacobi迭代

有了非阻塞调用的技术,可以再将Jacobi迭代的程序效率提升,其总体的实现思路如下:

1)先计算Jacobi迭代下次计算所需要的边界数据,这些数据与每个计算节点中的计算无关,可以先独立计算好

2)启动非阻塞通信,将边界数据在进程间传递

3)计算每个计算节点可以独立计算的部分;此时,2)中启动的非阻塞通信也在进行中,这时通信和计算就重叠了

4)等着非阻塞通信完成,再进行下一次迭代

再回顾一下之前用阻塞通信实现Jacobi迭代的思路:

1)先传递边界数据

2)等着数据都传递完了,再进行计算

3)等着计算完成了,进行下一次迭代

可以看到阻塞通信中实现Jacobi迭代的程序中,在同一计算节点下,通信和计算是分别进行的,效率不如非阻塞通信。

总结起来:

“单机程序 → 阻塞通信MPI程序” 实现单机计算到多机计算,用并行代替串行提高效率。

“阻塞MPI程序 → 非阻塞MPI程序” 不仅将多台机器之间的并行,而且还能将每台机器的通信与计算过程并行,实现更高效的并行。

(4)非阻塞通信实现Jacobi迭代的代码

书上的源代码是Fortan的,数据存储是列优先的,矩阵按列分块;下面的代码是我翻译的C的代码,数据存储是行优先的,矩阵按行分块。

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h> #define N 8
#define SIZE N/4
#define T 2 void print_matrix(int myid, float myRows[][N]); int main(int argc, char *argv[])
{
float matrix1[SIZE+][N], matrix2[SIZE+][N];
int myid;
MPI_Status status[];
MPI_Request request[]; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid); // 初始化
int i,j;
for(i=; i<SIZE+; i++)
{
for(j=; j<N; j++)
{
matrix1[i][j] = matrix2[i][j] = ;
}
}
if(==myid) // 按行划分 上面第一分块矩阵 上边界
{
for(j=; j<N; j++) matrix1[][j] = matrix2[][j] = N;
}
if (==myid) { // 按行划分 最下面一分块矩阵 下边界
for(j=; j<N; j++) matrix1[SIZE][j] = matrix2[SIZE][j] = N;
}
for(i=; i<SIZE+; i++) // 每个矩阵的两侧边界
{
matrix1[i][] = matrix1[i][N-] = matrix2[i][] = matrix2[i][N-] = N;
}
// 引入虚拟进程 并计算每个进程上下相邻进程
int up_proc_id = myid== ? MPI_PROC_NULL : myid-;
int down_proc_id = myid== ? MPI_PROC_NULL : myid+;
// jacobi迭代过程
int t,row,col;
for(t=; t<T; t++)
{
// 1 计算边界数据
if(==myid) // 最上的矩阵块
{
for (col=; col<N-; col++)
{
matrix2[SIZE][col] = (matrix1[SIZE][col-]+matrix1[SIZE][col+]+matrix1[SIZE+][col]+matrix1[SIZE-][col])*0.25;
}
}
else if (==myid) { // 最下的矩阵块
for (col=; col<N-; col++)
{
matrix2[][col] = (matrix1[][col-]+matrix1[][col+]+matrix1[][col]+matrix1[][col])*0.25;
}
}
else {
for(col=; col<N-; col++) // 中间的矩阵块
{
matrix2[SIZE][col] = (matrix1[SIZE][col-]+matrix1[SIZE][col+]+matrix1[SIZE+][col]+matrix1[SIZE-][col])*0.25;
matrix2[][col] = (matrix1[][col-]+matrix1[][col+]+matrix1[][col]+matrix1[][col])*0.25;
}
}
// 2 利用非阻塞函数传递边界数据 为下一次计算做准备
int tag1 = , tag2 = ;
MPI_Isend(&matrix2[][], N, MPI_FLOAT, up_proc_id, tag1, MPI_COMM_WORLD, &request[]);
MPI_Isend(&matrix2[SIZE][], N, MPI_FLOAT, down_proc_id, tag2, MPI_COMM_WORLD, &request[]);
MPI_Irecv(&matrix1[SIZE+][], N, MPI_FLOAT, down_proc_id, tag1, MPI_COMM_WORLD, &request[]);
MPI_Irecv(&matrix1[][], N, MPI_FLOAT, up_proc_id, tag2, MPI_COMM_WORLD, &request[]);
// 3 计算中间数据
int begin_row = ==myid ? : ;
int end_row = ==myid ? (SIZE-) : SIZE;
for (row=begin_row; row<end_row; row++)
{
for (col=; col<N-; col++)
{
matrix2[row][col] = (matrix1[row][col-]+matrix1[row][col+]+matrix1[row+][col]+matrix1[row-][col])*0.25;
}
}
// 4 更新矩阵 并等待各个进程间数据传递完毕
for (row=begin_row; row<=end_row; row++)
{
for (col=; col<N-; col++)
{
matrix1[row][col] = matrix2[row][col];
}
}
MPI_Waitall(, &request[], &status[]);
}
MPI_Barrier(MPI_COMM_WORLD);
print_matrix(myid, matrix1);
MPI_Finalize();
} void print_matrix(int myid, float myRows[][N])
{
int i,j;
int buf[];
MPI_Status status;
buf[] = ;
if ( myid> ) {
MPI_Recv(buf, , MPI_INT, myid-, , MPI_COMM_WORLD, &status);
}
printf("Result in process %d:\n", myid);
for ( i = ; i<SIZE+; i++)
{
for ( j = ; j<N; j++)
printf("%1.3f\t", myRows[i][j]);
printf("\n");
}
if ( myid< ) {
MPI_Send(buf, , MPI_INT, myid+, , MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
}

程序的执行结果如下:

上述程序设计的逻辑如下:

1)各个分块矩阵的边界数据是可以需要通信交换的

2)先计算边界数据,尽量把需要通信交换而且又相对独立的数据先计算出来

3)用非阻塞通信传递分块矩阵的边界数据;同时每个节点内计算内部的数据;计算与通信并行

4)等到每个计算节点的2个发送、2个接收,总共4个非阻塞调用都完成了,进行下一轮迭代

(5)重复非阻塞通信

上面实现Jacobi迭代的代码中,以进程1和进程2为例:

1)迭代一轮二者之间就需要互相通信一次

2)每次互相通信,随着MPI_Wait的执行,request通信对象释放,两个进程通信完全被切断了

3)两个进程之间每次通信,有一些通信连接操作都是重复的,最好不用每次通信都重新执行这些连接操作,以此提高效率

4)因此,比上面实现jacobi迭代更优化一些的做法是:每次不完全掐断两个进程的非阻塞通信,保持那些基础的通用的操作,每次迭代只需要更新需要传输的数据,再激活两个进程之间的非阻塞通信

依照上面的思路,MPI给出了重复非阻塞的通信调用实现。用重复非阻塞的通信再实现一次Jacobi迭代,代码如下:

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h> #define N 8
#define SIZE N/4
#define T 2 void print_matrix(int myid, float myRows[][N]); int main(int argc, char *argv[])
{
float matrix1[SIZE+][N], matrix2[SIZE+][N];
int myid;
MPI_Status status[];
MPI_Request request[]; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid); // 初始化
int i,j;
for(i=; i<SIZE+; i++)
{
for(j=; j<N; j++)
{
matrix1[i][j] = matrix2[i][j] = ;
}
}
if(==myid) // 按行划分 上面第一分块矩阵 上边界
{
for(j=; j<N; j++) matrix1[][j] = matrix2[][j] = N;
}
if (==myid) { // 按行划分 最下面一分块矩阵 下边界
for(j=; j<N; j++) matrix1[SIZE][j] = matrix2[SIZE][j] = N;
}
for(i=; i<SIZE+; i++) // 每个矩阵的两侧边界
{
matrix1[i][] = matrix1[i][N-] = matrix2[i][] = matrix2[i][N-] = N;
}
// 引入虚拟进程 并计算每个进程上下相邻进程
int up_proc_id = myid== ? MPI_PROC_NULL : myid-;
int down_proc_id = myid== ? MPI_PROC_NULL : myid+;
// 初始化重复非阻塞通信
int tag1 = , tag2 = ;
MPI_Send_init(&matrix2[][], N, MPI_FLOAT, up_proc_id, tag1, MPI_COMM_WORLD, &request[]);
MPI_Send_init(&matrix2[SIZE][], N, MPI_FLOAT, down_proc_id, tag2, MPI_COMM_WORLD, &request[]);
MPI_Recv_init(&matrix1[SIZE+][], N, MPI_FLOAT, down_proc_id, tag1, MPI_COMM_WORLD, &request[]);
MPI_Recv_init(&matrix1[][], N, MPI_FLOAT, up_proc_id, tag2, MPI_COMM_WORLD, &request[]);
// jacobi迭代过程
int t,row,col;
for(t=; t<T; t++)
{
// 1 计算边界数据
if(==myid) // 最上的矩阵块
{
for (col=; col<N-; col++)
{
matrix2[SIZE][col] = (matrix1[SIZE][col-]+matrix1[SIZE][col+]+matrix1[SIZE+][col]+matrix1[SIZE-][col])*0.25;
}
}
else if (==myid) { // 最下的矩阵块
for (col=; col<N-; col++)
{
matrix2[][col] = (matrix1[][col-]+matrix1[][col+]+matrix1[][col]+matrix1[][col])*0.25;
}
}
else {
for(col=; col<N-; col++) // 中间的矩阵块
{
matrix2[SIZE][col] = (matrix1[SIZE][col-]+matrix1[SIZE][col+]+matrix1[SIZE+][col]+matrix1[SIZE-][col])*0.25;
matrix2[][col] = (matrix1[][col-]+matrix1[][col+]+matrix1[][col]+matrix1[][col])*0.25;
}
}
// 2 启动重复非阻塞通信
MPI_Startall(, &request[]);
// 3 计算中间数据
int begin_row = ==myid ? : ;
int end_row = ==myid ? (SIZE-) : SIZE;
for (row=begin_row; row<end_row; row++)
{
for (col=; col<N-; col++)
{
matrix2[row][col] = (matrix1[row][col-]+matrix1[row][col+]+matrix1[row+][col]+matrix1[row-][col])*0.25;
}
}
// 4 更新矩阵 并等待各个进程间数据传递完毕
for (row=begin_row; row<=end_row; row++)
{
for (col=; col<N-; col++)
{
matrix1[row][col] = matrix2[row][col];
}
}
MPI_Waitall(, &request[], &status[]);
}
int n;
for(n = ; n < ; n++) MPI_Request_free(&request[n]); // 释放非阻塞通信对象
MPI_Barrier(MPI_COMM_WORLD);
print_matrix(myid, matrix1);
MPI_Finalize();
} void print_matrix(int myid, float myRows[][N])
{
int i,j;
int buf[];
MPI_Status status;
buf[] = ;
if ( myid> ) {
MPI_Recv(buf, , MPI_INT, myid-, , MPI_COMM_WORLD, &status);
}
printf("Result in process %d:\n", myid);
for ( i = ; i<SIZE+; i++)
{
for ( j = ; j<N; j++)
printf("%1.3f\t", myRows[i][j]);
printf("\n");
}
if ( myid< ) {
MPI_Send(buf, , MPI_INT, myid+, , MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
}

1)上述的代码不难理解,可以查阅相关函数手册;最核心的思想就是,如果两个进程有多次迭代通信,就可以用这种重复非阻塞的通信函数。

2)另外,对于重复非阻塞通信的调用,在调用MPI_Wait系列函数时,不会释放与通信关联的request函数(即上面说的保持一些共性的通信设定操作,不完全掐断),因此,需要在line98中,程序员手动释放非则色通信操作对象

【MPI学习4】MPI并行程序设计模式:非阻塞通信MPI程序设计的更多相关文章

  1. 【MPI学习5】MPI并行程序设计模式:组通信MPI程序设计

    相关章节:第13章组通信MPI程序设计. MPI组通信与点到点通信的一个重要区别就是:组通信需要特定组内所有成员参与,而点对点通信只涉及到发送方和接收方. 由于需要组内所有成员参与,因此也是一种比较复 ...

  2. 【MPI学习3】MPI并行程序设计模式:不同通信模式MPI并行程序的设计

    学习了MPI四种通信模式 及其函数用法: (1)标准通信模式:MPI_SEND (2)缓存通信模式:MPI_BSEND (3)同步通信模式:MPI_SSEND (4)就绪通信模式:MPI_RSEND ...

  3. 【MPI学习6】MPI并行程序设计模式:具有不连续数据发送的MPI程序设计

    基于都志辉老师<MPI并行程序设计模式>第14章内容. 前面接触到的MPI发送的数据类型都是连续型的数据.非连续类型的数据,MPI也可以发送,但是需要预先处理,大概有两类方法: (1)用户 ...

  4. Java进阶7 并发优化2 并行程序设计模式

    Java进阶7 并发优化2 并行程序设计模式20131114 1.Master-worker模式 前面讲解了Future模式,并且使用了简单的FutureTask来实现并发中的Future模式.下面介 ...

  5. 用Java实现非阻塞通信

    用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的acc ...

  6. Java NIO Socket 非阻塞通信

    相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现 客户端: package com.test.client; import java.io. ...

  7. 利用Python中SocketServer 实现客户端与服务器间非阻塞通信

    利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信 版权声明 本文转自:http://blog.csdn.net/cnmilan/article/details/9664823 ...

  8. UE4 Socket多线程非阻塞通信

    转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...

  9. 【python】网络编程-SocketServer 实现客户端与服务器间非阻塞通信

    利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信.首先,先了解下SocketServer模块中可供使用的类:BaseServer:包含服务器的核心功能与混合(mix-in)类 ...

随机推荐

  1. TFS配置过程中的错误

    有些人在配置TFS的过程中会报出[以前的更新或安装需要重新启动操作系统.……]的错误,但会发现无论重启多次操作系统,再配置的时候依然会报这个错误,很是让人苦恼哦. 这个错误在安装SharePoint的 ...

  2. netty-socketio

    一.简介 netty-socketio是一个开源的Socket.io服务器端的一个java的实现,它基于Netty框架.项目地址为:https://github.com/mrniko/netty-so ...

  3. redis AOF保存机制

    网上说AOF有三种保存方式,不自动保存.每秒自动保存.每命令自动保存. 其中每秒自动保存这个看起来很美好,但是可能会被各种IO的时间所延迟,所以究竟是怎么判断每秒保存的,并不是太明白,故有此文. AO ...

  4. QT添加程序图标及窗口图标

    程序图标 材料准备 图标文件:*.ico文件,存放在源文件同一目录下,如"myapp.ico" 写入图标 向*.pro文件中,独立一行写入"RC_ICONS = *.ic ...

  5. Linux Purify命令

    一.简介 在C/C++的软件开发中,没有任何一种工具可以让你的应用程序避免引入内存问题,但是我们可以使用诸如Purify这样的工具对已经做好了的程序进行内存问题的检查.Purify的强大之处是可以找到 ...

  6. java如果读取xml内容

    本文介绍的是使用dom4j方式读取,如需要其他方式可自行百度. 1.首先导入dom4j的jar包:http://www.dom4j.org/dom4j-1.6.1/ 2.准备xml文件 <?xm ...

  7. Google play billing(Google play 内支付) 上篇

    写在前面: 最近Google貌似又被全面封杀了,幸好在此之前,把Google play billing弄完了,现在写篇 博客来做下记录.这篇博客一是自己做个记录,二是帮助其他有需要的人.因为现在基本登 ...

  8. maven总结4

     仓库.nexus 构件:在maven中,任何一个依赖(jar包).插件(maven-compiler-plugin-2.5.1.jar)或者项目输出(前面例子中运行mvn clean install ...

  9. java 16-6 泛型

    ArrayList存储字符串并遍历 我们按照正常的写法来写这个程序, 结果确出错了. 为什么呢? 因为我们开始存储的时候,存储了String和Integer两种类型的数据. 而在遍历的时候,我们把它们 ...

  10. java13-5 JDK1.5以后的一个新特性和Integer的面试题

    1.JDK5的新特性 自动装箱:把基本类型转换为包装类类型 自动拆箱:把包装类类型转换为基本类型 注意一个小问题: 在使用时,Integer x = null;代码就会出现NullPointerExc ...