Introduction to MPI

Aveuh
75.6K views

Custom types - exercise

In the previous lesson, we have seen how to create very basic contiguous datatypes. This way of creating datatypes does not help us when we want to create datatypes that mix different basice types. For instance, in the previous lesson's example, we have seen a custom structure used to store the data :

struct CustomData {
int n_values;
double dbl_values[10];
};


To represent this using the type/displacement formalism, our datatype would look something like :

[(int, 0), (double, 4), (double, 12), (double, 20), (double, 28), (double, 36), (double, 44), (double, 52), (double, 60), (double, 68), (double, 76)]


To simplify everything, we can convert everyone of these couples as a triplet : (type, start, number of elements). Thus our list simplifies to :

[(int, 0, 1), (double, 4, 10)]


MPI provides us with a special function to actually convert such a list in a datatype :

int MPI_Type_create_struct(int count, const int block_length[], const MPI_Aint displacement[], const MPI_Datatype types[], MPI_Datatype *new_type);


Let's see these arguments one by one. count is the number of elements in your list, in our case we have two entries, so count will be 2.

block_length is an array of integers, indicating, for entry i, the number of contiguous elements of that type. That will be the third value of our triplet : 1 in the int case, 10 in the double case.

displacement is an array of MPI_Aint. We have never seen this type, but let's forget immediatly about it, and consider this to be an array of integers. MPI_Aint stands for Address integer. These are just a specific MPI type for integers. In our case, that's the second element of each triplet.

types is an array of MPI_Datatypes. This should be pretty obvious by now : it's an array of all the different sub-types we are going to use in the custom type.

Finally, we store the resulting datatype in new_type.

Knowing this, you are ready to optimise the example code we gave in last lesson, especially, removing all the copies in memory and transferring all the data using only one gather communication.

Displacements

Now there is a catch wit the displacement. Computing manually the offsets can be tricky. Although it tends to be less and less the case, some types have sizes that can vary on a system/OS basis, so hardcoding the values might lead to trouble. One way of doing things in a cleaner way is to use the offsetof macro from the standard library (You will have to include stddef.h in C or cstddef in C++). offsetof takes two parameters : a struct and the name of one attribute of the struct. It returns a size_t (implicitly castable in a MPI_Aint) corresponding to the displacement of this attribute.

For example if we had the following structure :

struct MyStruct {
int a;
double b;
char c;
float d;
};


Then, we could define out displacement table as :

MPI_Aint displacements[4] = {offsetof(MyStruct, a), offsetof(MyStruct, b), offsetof(MyStruct, c), offsetof(MyStruct, d)};


Exercise

It's your turn to optimise the program we have made in the previous lesson. Use MPI_Type_create_struct to define a derived datatype and commit it so the data can be gathered on process 0.

Custom struct datatype
This playground was created on Tech.io, our hands-on, knowledge-sharing platform for developers.
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <mpi.h>
#include <cstddef>
constexpr int DOUBLE_MAX = 10;
struct CustomData {
int n_values;
double values[DOUBLE_MAX];
};
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int rank, size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
constexpr int n_structure_per_process = 5; // M = 5
// Random generator init
srand(rank * 10);
// Creating the dataset
CustomData data[n_structure_per_process];
// Generating the data
for (int i=0; i < n_structure_per_process; ++i) {
// Terrible way of generating random numbers, don't reproduce this at home
data[i].n_values = rand() % DOUBLE_MAX + 1;
for (int j=0; j < DOUBLE_MAX; ++j)
data[i].values[j] = (j < data[i].n_values ? (double)rand() / (double)RAND_MAX : 0.0);
}
// 1- Here, create all the properties to call MPI_Type_create_struct
MPI_Aint displacements[2] = {};
int block_lengths[2] = {};
MPI_Datatype types[2] = {};
MPI_Datatype custom_dt;
// 2- Create the type, and commit it
// Gathering the data
CustomData *gathered_data = nullptr;
if (rank == 0)
gathered_data = new CustomData[n_structure_per_process * size];
MPI_Gather(data, n_structure_per_process, custom_dt,
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Join the CodinGame community on Discord to chat about puzzle contributions, challenges, streams, blog articles - all that good stuff!
Online Participants