< Kommunikation Teil 2 Komplexere Datentypen std::vector std::string Exchange-Objekt MPI::Datatype::Create_contiguous MPI::Datatype::Create_vector MPI::Datatype::Create_indexed Displacement Kommunikation Teil 4 >

Komplexere Datentypen

In den ersten zwei Abschnitten haben wir einfache Datentypen verschickt. Aber genauso ist es möglich Strings und Vektoren der Standard Template Library (STL), Klassen oder eigene Datentypen (MPI) zu verschicken. Beginnen wir mit den Vektoren der STL.


Vektor und String-Klasse

Die Vektorklasse ist im Prinzip ein einfaches Array mit "Zusatzfunktionalität". Das heißt die Klasse verhält sich genauso wie ein Array. Damit kann es für das Senden mit MPI eingesetzt werden. Allerdings ist in der "Zusatzfunktionalität" das dynamische Wachsen des Vektors enthalten, das heißt, er kann eine unterschiedliche Anzahl von Elementen enthalten (Trifft auch auf ein zur Laufzeit Speicher allokierendes Array mit new zu). Die Anzahl der Elemente eines Vektors kann mittels size() abgefragt werden.

	std::vector<double> vec;
	//vec is filled with doubles...
	send(&vec[0], vec.size(), MPI::DOUBLE, dest, tag);
	
Das Senden ist demnach relativ einfach: Man gibt die Adresse des ersten Elementes und deren Anzahl an. Der Rest ist wie gehabt. Beim Empfangen allerdings ergeben sich Schwierigkeiten, denn der Empfänger kennt nicht die Anzahl der zu empfangenden Elemente, da diese erst zur Laufzeit feststehen. Deshalb muss vor dem eigentlichen Empfangen (Füllen des Empfangspuffers) über einen speziellen MPI-Befehl die Eigenschaften der Nachricht abgefragt werden.

Der MPI-Befehl lautet dafür Probe() beziehungsweise nicht-blockierend Iprobe(). Als Parameter müssen der Sender und das tag angegeben werden. Zusätzlich kann ein Status-Objekt übergeben werden, was in unserem Fall erforderlich ist, da in diesem nach dem Aufruf die benötigten Informationen des Senders enthalten sind. Beide Methoden noch einmal im Überblick:

	void MPI::Comm::Probe(int source, int tag) const;
	void MPI::Comm::Probe(int source, int tag, MPI::Status& status) const;
	bool MPI::Comm::Iprobe(int source, int tag) const;
	bool MPI::Comm::IProbe(int source, int tag, MPI::Status& status) const;
	
Bei nicht-blockierenden Iprobe() wird true oder false zurück gegeben, welches anzeigt, ob eine Nachricht empfangen wurde oder nicht. Während also immer wieder geprüft wird, ob ein anderer Prozess etwas geschickt hat, ist es möglich weitere Berechnungen/Aufgaben abzuarbeiten. Ein Beispiel soll dies noch etwas verdeutlichen:

	 1 #include "MyMPI.hpp"
	 2 #include <iostream>
	 3 #include <vector>
	 4 
	 5 using namespace MF;
	 6 using std::cout;
	 7 using std::endl;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13 
	14   if (mpi->rank() == master)  //master
	15   {
	16     std::vector<int> vec;
	17     const int tag = 0;
	18 
	19     for (int dest=1; dest<mpi->size(); ++dest)
	20     {
	21       vec.push_back(dest);
	22       mpi->world().Send(&vec[0], static_cast<int>(vec.size()), MPI::INT, dest, tag);
	23     }
	24   }
	25   else
	26   {
	27     MPI::Status status;
	28     unsigned int counter = 0;
	29 
	30     for(;;)
	31     {
	32       if(mpi->world().Iprobe(MPI::ANY_SOURCE, MPI::ANY_TAG, status))
	33       {
	34         std::vector<int> vec;
	35         const int msglen = status.Get_count(MPI::INT);
	36         vec.resize(msglen);
	37         mpi->world().Recv(&vec[0], msglen, MPI::INT, status.Get_source(), status.Get_tag());
	38 
	39         cout << *MyMPI::instance() << " received: ";
	40         std::vector<int>::const_iterator it = vec.begin();
	41         for(; it!=vec.end(); ++it)
	42           cout << *it << " ";
	43         break;
	44       }
	45       // some things to do...
	46       ++counter;
	47     }
	48     cout << " Counter: " << counter << endl;
	49   }
	50   return 0;
	51 }
	

Die Ausgabe sollte bei fünf Prozessen in etwa folgendes ergeben:

	process 1 of 5 running on host m13f_mobile2 received: 1  Counter: 58171
	process 2 of 5 running on host m13f_mobile2 received: 1 2  Counter: 79660
	process 3 of 5 running on host m13f_mobile2 received: 1 2 3  Counter: 138210
	process 4 of 5 running on host m13f_mobile2 received: 1 2 3 4  Counter: 114643
	

Dabei dient der Zähler als eine Abschätzung über die "genutzte" Rechenzeit, bevor das Empfangen gestartet wurde (Auf einem Parallelrechner im Release-Modus sind alle Counter null!). In Zeile 32 werden statt eines festgelegten Senders und tags die Konstanten ANY_SOURCE und ANY_TAG verwendet. Damit wird jede eingehende Nachricht angenommen. Die Zeile 35 ist die Entscheidende. Hier wird die Anzahl der übertragenen "Top-Level-Elemente" erfragt. Anschließend muss der Vektor auf die entsprechende Größe erweitert werden, da sonst beim "richtigen" Empfangen in andere Speicherbereiche geschrieben wird, die nicht zum eigentlichen Vektor gehören.

In ähnlicher Weise lassen sich Objekte der std::string-Klasse verschicken, was am besten an einem Beispiel zu erkennen ist.

	13   if (mpi->rank() == master)  //master
	14   {
	15     std::string str("a message from the master process");
	16     const int tag = 0;
	17 
	18     for (int dest=1; dest<mpi->size(); ++dest)
	19     {
	20       mpi->world().Send(&str[0], static_cast<int>(str.size()), MPI::CHAR, dest, tag);
	21     }
	22   }
	23   else
	24   {
	25     MPI::Status status;
	26     unsigned int counter = 0;
	27 
	28     for(;;)
	29     {
	30       if(mpi->world().Iprobe(MPI::ANY_SOURCE, MPI::ANY_TAG, status))
	31       {
	32         std::string str;
	33         const int msglen = status.Get_count(MPI::CHAR);
	34         str.resize(msglen);
	35         mpi->world().Recv(&str[0], msglen, MPI::CHAR, status.Get_source(), status.Get_tag());
	36 
	37         cout << *MyMPI::instance() << " received: ";
	38         cout << str << " ";
	39         break;
	40       }
	41       // some things to do...
	42       ++counter;
	43     }
	44     cout << " Counter: " << counter << endl;
	45   }
	

Im Prinzip wird statt des Vektors ein String benutzt und der Datentyp wurde auf MPI::CHAR geändert. Das heißt, das jeder(?) zusammenhängende Datenbereich übertragen werden kann?


Versenden von Objekten

Probieren wir deshalb doch einmal die Übertragung eines Objektes einer einfachen Klasse. Dazu soll als Beispiel die Klasse Exchange dienen, die als Datenmember ein Integer und einen Zeiger auf einen Integer besitzt:

Exchange
	 1 class Exchange
	 2 {
	 3   int stack_;
	 4   int* heap_;
	 5 
	 6 public:
	 7   Exchange(int stack, int heap)
	 8     : stack_(stack)
	 9     , heap_(new int(heap))
	10   {}
	11   int getStack() const {return stack_;}
	12   int getHeap() const {return *heap_;}
	13   ~Exchange()
	14   {
	15     delete heap_;
	16   }
	17 };
	

Diese Klasse verwenden wir im folgenden Beispiel:

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13 
	14   if (mpi->rank() == master)  //master
	15   {
	16     Exchange exchange(13,13);
	17     const int tag = 0;
	18     Transceiver<> transceiver;
	19 
	20     for (int dest=1; dest<mpi->size(); ++dest)
	21     {
	22       transceiver.send(&exchange, sizeof(Exchange), MPI::BYTE, dest, tag);
	23     }
	24   }
	25   else
	26   {
	27     const int from = master;
	28     const int tag = 0;
	29     MPI::Status status;
	30     Transceiver<> transceiver;
	31     Exchange exchange(0,0);
	32 
	33     transceiver.recv(&exchange, sizeof(Exchange), MPI::BYTE, from, tag, status);
	34     cout << " Stack: " << exchange.getStack() << endl;
	35     cout << " Heap:  " << exchange.getHeap() << endl;
	36   }
	37   return 0;
	38 }
	

Der Master verschickt Byte-weise sein Exchange-Objekt an die anderen Prozesse. Diese empfangen das Objekt in ihre vorher angelegten Dummy-Objekte. Die Ausgabe bei zwei Prozessen ergibt folgendes:

	Stack: 13
	Heap: 0
	

Das Objekt wird also nicht vollständig übertragen. Nur die Elemente auf dem Stack werden ordnungesgemäß versendet. Das heißt, die Variable stack_ mit der Zahl 13 und der Inhalt des Zeigers heap_ werden übertragen. Das letztere ist sehr gefährlich, da der Zeiger jedes Prozesses umgebogen wird. Dieser zeigt somit auf eine andere Speicherzelle und beim Verlassen wird diese dann gelöscht. Zusammenfassend können also keine Objekte mit Variablen die auf den Heap Daten haben ohne Weiteres versendet werden. Dazu bedarf es dann einer expliziten Serialisierung.


Abgeleitete Datentypen

MPI unterstützt mit vielen unterschiedlichen Funktionen das Zusammenfassen von einfachen Datentypen zu einem Neuen. Damit wird vor Allem ein Kopieren von Daten in einen extra Puffer vermieden, denn es können auch nicht zusammenhängende Speicherbereiche zu einem Datentyp zusammengefasst werden. Mit dem MPI-2 Standard existieren acht verschiedene Möglichkeiten einen neuen Datentyp zu definieren:

  • MPI::Datatype::Create_contiguous()
  • MPI::Datatype::Create_vector()
  • MPI::Datatype::Create_hvector()
  • MPI::Datatype::Create_indexed()
  • MPI::Datatype::Create_hindexed()
  • MPI::Datatype::Create_struct()
  • MPI::Datatype::Create_darray()
  • MPI::Datatype::Create_subarray()
  • MPI::Datatype::Create_indexed_block()

Der einfachste Datentyp lässt sich mit Create_contiguous() erstellen. Mit diesen werden Datentypen gleichen Typs die hintereinander im Speicher stehen zusammengefasst. Im folgenden Beispiel wird dies am Erstellen eines neuen Datentyps Alphabet bestehend aus 26 Chars und der Null-Terminierung gezeigt.

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13   const unsigned int count = 26 + 1;
	14 
	15   MPI::Datatype Alphabet = MPI::CHAR.Create_contiguous(count);
	16   Alphabet.Commit();
	17 
	18   if (mpi->rank() == master)  //master
	19   {
	20     char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	21 
	22     const int tag = 0;
	23     Transceiver<> transceiver;
	24 
	25     for (int dest=1; dest<mpi->size(); ++dest)
	26     {
	27       transceiver.send(&alphabet, 1, Alphabet, dest, tag);
	28     }
	29   }
	30   else
	31   {
	32     const int from  = master;
	33     const int tag  = 0;
	34     MPI::Status status;
	35     Transceiver<> transceiver;
	36     char toRecv[count];
	37 
	38     transceiver.recv(&toRecv, 1, Alphabet, from, tag, status);
	39 
	40     cout << *mpi->instance() << " received: " << toRecv << endl;
	41   }
	42 
	43   Alphabet.Free();
	44   return 0;
	45 }
	46 
	

Der neue Datentyp Alphabet wird in Zeile 15 abgeleitet vom Datentyp MPI::CHAR erstellt und mittels Commit() MPI als neuen Datentyp bekannt gegeben. Das Char-Array kann jetzt ganz normal versendet und empfangen werden. Wenn der Datentyp nicht mehr benötigt wird, sollte dieser wie in Zeile 43 wieder "abgemeldet" werden.

Angenommen man teilt alle Buchstaben in Vierer-Gruppen ein (Y und Z bilden eine Zweier-Gruppe) und möchte von diesen immer nur die ersten beiden Buchstaben verschicken. Ein Ansatz wäre die benötigten Buchstaben in einen Puffer zu schreiben und diesen zu verschicken, um die Buchstaben beim Empfänger wieder an genau die gleiche Stelle einzufügen. Diese Möglichkeit ist allerdings nicht sehr effektiv. Mit Create_vector() bzw. Create_hvector kann diese Aufgabe eleganter gelöst werden. Beide benötigen drei Parameter: Der erste gibt die Anzahl von Blöcken und der zweite die Anzahl der Datentypen pro Block an. Hier wären dies 7 Blöcke a zwei Buchstaben. Nun fehlt noch der Abstand (stride) zwischen den Blöcken. Dieser wird immer ausgehend vom Anfang des Speicherbereiches berechnet. Da hier die Einteilung in Vierer-Gruppen vorliegt, ist der Abstand vier Elemente (Create_vector()) bzw. vier Byte (Create_hvector).

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13   const unsigned int cntAlpha = 26 + 1;
	14 
	15   const unsigned int cntBlock = 7;
	16   const unsigned int lenBlock = 2;
	17   const unsigned int stride = 4;
	18   MPI::Datatype Alphabet = MPI::CHAR.Create_vector(cntBlock, lenBlock, stride);
	19   Alphabet.Commit();
	20 
	21   if (mpi->rank() == master)  //master
	22   {
	23     char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	24 
	25     const int tag = 0;
	26     Transceiver<> transceiver;
	27 
	28     for (int dest=1; dest<mpi->size(); ++dest)
	29     {
	30       transceiver.send(&alphabet, 1, Alphabet, dest, tag);
	31     }
	32   }
	33   else
	34   {
	35     const int from  = master;
	36     const int tag  = 0;
	37     MPI::Status status;
	38     Transceiver<> transceiver;
	39     char toRecv[cntAlpha];
	40 
	41     transceiver.recv(&toRecv, 1, Alphabet, from, tag, status);
	42 
	43     toRecv[26] = '\0';//explicit termination
	44     cout << *mpi->instance() << " received: " << toRecv << endl;
	45   }
	46 
	47   Alphabet.Free();
	48   return 0;
	49 }
	50 
	

Im vorherigen Beispiel ist die Länge der einzelnen Blöcke immer konstant. Mit den Befehlen Create_index() bzw Create_hindex() kann diese variiert werden. Der erste Parameter gibt wieder die Anzahl der Blöcke an, der zweite ist ein Array mit der jeweiligen Länge pro Block und der dritte ist ebenfalls ein Array mit den Abständen vom Beginn in Anzahl von Elementen bzw. Bytes. Im folgenden Beispiel wird das Alphabet in Einer-, Zweier- bis zu einer Sechser-Gruppe mit jeweils einem Element dazwischen ausgegeben.

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13   const unsigned int cntAlpha = 26 + 1;
	14 
	15   const unsigned int cntBlock = 6;
	16   const int lenBlock[cntBlock] = {1, 2, 3, 4, 5, 6};
	17   const int displacement[cntBlock] = {0, 2, 5, 9, 14, 20};
	18   MPI::Datatype Alphabet = MPI::CHAR.Create_indexed(cntBlock, &lenBlock[0], &displacement[0]);
	19   Alphabet.Commit();
	20 
	21   if (mpi->rank() == master)  //master
	22   {
	23     char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	24 
	25     const int tag = 0;
	26     Transceiver<> transceiver;
	27 
	28     for (int dest=1; dest<mpi->size(); ++dest)
	29     {
	30       transceiver.send(&alphabet, 1, Alphabet, dest, tag);
	31     }
	32   }
	33   else
	34   {
	35     const int from  = master;
	36     const int tag  = 0;
	37     MPI::Status status;
	38     Transceiver<> transceiver;
	39     char toRecv[cntAlpha];
	40 
	41     transceiver.recv(&toRecv, 1, Alphabet, from, tag, status);
	42 
	43     toRecv[26] = '\0';//explicit termination
	44     cout << *mpi->instance() << " received: " << toRecv << endl;
	45   }
	46 
	47   Alphabet.Free();
	48   return 0;
	49 }
	50 
	

Natürlich ist noch eine weitere Steigerung möglich. Denn die Datentypen jedes Blockes können ebenfalls variieren.

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13   const unsigned int cntAlpha = 26 + 1;
	14 
	15   const unsigned int cntBlock = 3;
	16   const int lenBlock[cntBlock] = {3, 3, 3};
	17   const int displ[cntBlock] = {0, 6, 12};
	18   const MPI::Datatype type[cntBlock] = {MPI::CHAR, MPI::CHAR, MPI::CHAR};
	19 
	20   MPI::Datatype Alphabet = MPI::Datatype::Create_struct(cntBlock, &lenBlock[0], &displ[0], &type[0]);
	21   Alphabet.Commit();
	22 
	23   if (mpi->rank() == master)  //master
	24   {
	25     char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	26 
	27     const int tag = 0;
	28     Transceiver<> transceiver;
	29 
	30     for (int dest=1; dest<mpi->size(); ++dest)
	31     {
	32       transceiver.send(&alphabet, 1, Alphabet, dest, tag);
	33     }
	34   }
	35   else
	36   {
	37     const int from  = master;
	38     const int tag  = 0;
	39     MPI::Status status;
	40     Transceiver<> transceiver;
	41     char toRecv[cntAlpha];
	42 
	43     transceiver.recv(&toRecv, 1, Alphabet, from, tag, status);
	44 
	45     toRecv[26] = '\0';//explicit termination
	46     cout << *mpi->instance() << " received: " << toRecv << endl;
	47   }
	48 
	49   Alphabet.Free();
	50   return 0;
	51 }
	52 
	

In den letzten Beispielen ist nur der Datentyp char als Elementtyp verwendet worden. Dieser Typ benötigt genau ein Byte Speicherplatz und hat eine Speicherausrichtung (Alignment) von einem Byte. Ein int belegt im Allgemeinen vier Byte und ist auf vier Byte im Speicher ausgerichtet. Das heißt, ein Integer muss immer an einer durch vier teilbaren Speicheradresse beginnen.
Nehmen wir nun einmal an, wir haben eine Klasse mit drei Datenmembern, nämlich char, int und double. Diese Daten sollen nun als ein neuer Datentyp, erzeugt mit Hilfe der MPI-Funktion MPI::Struct(), verschickt werden. Nun kommt die Speicherausrichtung ins Spiel. Der char-Datenmember beginnt an Speicherstelle null in Bezug auf seine Klasse. Danach folgt der int-Datentyp, der eine Speicherausrichtung von vier Byte hat. Daher setzt der Compiler den Beginn des Integers an die nächste durch vier teilbare Speicheradresse, indem er die übrigen drei Byte mit sogenannten Padding-Bytes auffüllt. Ein double hat eine Speicherausrichtung von acht Byte. Daher kann dieser Datentyp nahtlos an die bisherigen Datentypen angefügt werden, da diese insgesamt acht Byte belegen. Die folgende Grafik verdeutlicht die eben geschilderte Situation:

Für das Erstellen des neuen Datentyps ist die Speicheradresse der einzelnen Grunddatentypen wichtig, beziehungsweise wie groß das Displacement jedes Typs ist. Die Speicheradresse jedes Typs kann mit der MPI-Funktion MPI::Get_Address() berechnet werden. Subtrahiert man jetzt die Adresse vom ersten Datentyp vom zweiten, so erhält man dessen Displacement. Analog wird die Adresse des ersten Datentyps vom dritten subtrahiert. Für das obige Beispiele wird der char-Datenmember ein Displacement von null, der Integer von vier und das Double von acht erhalten. Auf diese Art und Weise wird das Programm Plattform unabhängig gehalten. Das folgende Beispiel zeigt das Erstellen und Versenden von Datenmembern einer Klasse, welche dem Byte-weisen Verschicken vorzuziehen ist.

	 1 #include "MyMPI.hpp"
	 2 #include "P2PComm.hpp"
	 3 #include <iostream>
	 4 
	 5 using std::cout;
	 6 using std::endl;
	 7 using namespace MF;
	 8 
	 9 int main(int argc, char* argv[])
	10 {
	11   const MyMPI* mpi = MyMPI::instance();
	12   const int master = 0;
	13   const int cntDt = 3;
	14 
	15   struct SpecialData
	16   {
	17     char letter_;
	18     unsigned int id_;
	19     double value_;
	20 
	21     SpecialData(char letter='\0', unsigned int id=0, double value=0.0)
	22       : letter_(letter)
	23       , id_(id)
	24       , value_(value)
	25     {}
	26   };
	27 
	28   MPI::Datatype types[] = {MPI::CHAR, MPI::UNSIGNED, MPI::DOUBLE};
	29 
	30   int lenBlock[] = {1,1,1};
	31   SpecialData specialData;
	32   MPI::Aint tmpDisp[] = { MPI::Get_address(&specialData.letter_),
	33                           MPI::Get_address(&specialData.id_),
	34                           MPI::Get_address(&specialData.value_)};
	35 
	36   MPI::Aint displacement[cntDt] = {};
	37   for(unsigned int i=1; i<cntDt; ++i)
	38   {
	39     displacement[i] = tmpDisp[i] - tmpDisp[0];
	40   }
	41 
	42   MPI::Datatype buff_datatype = MPI::Datatype::Create_struct(3,&lenBlock[0],&displacement[0],&types[0]);
	43   buff_datatype.Commit();
	44 
	45   if (mpi->rank() == master)  //master
	46   {
	47     specialData.letter_ = 'a';
	48     specialData.id_ = mpi->rank();
	49     specialData.value_ = 13.0;
	50 
	51     cout << *mpi->instance() << " send: " << specialData.letter_ << " ";
	52     cout << specialData.id_ << " " << specialData.value_ <<  endl;
	53 
	54     const int tag = 0;
	55     Transceiver<> transceiver;
	56 
	57     for (int dest=1; dest<mpi->size(); ++dest)
	58     {
	59       transceiver.send(&specialData, 1, buff_datatype, dest, tag);
	60     }
	61   }
	62   else
	63   {
	64     const int from  = master;
	65     const int tag  = 0;
	66     MPI::Status status;
	67     Transceiver<> transceiver;
	68 
	69     transceiver.recv(&specialData, 1, buff_datatype, from, tag, status);
	70 
	71     cout << *mpi->instance() << " send: " << specialData.letter_ << " ";
	72     cout << specialData.id_ << " " << specialData.value_ <<  endl;
	73   }
	74 
	75   buff_datatype.Free();
	76   return 0;
	77 }
	78 
	

Die letzten drei Funktionen MPI::Datatype::Create_darray() MPI::Datatype::Create_subarray() und MPI::Datatype::Create_indexed_block() werden im Kapitel Parallel I/O behandelt, da diese Funktionen unter anderem dem Speichern eines globalen, auf die einzelnen Prozesse verteilten Arrays in eine Datei dienen.



Version 1.2