关键词:MPI自定义数据类型,subarray(子数组)
MPI在高性能计算中应用广泛,并且,MPI开始门槛很低,只要了解并行计算的基本概念和常用MPI函数(MPI_Init( )、MPI_Send( )等)就可以写出并行程序。如果想在此基础上优化程序、或做一些更复杂的计算,就需要用到更多MPI函数或掌握一些更高级的用法。
但同时,MPI本身也是一个非常古老的标准、参考资料有限(特别是中文资料更少),笔者在编写一种新的并行算法时,需要用到MPI_Type_create_subarray( )函数,网络上参考资料有限,因此笔者整理了一些资料和学习笔记,发在博客上,欢迎大家交流讨论。
参考文章
目前通过网络上能够搜到一些MPI_Type_create_subarray()函数的使用方法,但基本上所有的结果无非是把国外网站上的文章Google翻译了一下就贴过来(还丢失了一些东西),价值不大,不如去看原文。
1.stackoverflow: How to use MPI_Type_create_subarray?
2.微软的MPI文档:MPI_Type_create_subarray function
当然,这两篇文章都是英文的,写的略生涩。
MPI_Type_create_subarray( )函数做了什么?
这个函数其实创建了(准确的说是用于创建)一个子数组的自定义MPI数据类型,这个函数本身就做了这件事。其他的事情是配合其他函数一起做的。
为什么使用自定义数据类型?
MPI 的消息收发函数只能处理连续存储的同一类型的数据。不同系统有不同的数据表示格式。MPI预先定义一些基本数据类型,在实现过程中在这些基本数据类型为桥梁进行转换。
自定义数据类型的优势:它的使用可有效地减少消息传递的次数, 增大通信粒度, 并且在收/发消息时避免或减少数据在内存中的拷贝、复制。 所以,我们定义subarray,再进行Send/Revc等消息收发,在一些情况下可以提高效率,这是我们为什么要使用这个函数的原因。
如何使用MPI_Type_create_subarray( )
我们可以在API文档中找到这个函数的定义。
int MPI_Type_create_subarray(
int ndims,
_In_count_(ndims) int array_of_sizes[],
_In_count_(ndims) int array_of_subsizes[],
_In_count_(ndims) int array_of_starts[],
int order,
MPI_Datatype oldtype,
_Out_ MPI_Datatype *newtype
);
但其实真正核心的参数只有3个:
- bigarray 原来数组的大小
- subarray 子数组的大小
- starts 子数组的开始标记
但需要采用数组的方式对参数进行声明,以2维数组(父数组、子数组)为例。
/* array sizes */
const int bigsize =10;
const int subsize =5;
/*核心参数的定义*/
int starts[2] = {5,3};
int subsizes[2] = {subsize,subsize};
int bigsizes[2] = {bigsize, bigsize};
然后就可以使用create函数定义子数组类型了,定义后,我们还需要向MPI提交
/*1.*定义数据类型*/
MPI_Datatype mysubarray;
/*2.子数组创建函数*/
MPI_Type_create_subarray(2, bigsizes, subsizes, starts,
MPI_ORDER_C, MPI_INT, &mysubarray);
/*3.向MPI提交这个数据类型*/
MPI_Type_commit(&mysubarray);
这些步骤都完成后,我们就可以在消息函数中使用这个数据类型,也可以关注一下,是否有效率的提升。
MPI_Send(&(bigarray[0][0]), 1, mysubarray, receiver, ourtag, MPI_COMM_WORLD);
MPI_Recv(&(subarray[0][0]), subsize*subsize, MPI_INT, sender, ourtag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
一个完整的示例程序
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
void printarr(int **data, int n, char *str);
int **allocarray(int n);
int main(int argc, char **argv) {
/* array sizes */
const int bigsize =10;
const int subsize =5;
/* communications parameters */
const int sender =0;
const int receiver=1;
const int ourtag =2;
int rank, size;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if (size < receiver+1) {
if (rank == 0)
fprintf(stderr,"%s: Needs at least %d processors.\n", argv[0], receiver+1);
MPI_Finalize();
return 1;
}
if (rank == sender) {
int **bigarray = allocarray(bigsize);
for (int i=0; i<bigsize; i++)
for (int j=0; j<bigsize; j++)
bigarray[i][j] = i*bigsize+j;
printarr(bigarray, bigsize, " Sender: Big array ");
MPI_Datatype mysubarray;
int starts[2] = {5,3};
int subsizes[2] = {subsize,subsize};
int bigsizes[2] = {bigsize, bigsize};
MPI_Type_create_subarray(2, bigsizes, subsizes, starts,
MPI_ORDER_C, MPI_INT, &mysubarray);
MPI_Type_commit(&mysubarray);
MPI_Send(&(bigarray[0][0]), 1, mysubarray, receiver, ourtag, MPI_COMM_WORLD);
MPI_Type_free(&mysubarray);
free(bigarray[0]);
free(bigarray);
} else if (rank == receiver) {
int **subarray = allocarray(subsize);
/*for (int i=0; i<subsize; i++)
for (int j=0; j<subsize; j++)
subarray[i][j] = 0;*/
MPI_Recv(&(subarray[0][0]), subsize*subsize, MPI_INT, sender, ourtag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printarr(subarray, subsize, " Receiver: Subarray -- after receive");
free(subarray[0]);
free(subarray);
}
MPI_Finalize();
return 0;
}
void printarr(int **data, int n, char *str) {
printf("-- %s --\n", str);
for (int i=0; i<n; i++) {
for (int j=0; j<n; j++) {
printf("%3d ", data[i][j]);
}
printf("\n");
}
}
int **allocarray(int n) {
int *data = malloc(n*n*sizeof(int));
int **arr = malloc(n*sizeof(int *));
for (int i=0; i<n; i++)
arr[i] = &(data[i*n]);
return arr;
}
运行结果:(我们可以观察什么是子数组)
参考文献
1.谭光明,C04—数据类型和集合通信.pdf
2.都志辉,高性能计算之并行编程技术—MPI并行程序设计