上一次尝试了使用Send/Recv传递简单的build-in类型的数据。
有时候人们需要一次性传递多种不同类型不同数量的build-in类型数据,通常将数据组织成struct或class来实现。对于这种需求MPI提供了多种支持方式,如MPI_Type_vector、MPI_Type_indexed和MPI_Type_struct等。MPI_Type_struct可能是最通用的自定义类型生成器,因此下文主要是描述如何使用这种方式进行数据传递。
首先,展示一下需要传递的自定义数据
- class CTestSendRecv
- {
- public:
- CTestSendRecv(){}
- int m_n;
- double m_d;
- double m_arr[4];
- float m_f;
- };
如果要传递这样一个自定义类型的数据,MPI要求注册一个新的数据类型,注册时必须详细描述CTestSendRecv的数据结构及其成员对应的MPI类型定义(MPI_INT、MPI_DOUBLE等)。相关的函数调用和数据定义代码如下
- int blockLength[] = {1,1,4,1};
- MPI::Datatype oldTypes[] = {MPI_INT,MPI_DOUBLE,MPI_DOUBLE,MPI_FLOAT};
- //直接计算偏移量,每一个数据以double对齐
- MPI::Aint addressOffsets[]={0,sizeof(double),2*sizeof(double),6*sizeof(double)};
- MPI::Datatype newType =
- MPI::Datatype::Create_struct(
- sizeof(blockLength)/sizeof(int), /**/
- blockLength,
- addressOffsets,
- oldTypes);
- newType.Commit();
blockLength数组是用来描述CTestSendRecv中各个成员对应的build-in数据个数。
oldTypes数组是用来描述CTestSendRecv中各个成员对应的build-in数据的MPI类型定义。
addressOffsets是用来描述CTestSendRecv中各个成员离该CTestSendRecv类的首地址的偏移量,这里涉及到编译器的字节对齐的知识。当然MPI提供了MPI_Get_address这样的函数来计算偏移量,但使用起来较为麻烦。
MPI::Datatype::Create_struct为CTestSendRecv创建新的MPI类型。
newType.Commit();提交注册新的MPI数据类型。对应的注销数据类型的函数是newType.Free();
下面是完整的代码示例
- int main(int argc, char* argv[])
- {
- Cmpi mpi;
- mpi.PrintMachineInfoWithStdIO();
- int blockLength[] = {1,1,4,1};
- MPI::Datatype oldTypes[] = {MPI_INT,MPI_DOUBLE,MPI_DOUBLE,MPI_FLOAT};
- //直接计算偏移量,每一个数据以double对齐
- MPI::Aint addressOffsets[]={0,sizeof(double),2*sizeof(double),6*sizeof(double)};
- MPI::Datatype newType =
- MPI::Datatype::Create_struct(
- sizeof(blockLength)/sizeof(int), /**/
- blockLength,
- addressOffsets,
- oldTypes);
- newType.Commit();
- const int nMsgTag =999; /*消息标识号 */
- int nSendProcessId(1); /*发送数据的进程的编号*/
- int nRecvProcessId(0); /*目的进程的标识号*/
- const int minProcessorCount =2;
- if (mpi.GetTotalProcessCount() < minProcessorCount) {
- std::cout<<"Not enough processor"<<std::endl;
- return 0;
- }
- if (mpi.GetCurrentProcessId()==nSendProcessId) {
- CTestSendRecv testSend;
- testSend.m_d =5.6;
- testSend.m_f =8.2f;
- testSend.m_n =11;
- testSend.m_arr[0]= 1.1; testSend.m_arr[1]= 2.2; testSend.m_arr[2]= 3.3;testSend.m_arr[3]= 4.4;
- MPI::COMM_WORLD.Send(&testSend, /*发送数据的内存地址*/
- sizeof(testSend)/sizeof(CTestSendRecv), /*发送数据的数目*/
- newType, /*发送数据的类型*/
- nRecvProcessId, /*目的进程的标识号*/
- nMsgTag /*本消息标识号 MPI_ANY_TAG*/
- );
- }
- else {
- CTestSendRecv testRecv;; /*接收数据的变量*/
- if (nSendProcessId >= mpi.GetTotalProcessCount() ) return 0;
- MPI::COMM_WORLD.Recv(&testRecv,
- sizeof(testRecv)/sizeof(CTestSendRecv),
- newType,
- nSendProcessId,
- nMsgTag);
- std::cout<<"Data="<<testRecv.m_arr[2]<<std::endl;
- }
- newType.Free();
- return 0;
- }
这样就可以达到了传递自定义数据类型的目的。
从以上的代码看来,MPI必须以静态的方式了解自定义数据结构的组成,所以可能不支持模板类型的自定义数据。
还有一个遗留问题就是,MPI能否支持如下方式的嵌套自定义数据CNestTestData,还有待进一步验证。
- class CTestSendRecv
- {
- public:
- CTestSendRecv(){}
- int m_n;
- double m_d;
- double m_arr[4];
- float m_f;
- };
- class CNestedTestData
- {
- public:
- char m_c[5];
- CTestSendRecv m_testSendRecv;
- };