The Boost C++ Libraries

Simple Data Exchange

Boost.MPI is a C++ interface to the MPI standard. The library uses the namespace boost::mpi. It is sufficient to include the header file boost/mpi.hpp to get access to all classes and functions.

Example 47.1. MPI environment and communicator
#include <boost/mpi.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  std::cout << world.rank() << ", " << world.size() << '\n';
}

Example 47.1 is a simple MPI program. It uses two classes that you will find in all of the examples that follow. boost::mpi::environment initializes MPI. The constructor calls the function MPI_Init() from the MPI standard. The destructor calls MPI_Finalize(). boost::mpi::communicator is used to create a communicator. The communicator is one of the central concepts of MPI and supports data exchange between processes.

boost::mpi::environment is a very simple class that provides only a few member functions. You can call initialized() to check whether MPI has been initialized successfully. The member function returns a value of type bool. processor_name() returns the name of the current process as a std::string. And abort() stops an MPI program, not just the current process. You pass an int value to abort(). This value will be passed to the MPI runtime environment as the return value of the MPI program. For most MPI programs you won’t need these member functions. You usually instantiate boost::mpi::environment at the beginning of a program and then don’t use the object afterwards – as in Example 47.1 and the following examples in this chapter.

boost::mpi::communicator is more interesting. This class is a communicator that links the processes that are part of an MPI program. Every process has a rank, which is an integer – all processes are enumerated. A process can discover its rank by calling rank() on the communicator. If a process wants to know how many processes there are, it calls size().

To run Example 47.1, you have to use a helper program provided by the MPI implementation you are using. With MPICH, the helper program is called mpiexec. You can run Example 47.1 using this helper with the following command:

mpiexec -n 4 sample.exe

mpiexec expects the name of an MPI program and an option that tells it how many processes to launch. The option -n 4 tells mpiexec to launch four processes. Thus the MPI program is started four times. However, the four processes aren’t independent. They are linked through the MPI runtime environment, and they all belong to the same communicator, which gave each process a rank. If you run Example 47.1 with four processes, rank() returns a number from 0 to 3 and size() 4.

Please note that the output can be mixed up. After all, four processes are writing to the standard output stream at the same time. For example, it’s unknown whether the process with rank 0, or any other rank, is the first one to write to the standard output stream. It’s also possible that one process will interrupt another one while writing to the standard output stream. The interrupted process might not be able to complete writing its rank and the size of the communicator before another process writes to the standard output stream, breaking up the output.

Example 47.2. Blocking functions to send and receive data
#include <boost/mpi.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    world.recv(1, 16, i);
    std::cout << i << '\n';
  }
  else if (world.rank() == 1)
  {
    world.send(0, 16, 99);
  }
}

boost::mpi::communicator provides two simple member functions, send() and recv(), to exchange data between two processes. They are blocking functions that only return when data has been sent or received. This is especially important for recv(). If recv() is called without another process sending it data, the call blocks and the process will stall in the call.

In Example 47.2, the process with rank 0 receives data with recv(). The process with rank 1 sends data with send(). If you start the program with more than two processes, the other processes exit without doing anything.

You pass three parameters to send(): The first parameter is the rank of the process to which data should be sent. The second parameter is a tag to identify data. The third parameter is the data.

The tag is always an integer. In Example 47.2 the tag is 16. The tag makes it possible to identify a call to send(). You’ll see that the tag is used with recv().

The third parameter passed to send() is 99. This number is sent from the process with rank 1 to the process with rank 0. Boost.MPI supports all primitive types. An int value like 99 can be sent directly.

recv() expects similar parameters. The first parameter is the rank of the process from which data should be received. The second parameter is the tag that links the call to recv() with the call to send(). The third parameter is the variable to store the received data in.

If you run Example 47.2 with at least two processes, 99 is displayed.

Example 47.3. Receiving data from any process
#include <boost/mpi.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    world.recv(boost::mpi::any_source, 16, i);
    std::cout << i << '\n';
  }
  else
  {
    world.send(0, 16, world.rank());
  }
}

Example 47.3 is based on Example 47.2. Instead of sending the number 99, it sends the rank of the process that calls send(). This could be any process with a rank greater than 0.

The call to recv() for the process with rank 0 has changed, too. boost::mpi::any_source is the first parameter. This means the call to recv() will accept data from any process that sends data with the tag 16.

If you start Example 47.3 with two processes, 1 will be displayed. After all, there is only one process that can call send() – the process with rank 1. If you start the program with more than two processes, it’s unknown which number will be displayed. In this case, multiple processes will call send() and try to send their rank. Which process is first and, therefore, which rank is displayed, is random.

Example 47.4. Detecting the sender with boost::mpi::status
#include <boost/mpi.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    boost::mpi::status s = world.recv(boost::mpi::any_source, 16, i);
    std::cout << s.source() << ": " << i << '\n';
  }
  else
  {
    world.send(0, 16, 99);
  }
}

recv() has a return value of type boost::mpi::status. This class provides a member function source(), which returns the rank of the process from which data was received. Example 47.4 tells you from which process the number 99 was received.

So far, all examples have used send() and recv() to transmit an int value. In Example 47.5 a string is transmitted.

Example 47.5. Transmitting an array with send() and recv()
#include <boost/mpi.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    char buffer[14];
    world.recv(boost::mpi::any_source, 16, buffer, 13);
    buffer[13] = '\0';
    std::cout << buffer << '\n';
  }
  else
  {
    const char *c = "Hello, world!";
    world.send(0, 16, c, 13);
  }
}

send() and recv() can transmit arrays as well as single values. Example 47.5 transmits a string in a char array. Because send() and recv() support primitive types like char, the char array can be transmitted without any problems.

send() takes a pointer to the string as its third parameter and the size of the string as its fourth parameter. The third parameter passed to recv() is a pointer to an array to store the received data. The fourth parameter tells recv() how many chars should be received and stored in buffer. Example 47.5 writes Hello, world! to the standard output stream.

Example 47.6. Transmitting a string with send() and recv()
#include <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <string>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    std::string s;
    world.recv(boost::mpi::any_source, 16, s);
    std::cout << s << '\n';
  }
  else
  {
    std::string s = "Hello, world!";
    world.send(0, 16, s);
  }
}

Even though Boost.MPI supports only primitive types, that doesn’t mean it’s impossible to transmit objects of non-primitive types. Boost.MPI works together with Boost.Serialization. Objects that can be serialized according to the rules of Boost.Serialization can be transmitted with Boost.MPI.

Example 47.6 transmits Hello, world! This time the value transmitted is not a char array but a std::string. Boost.Serialization provides the header file boost/serialization/string.hpp, which only needs to be included to make std::string serializable.

If you want to transmit objects of user-defined types, see Chapter 64.