Say you have a 4x4 matrix and you want to send the middle four elements:
int main( int argc, char ** argv )
{
int m[4][4] = { 5, 5, 5, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 5, 5, 5 };
int n[4][4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int rank;
MPI_Status mystatus;
int i, j;
char buffer[100];
int position = 0;
MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
if ( rank == 0 )
{
MPI_Pack( &m[1][1], 2, MPI_INT, buffer, 100, &position, MPI_COMM_WORLD ) ;
MPI_Pack( &m[2][1], 2, MPI_INT, buffer, 100, &position, MPI_COMM_WORLD ) ;
MPI_Send( buffer, 100, MPI_PACKED, 1, 0, MPI_COMM_WORLD );
}
else
{
for (i = 0; i < 4; i++)
{
printf("\n" );
for (j=0; j < 4; j++)
printf("%i ", n[i][j] );
}
MPI_Recv( buffer, 100, MPI_PACKED, 0, 0, MPI_COMM_WORLD, &mystatus );
MPI_Unpack( buffer, 100, &position, &n[1][1], 2, MPI_INT, MPI_COMM_WORLD);
MPI_Unpack( buffer, 100, &position, &n[2][1], 2, MPI_INT, MPI_COMM_WORLD);
printf("\n" );
for (i = 0; i < 4; i++)
{
printf("\n" );
for (j=0; j < 4; j++)
printf("%i ", n[i][j] );
}
}
MPI_Finalize( );
return 0;
}
So, basically what you can do is pack one row at a time and then send it off. The output looks something like this:
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 4 4 0
0 4 4 0
0 0 0 0
MPI_Type_vector
int MPI_Type_vector( count, blocklen, stride, old_type, newtype )
int count;
int blocklen;
int stride;
MPI_Datatype old_type;
MPI_Datatype *newtype;
int MPI_Type_commit ( datatype )
MPI_Datatype *datatype;
MPI_Type_vector will allow you to send submatrices. In a sense, you are creating a chunk type. Then you commit that chunk type with MPI_Type_commit. So, count is the height of the submatrix or number of rows, blocklen is the width of the submatrix or number of columns, and stride is the width of the matrix.
How to use MPI_Type_vector to send a submatrix:
Say you have a 4x4 matrix and you want to send the middle four elements:
int main( int argc, char ** argv )
{
int vector[4][4] = { 5, 5, 5, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 5, 5, 5 };
int wvector[4][4] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int blocklengths = 2;
int stride = 4;
int rank;
MPI_Datatype mytype;
MPI_Status mystatus;
int i, j;
MPI_Init( &argc, &argv );
MPI_Type_vector( 2, 2, 4, MPI_INT, &mytype );
MPI_Type_commit( &mytype );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
if ( rank == 0 )
{
MPI_Send( &vector[1][1], 1, mytype, 1, 0, MPI_COMM_WORLD );
}
else
{
for (i = 0; i < 4; i++)
{
printf("\n" );
for (j=0; j < 4; j++)
printf("%i ", wvector[i][j] );
}
MPI_Recv( &wvector[1][1], 1, mytype, 0, 0, MPI_COMM_WORLD, &mystatus );
printf("\n" );
for (i = 0; i < 4; i++)
{
printf("\n" );
for (j=0; j < 4; j++)
printf("%i ", wvector[i][j] );
}
}
MPI_Finalize( );
return 0;
}
So, the other question is how do you use MPI_Type_vector when you are packing up submatrix and unpacking it into a smaller matrix?
Basically, this amounts to type matching. So, MPI stores types by:
{(t0, d0), (t1, d1), (t2, d2), ..., (tn, dn)}
where ti is the MPI type and di is the displacement in bytes from the start of the array. The following is referred to as a type signature
{t0, t1, t2, ... , tn}
So, as long as the type signature of any user defined type matches, there is a type match. In the other words, the displacement (or stride) doesn't matter. The following code shows how to pack the 2x2 submatrix above into a 2x2 matrix.
int main( int argc, char ** argv )
{
int vector[4][4] = { 5, 5, 5, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 5, 5, 5 };
int nvector[2][2];
int blocklengths = 2;
int stride = 4;
int rank;
MPI_Datatype mytype;
MPI_Datatype wtype;
MPI_Status mystatus;
int i, j;
MPI_Init( &argc, &argv );
MPI_Type_vector( 2, 2, 4, MPI_INT, &mytype );
MPI_Type_vector( 2, 2, 2, MPI_INT, &wtype );
MPI_Type_commit( &mytype );
MPI_Type_commit( &wtype );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );
if ( rank == 0 )
{
MPI_Send( &vector[1][1], 1, mytype, 1, 0, MPI_COMM_WORLD );
}
else
{
for (i = 0; i < 4; i++)
{
printf("\n" );
for (j=0; j < 4; j++)
printf("%i ", wvector[i][j] );
}
MPI_Recv( &nvector[0][0], 1, wtype, 0, 0, MPI_COMM_WORLD, &mystatus );
printf("\n" );
for (i = 0; i < 2; i++)
{
printf("\n" );
for (j=0; j < 2; j++)
printf("%i ", nvector[i][j] );
}
}
MPI_Finalize( );
return 0;
}
pack_array/unpack_array
Here are my packing routines that I used only for my manager. Be sure you understand how to use them before you stick them into your program because there is no kind of checking in them...they assume correct inputs.
The idea behind these routines is that a manager has chunks of work to send to various workers. I order my chunks from 0 to P-1, where P is the total number of chunks such as
So, each chunk will consist of one or more elements. In order for a worker to be able to compute all of the elements in the chunk, it's going to need the ghostcells as discussed in section. So, the manager will send a chunk plus its ghostcells to an individual worker. This is the idea behind pack_array. Once each worker finishes computing the elements in a chunk, it can send the data back to the manager. But the manager only needs the chunk values (and not the ghostcells), so we send just the chunk values. This is illustrated in the picture below.
Okay, now the details.
void pack_array( int *to_array, int *from_array, int chunk_x, int chunk_y, int width, int height, int num );
- to_array is the chunk that you'll be sending to the worker. Therefore, it should be of size ( <width of chunk> + 2) * ( <height of chunk> + 2 )
- from_array is your matrix of image values plus the ghostcells. Therefore, it should be of size ( <width of image> + 2) * ( <height of image> + 2 )
- chunk_x is the width of our chunk
- chunk_y is the height of our chunk
- width is the width of our image
- height is the height of our image
- num is the chunk number we want to send
void unpack_array( int *to_array, int *from_array, int chunk_x, int chunk_y, int width, int height, int num );
- to_array is your matrix of image values plus the ghostcells. Therefore, it should be of size ( <width of image> + 2) * ( <height of image> + 2 )
- from_array is the chunk that you'll be sending to the worker. It doesn't include ghostcells. Therefore, it should be of size ( <width of chunk> ) * ( <height of chunk> )
- chunk_x is the width of our chunk
- chunk_y is the height of our chunk
- width is the width of our image
- height is the height of our image
- num is the chunk number we want to send
If you are wondering about my macro EL_G, here's my explanation. It was easier for me to picture things if I could reference elements within my image matrix (with ghostcells) if I could use the same indices to refer to elements as I would if the image didn't contain ghostcells. So, basically the picture looks something like this:
Create your own routines:
Don't in anyway feel obligated to use either of these methods. If you want to use something else that you feel is more efficient, go for it. Be creative!