Die Boost C++ Bibliotheken

Kommunikatoren

Alle bisherigen Beispiele haben einen einzigen Kommunikator verwendet, in dem alle Prozesse vereint waren. Es ist jedoch möglich, mehrere Kommunikatoren zu erstellen, um eine bestimmte Anzahl von Prozessen zusammenzufassen. Das ist vor allem im Zusammenhang mit kollektiven Operationen nützlich. Denn die entsprechenden Funktionen müssen nicht für alle Prozesse aufgerufen werden. Sie müssen lediglich für alle Prozesse in dem Kommunikator aufgerufen werden, der als erster Parameter an die entsprechenden Funktionen übergeben wird.

Beispiel 47.15. Mit mehreren Kommunikatoren arbeiten
#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;
  boost::mpi::communicator local = world.split(world.rank() < 2 ? 99 : 100);
  std::string s;
  if (world.rank() == 0)
    s = "Hello, world!";
  boost::mpi::broadcast(local, s, 0);
  std::cout << world.rank() << ": " << s << '\n';
}

Im Beispiel 47.15 kommt die bereits bekannte Funktion boost::mpi::broadcast() zum Einsatz. Die Funktion sendet den String Hello, world! vom Prozess mit Rang 0 an alle Prozesse, die im Kommunikator local vereint sind. Der Prozess mit Rang 0 muss dabei diesem Kommunikator angehören.

Der Kommunikator local wird über den Aufruf von split() erstellt. Es handelt sich dabei um eine Methode, die für den globalen Kommunikator world aufgerufen wird. split() erwartet eine Zahl vom Typ int, um Prozesse in Kommunikatoren einzuordnen. Prozesse, die die gleiche Zahl übergeben, landen im gleichen Kommunikator. Welche Zahl genau an split() übergeben wird, spielt keinen Rolle. Es kommt lediglich darauf an, welche Prozesse die gleichen Zahlen übergeben.

Im Beispiel 47.15 übergeben die ersten beiden Prozesse mit den Rängen 0 und 1 die Zahl 99 an split(). Wird das Programm mit mehr als zwei Prozessen gestartet, übergeben diese die Zahl 100. Das bedeutet, dass es einen lokalen Kommunikator gibt, in dem sich die ersten beiden Prozesse befinden, und einen anderen lokalen Kommunikator, in dem sich alle anderen Prozesse befinden. Jeder Prozess gehört dem Kommunikator an, der von split() zurückgegeben wird. Inwiefern sich in diesem Kommunikator andere Prozesse befinden, hängt davon ab, ob andere Prozesse die gleiche Zahl an split() übergeben.

Beachten Sie, dass sich Ränge immer auf einen Kommunikator beziehen. Dabei beginnt die Zählweise immer bei 0. So hat im Beispiel 47.15 der Prozess, der bezogen auf den globalen Kommunikator den Rang 0 hat, bezogen auf den neuen lokalen Kommunikator ebenfalls den Rang 0. Der Prozess, der bezogen auf den globalen Kommunikator den Rang 2 hat, hat bezogen auf den neuen lokalen Kommunikator auch den Rang 0. Es gibt zwei lokale Kommunikatoren, nachdem zwei Prozesse die Zahl 99 und alle andere Prozesse die Zahl 100 an split() übergeben.

Wenn Sie Beispiel 47.15 mit zwei oder mehr Prozessen ausführen, wird zweimal Hello, world! ausgegeben – und zwar von den Prozessen mit den Rängen 0 und 1 bezogen auf den globalen Kommunikator. Da nur für den Prozess mit Rang 0 die Variable s auf Hello, world! gesetzt wird, wird dieser String über den lokalen Kommunikator nur an die Prozesse gesendet, die sich mit dem Prozess mit Rang 0 in der gleichen Gruppe befinden. Dies ist ausschließlich der Prozess mit Rang 1, da nur dieser die gleiche Zahl an split() übergibt wie der Prozess mit Rang 0.

Beispiel 47.16. Prozesse mit group gruppieren
#include <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <boost/range/irange.hpp>
#include <boost/optional.hpp>
#include <string>
#include <iostream>

int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  boost::mpi::group local = world.group();
  boost::integer_range<int> r = boost::irange(0, 1);
  boost::mpi::group subgroup = local.exclude(r.begin(), r.end());
  boost::mpi::communicator others{world, subgroup};
  std::string s;
  boost::optional<int> rank = subgroup.rank();
  if (rank)
  {
    if (rank == 0)
      s = "Hello, world!";
    boost::mpi::broadcast(others, s, 0);
  }
  std::cout << world.rank() << ": " << s << '\n';
}

MPI ermöglicht, Prozessgruppen explizit zusammenzustellen. Dazu wird die Klasse boost::mpi::group angeboten. Wenn Sie die Methode group() für einen Kommunikator aufrufen, erhalten Sie alle Prozesse des Kommunikators in einem Objekt vom Typ boost::mpi::group. Sie können dieses Objekt nicht zur Kommunikation verwenden. Die Klasse boost::mpi::group dient ausschließlich dazu, eine Gruppierung zu verändern, um einen neuen Kommunikator zu erstellen.

boost::mpi::group bietet Methoden wie include() und exclude() an. Sie übergeben diesen Methoden Iteratoren, um Prozesse in der Gruppe ein- oder auszuschließen. include() und exclude() geben daraufhin eine neue Gruppe vom Typ boost::mpi::group zurück.

Im Beispiel 47.16 werden exclude() zwei Iteratoren übergeben, die auf ein Objekt vom Typ boost::integer_range zeigen. Dieses Objekt repräsentiert einen Wertebereich bestehend aus Ganzzahlen. Das Objekt wird mit Hilfe der Funktion boost::irange() erstellt, die eine Unter- und Obergrenze für den Wertebereich erwartet. Wichtig ist, dass der zweite Parameter die Zahl ist, die gerade nicht mehr zum Wertebereich zählt. Das bedeutet, dass im Wertebereich der Variablen r ausschließlich die Zahl 0 enthalten ist.

Der Aufruf von exclude() führt dazu, dass eine neue Gruppe subgroup entsteht, die alle Prozesse außer den mit Rang 0 enthält. Diese Gruppe wird verwendet, um einen neuen Kommunikator others zu erstellen. Dazu muss sowohl der globale Kommunikator world als auch die Gruppe subgroup an den Konstruktor von boost::mpi::communicator übergeben werden.

Beachten Sie, dass mit others ein Kommunikator erstellt wurde, der für den Prozess mit Rang 0 leer ist. Da dieser Prozess dem Kommunikator nicht angehört, die Variable others im Prozess mit Rang 0 aber natürlich trotzdem existiert, müssen Sie darauf achten, dass Sie den Kommunikator in diesem Prozess nicht verwenden. Im Beispiel 47.16 geschieht dies über den Aufruf von rank(). Diese Methode gibt für Prozesse, die der Gruppe nicht angehören, ein leeres Objekt vom Typ boost::optional zurück. Alle anderen Prozesse erhalten ihren Rang bezogen auf die Gruppe.

Wenn rank() einen Rang und kein leeres Objekt vom Typ boost::optional zurückgibt, wird boost::mpi::broadcast() aufgerufen. Der Prozess mit Rang 0 sendet den String Hello, world! an alle Prozesse im Kommunikator others. Beachten Sie, dass sich der Rang hierbei auf den ersten Prozess in diesem Kommunikator bezieht. Dies ist der Prozess, der bezogen auf den globalen Kommunikator world den Rang 1 hat.

Wenn Sie Beispiel 47.16 mit mehr als zwei Prozessen ausführen, wird für alle Prozesse mit einem Rang größer als 0 Hello, world! ausgegeben.