Skip to content

MPI_SEND - send structure as vector

An answer to this question on Stack Overflow.

Question

Considering this structure:

struct Book {
 int id;
 string title;
};

And this vector:

vector<Book> books;

How can I use MPI_Send in order to send the elements of vector books?

I have tried to find a way to do this the entire day, but no results.

Answer

Method 1

One way to do this is to set the title to a constant length. You can then build an MPI data type around your struct, like so:

#include "mpi.h"
#include <iostream>
#include <string>
#include <vector>
const int MAX_TITLE_LENGTH = 256;
struct Book {
  int id;
  char title[MAX_TITLE_LENGTH];
};
int main(int argc, char *argv[]){
  MPI_Init(&argc, &argv);
  std::vector<Book> books(343);
  MPI_Datatype BookType;
  MPI_Datatype type[2] = { MPI_INTEGER, MPI_CHAR };
  int blocklen[2] = { 1, MAX_TITLE_LENGTH };
  MPI_Aint disp[2];
  disp[0] = 0;
  disp[1] = sizeof(int);
  MPI_Type_create_struct(2, blocklen, disp, type, &BookType);
  MPI_Type_commit(&BookType);
  int myrank;
  MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
  if (myrank == 0) {
    books[3].id = 4;
    MPI_Send(books.data(), 343, BookType, 1, 123, MPI_COMM_WORLD);
  } else if (myrank == 1) {
    MPI_Status status;
    MPI_Recv(books.data(), 343, BookType, 0, 123, MPI_COMM_WORLD, &status);
    std::cout<<books[3].id<<std::endl;
  }
  MPI_Finalize();
  return 0;
}

Method 2

MPI is best used for quickly exchange numbers across grids of known size. But it can also work as a handy communication layer. To do so, we can use the Cereal library to serialize arbitrary C++ objects and then send the serialized representations using MPI, as follows. This is slower than using MPI as designed because there are more intermediate copies, but provides the flexibility of using the full flexibility of C++.

#include "mpi.h"
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
#include <cereal/archives/binary.hpp>
#include <sstream>
#include <string>
struct Book {
  int id;
  std::string title;
  template <class Archive>
  void serialize( Archive & ar ) { ar(id,title); }
};
template<class T>
int MPI_Send(const T &data, int dest, int tag, MPI_Comm comm){
  std::stringstream ss;
  { //Needed for RAII in Cereal
    cereal::BinaryOutputArchive archive( ss );
    archive( data );
  }
  const auto serialized = ss.str();
  return MPI_Send(serialized.data(), serialized.size(), MPI_CHAR, dest, tag, MPI_COMM_WORLD);
}
template<class T>
int MPI_Recv(T &data, int source, int tag, MPI_Comm comm, MPI_Status *status){
  //Get number of bytes in incoming message
  MPI_Probe(source, tag, MPI_COMM_WORLD, status);
  int num_incoming;
  MPI_Get_count(status, MPI_CHAR, &num_incoming);
  //Allocate a buffer of appropriate size
  std::vector<char> incoming(num_incoming);
  //Receive the data
  auto ret = MPI_Recv(incoming.data(), num_incoming, MPI_CHAR, source, tag, MPI_COMM_WORLD, status);
  std::stringstream ss;
  ss.write(incoming.data(), num_incoming);
  //Unpack the data
  {
    cereal::BinaryInputArchive archive(ss);
    archive(data);
  }
  return ret;
}
int main(int argc, char **argv){
  MPI_Init(&argc, &argv);
  std::vector<Book> books(343);
  int myrank;
  MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
  if (myrank == 0) {
    books[3].id    = 4;
    books[3].title = "Hello, world!";
    MPI_Send(books, 1, 123, MPI_COMM_WORLD);
  } else if (myrank == 1){
    MPI_Status status;
    MPI_Recv(books, 0, 123, MPI_COMM_WORLD, &status);
    std::cout<<books[3].id<<" "<<books[3].title<<std::endl;
  }
  MPI_Finalize();
  return 0;
}