< Kommunikation Teil 1 | nicht blockierende Kommunikation MPI::Isend | Kommunikation Teil 3 > |
Nicht-blockierende Punkt-zu-Punkt-KommunikationDie nicht-blockierende Punkt-zu-Punkt-Kommunikation unterscheidet sich in Bezug auf die blockierende, dass nach dem Aufruf einer Funktion das Programm gleich fortgesetzt wird. Die Kommunikation also im Hintergrund statt findet. Währendessen hat man "Zeit" für andere Dinge. Allerdings dürfen unter keinen Umständen Variablen, die an der Kommunikation beteiligt sind, geändert werden. Erst nach einer Überprüfung auf erfolgreiches Senden oder Empfangen dürfen diese wieder verändert werden. Die bisherigen Funktionen werden mit einem I im Funktionsnamen erweitert und haben als Rückgabetyp ein Request-Objekt. Die Parameter bleiben gleich. MPI::Request = MPI::Comm.Isend(const void* data, int cnt, const MPI::Datatype& type, int dest, int tag) MPI::Request = MPI::Comm.Ibsend(const void* data, int cnt, const MPI::Datatype& type, int dest, int tag) MPI::Request = MPI::Comm.Issend(const void* data, int cnt, const MPI::Datatype& type, int dest, int tag) MPI::Request = MPI::Comm.Irsend(const void* data, int cnt, const MPI::Datatype& type, int dest, int tag) MPI::Request = MPI::Comm.Irecv(const void* data, int cnt, const MPI::Datatype& type, int dest, int tag) Das Request-Objekt selbst kann auf verschiedene Weise überprüft werden. Zum einen kann man Testen, ob eine Nachricht versendet oder empfangen wurde, was entweder true oder false als Ergebnis hat. Oder man blockiert an einer Stelle im Programm und wartet auf den Erfolg des Sendens oder Empfanges. Zusätzlich gibt es noch verschiedene Modi, bei denen alle Prozesse, einige Prozesse oder einer von allen Prozessen abfragen kann. Für das Empfangen existiert ein weiterer Befehl Probe() der im Beispiel 3.1 angewendet wird. Zum Einstieg ersetzt ein nicht-blockierendes Isend() das normale Send() wie es im Beispiel MPI::Send an mehrere Prozesse angewendet wurde. 1 #define _MPI_CPP_BINDINGS 2 #include <mpi.h> 3 #include <iostream> 4 #include <vector> 5 6 using std::cout; 7 using std::endl; 8 9 int main(int argc, char* argv[]) 10 { 11 MPI::Init(argc, argv); 12 13 const int size = MPI::COMM_WORLD.Get_size(); 14 if (size < 2) return MPI::ERR_SIZE; //check for min 2 processes 15 16 const int rank = MPI::COMM_WORLD.Get_rank(); 17 const int master = 0; 18 19 if (rank == master) //the master 20 { 21 double toSend = 13.13; 22 const int cnt = 1; 23 const int tag = 0; 24 25 std::vector<MPI::Request> request; 26 request.resize(size-1); 27 28 for (int dest=1; dest<size; ++dest) 29 { 30 cout << "process " << rank << " sends "<< toSend << " to process " << dest << endl; 31 request[dest-1] = MPI::COMM_WORLD.Isend(&toSend, cnt, MPI::DOUBLE, dest, tag); 32 } 33 34 MPI::Request::Waitall(size-1, &request[0]); 35 36 } 37 else 38 { 39 double toRecv; 40 const int cnt = 1; 41 const int from = master; 42 const int tag = 0; 43 44 MPI::COMM_WORLD.Recv(&toRecv, cnt, MPI::DOUBLE, from, tag); 45 cout << "process " << rank << " received "<< toRecv << " from process " << from << endl; 46 } 47 MPI::Finalize(); 48 } Zusätzlich ist ein Request-Objekt für jede Verbindung notwendig, die in einen Vektor gespeichert werden. Diese Objekte werden nach dem Versenden auf erfolgreiches Versenden abgefragt, was im Prinzip einem normalen Send() entspricht. Nur, dass sofort alle Nachrichten fast gleichzeitig vom Master versendet werden und danach geprüft wird, ob alles ordungsgemäß versendet wurde. Im Rahmen des Tutorials habe ich eine Klasse geschrieben, die es ermöglicht, nicht-blockierende Kommunikations-Modi auszuprobieren. ITransceiver funktioniert genauso wie die Klasse Transceiver. 1 template <typename Type, typename T=ISend<Type>, typename R=IRecv<Type> > 2 class ITransceiver 3 { 4 typedef Type value_type; 5 typedef Type const* const_pointer; 6 typedef typename std::vector<value_type> VectorOfvalue_type; 7 8 MPI::Request sendRequest_; 9 MPI::Request recvRequest_; 10 11 VectorOfvalue_type* sendData_; 12 VectorOfvalue_type* recvData_; 13 14 public: 15 16 ITransceiver() 17 : sendData_(NULL), recvData_(NULL) 18 { 19 } 20 21 ~ITransceiver() 22 { 23 free(); 24 } 25 26 void send(const Type* data, int cnt, const MPI::Datatype& type, int dest, int tag) 27 { 28 if(sendData_) 29 { 30 while(!testSendRequest()); 31 sendData_->assign(&data[0], &data[cnt]); 32 } 33 else 34 sendData_ = new VectorOfvalue_type(&data[0], &data[cnt]); 35 36 sendRequest_ = T()( sendData_, type, dest, tag); 37 } 38 39 void recv(int cnt, const MPI::Datatype& type, int from, int tag, MPI::Status& status) 40 { 41 if(recvData_) 42 { 43 while(!testRecvRequest()); 44 recvData_->resize(cnt); 45 } 46 else 47 recvData_ = new VectorOfvalue_type(cnt, value_type()); 48 49 recvRequest_ = R()( recvData_, type, from, tag); 50 } 51 52 const_pointer getData() 53 { 54 while(!testRecvRequest()); 55 56 return &(*recvData_)[0]; 57 } 58 59 void free() 60 { 61 while (! (testSendRequest() && testRecvRequest()) ); 62 63 if (sendData_) delete sendData_; 64 if (recvData_) delete recvData_; 65 } 66 67 private: 68 bool testSendRequest() {return sendRequest_.Test(); } 69 bool testRecvRequest() {return recvRequest_.Test(); } 70 }; Als Template-Parameter wird der zu sendende beziehungsweise zu empfangende Typ benötigt, da intern ein Sendepuffer beziehungsweise ein Empfangspuffer benötigt wird. |