Creating an MPI_Datatype for a structure containing pointers

c mpi
ap040 picture ap040 · May 2, 2012 · Viewed 7.7k times · Source

I have the following structure.

typedef struct
{
 int *Ai;
 double *Ax;
 int nz;
}column;

I want to transfer this structure using MPI_Send and MPI_Receive. How do I create an MPI_Datatype for this structure?

Answer

Hristo Iliev picture Hristo Iliev · May 4, 2012

MPI is designed to work with arrays of structures rather that with structures of arrays.

The MPI_Hindexed that @suszterpatt proposed is a terrible hack. It will only allow you to send one element of the structure type and only the element that was used to define the MPI data type. For other variables of the same structure type it is mostly guaranteed that the computed offsets will be wrong. Besides Hindexed types use one and the same MPI data type for all elements and thus does not allow you to send both ints and doubles.

The wise thing to do is to transform your program to use arrays of structures:

 typedef struct
 {
    int i;
    double z;
 } point;

 typedef struct
 {
    point *A;
    int nz;
 } column;

Now you can create an MPI structured type point_type and use it to send nz elements of that type giving column.A as the buffer address:

 int lens[3];
 MPI_Aint base, disps[2];
 MPI_Datatype oldtypes[2], point_struct, point_type;

 MPI_Get_address(&point, disps);
 MPI_Get_address(&point.z, disps+1);
 base = disps[0];

 lens[0] = 1; disps[0] = MPI_Aint_diff(disps[0], base); oldtypes[0] = MPI_INT;
 lens[1] = 1; disps[1] = MPI_Aint_diff(disps[1], base); oldtypes[1] = MPI_DOUBLE;
 MPI_Type_create_struct(2, lens, disps, oldtypes, &point_struct);
 MPI_Type_create_resized(point_struct, 0, sizeof(point), &point_type);
 MPI_Type_commit(&point_type);

 MPI_Send(column.A, column.nz, point_type, ...);

This first creates an MPI datatype point_struct that describes the layout of the structure members, but does not account for any padding at the end and therefore cannot be used to reliably send an array of such structures. Therefore, a second datatype point_type with the correct extent is created using MPI_Type_create_resized.

On the receiver side you would peek the message with MPI_Probe, extract the number of elements with MPI_Get_count with a type of point_type (that goes straight to the nz field), allocate the A field and use it in MPI_Recv to receive the nz elements:

 MPI_Status status;
 MPI_Probe(source, tag, comm, &status);
 MPI_Get_count(&status, point_type, &column.nz);
 if (nz == MPI_UNDEFINED)
   ... non-integral message was received, do something
 column.A = (point *)malloc(column.nz*sizeof(point));
 MPI_Recv(column.A, column.nz, point_type, source, tag, comm, MPI_STATUS_IGNORE);

If that code change is impossible you can still go through the intermediate step of transforming your structure before sending it, a process usually called (un-)marshaling. In your case do something like this (I assume that you store the number of array elements in both Ai and Ax in the nz field):

 point *temp = (point *)malloc(nz*sizeof(point));
 for (int i = 0; i < column.nz; i++)
 {
   temp[i].i = column.Ai[i];
   temp[i].z = column.Az[i];
 }
 MPI_Send(temp, nz, point_type, ...);
 free(temp);

On the receiver side you must do the opposite: allocate a large enough buffer that can hold the structure, receive the message in it and then do the opposite transformation.

Once again, you do not need to transmit the actual value of nz since it can be easily extracted from the length of the message using MPI_Get_count.