(Vermutlich) ganz einfaches MPI Problem


  • Mod

    Hallo,
    bitte nicht von dem langen Post abschrecken lassen, es ist wahrscheinlich ein sehr einfaches Problem.

    Ich fange gerade an, mich ein bisschen mit MPI zu beschäftigen. Dabei bin ich auf ein mir unerklärliches Problem beim Austausch komplexer Datentypen gestoßen. Angenommen ich habe folgendes Programm, das eine einfache Datenstruktur aus zwei double-Arrays und einem int definiert, und diese zwischen zwei Prozessen austauscht:

    #include <mpi.h>
    #include <vector>
    #include <iostream>
    using namespace std;
    
    struct bead        // Eine einfache Datenstruktur
    {
      double foo[3];
      double bar[3];
      int number;
    };
    
    ostream &operator<<(ostream & lhs, bead& rhs)  // Für die Ausgabe
    {
      lhs<<rhs.foo[0]<<'\t'<<rhs.foo[1]<<'\t'<<rhs.foo[2]<<'\t'<<rhs.bar[0]<<'\t'<<rhs.bar[1]<<'\t'<<rhs.bar[2]<<'\t'<<rhs.number<<endl;
      return lhs;
    }
    
    int main(int argc, char *argv[]){
    
      // Initialisiere MPI:
      int err = MPI_Init(&argc, &argv);
    
      if (err != MPI_SUCCESS)
        {
          cout << "Error starting MPI program. Terminating.\n";
          MPI_Abort(MPI_COMM_WORLD, err);
        }
    
      int rank_num, rank_size;
    
      MPI_Comm_rank(MPI_COMM_WORLD, &rank_num);
      MPI_Comm_size(MPI_COMM_WORLD, &rank_size);
      cout << "rank_num " << rank_num << " of " << rank_size << " started\n";
    
       // Erstelle den MPI Datentyp. Dazu:
      MPI_Aint  displ_bead[3],    addr_bead[3];
      {  
        bead b;   // Erzeuge ein Objekt der Klasse
    
        MPI_Get_address(&b.foo     , &addr_bead[0]);  // finde die Adressen
        MPI_Get_address(&b.bar     , &addr_bead[1]);
        MPI_Get_address(&b.number  , &addr_bead[2]);
    
        displ_bead[0] = 0;                               // Rechne die Abstände aus
        displ_bead[1] = addr_bead[1] - addr_bead[0]; 
        displ_bead[2] = addr_bead[2] - addr_bead[0];
      }
      int          blockl_bead[3]={3       , 3         ,1       }; // 3 Datenfelder, mit Längen 3, 3 und 1
      MPI_Datatype type_bead[3]={MPI_DOUBLE, MPI_DOUBLE, MPI_INT}; // und den Typen double, double und int
      MPI_Datatype(P_BEAD);
      MPI_Type_struct(3, blockl_bead   , displ_bead   , type_bead   , &P_BEAD);
      MPI_Type_commit(&P_BEAD);
    
      vector<bead> h; // Erstelle ein Array von beads
      h.resize(10);
    
      if (rank_num==0)  // Fülle es in einem Prozess mit ein paar Zahlen
        {
          for (int i =0; i<10;++i) 
    	{
    	  h[i].number=i;
    	  h[i].foo[0]=i;
    	  h[i].foo[1]=2*i;
    	  h[i].foo[2]=3*i;
    	  h[i].bar[0]=4*i;
    	  h[i].bar[1]=5*i;
    	  h[i].bar[2]=6*i;
          }
        }
    
      cout<<"Rank "<<rank_num<<" before:"<<endl;
      for (int i =0; i<10;++i) cout<<"Rank "<<rank_num<<": "<<h[i];   // Ausgabe vor dem Versenden
    
      MPI_Status status;
      if (rank_num==0)
        {
          MPI_Ssend(&h[0], 10, P_BEAD, 1, 1, MPI_COMM_WORLD);  // Schicke von einem Prozess...
        }
      else if (rank_num==1)
        {
          MPI_Recv(& h[0], 10, P_BEAD, 0, 1 , MPI_COMM_WORLD, &status); // ...an den anderen
        }
    
      cout<<"Rank "<<rank_num<<" after:"<<endl;
    
      for (int i =0; i<10;++i) cout<<"Rank "<<rank_num<<": "<<h[i];  // Ausgabe nachher
    
      MPI_Finalize();
    }
    

    Das funktioniert wunderbar, die Ausgabe sieht so aus:

    rank_num 1 of 2 started
    Rank 1 before:
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    rank_num 0 of 2 started
    Rank 0 before:
    Rank 0: 0	0	0	0	0	0	0
    Rank 0: 1	2	3	4	5	6	1
    Rank 0: 2	4	6	8	10	12	2
    Rank 0: 3	6	9	12	15	18	3
    Rank 0: 4	8	12	16	20	24	4
    Rank 0: 5	10	15	20	25	30	5
    Rank 0: 6	12	18	24	30	36	6
    Rank 0: 7	14	21	28	35	42	7
    Rank 0: 8	16	24	32	40	48	8
    Rank 0: 9	18	27	36	45	54	9
    Rank 1 after:
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 1	2	3	4	5	6	1
    Rank 1: 2	4	6	8	10	12	2
    Rank 1: 3	6	9	12	15	18	3
    Rank 1: 4	8	12	16	20	24	4
    Rank 1: 5	10	15	20	25	30	5
    Rank 1: 6	12	18	24	30	36	6
    Rank 1: 7	14	21	28	35	42	7
    Rank 1: 8	16	24	32	40	48	8
    Rank 1: 9	18	27	36	45	54	9
    Rank 0 after:
    Rank 0: 0	0	0	0	0	0	0
    Rank 0: 1	2	3	4	5	6	1
    Rank 0: 2	4	6	8	10	12	2
    Rank 0: 3	6	9	12	15	18	3
    Rank 0: 4	8	12	16	20	24	4
    Rank 0: 5	10	15	20	25	30	5
    Rank 0: 6	12	18	24	30	36	6
    Rank 0: 7	14	21	28	35	42	7
    Rank 0: 8	16	24	32	40	48	8
    Rank 0: 9	18	27	36	45	54	9
    

    Wenn ich jetzt aber eine kleine Änderung an meinem Struct vornehme indem ich die Reihenfolge der Datenfelder ändere:

    struct bead
    {
      int number;
      double foo[3];
      double bar[3];
    };
    

    Den Rest des Programms lasse ich unverändert, auch die Art und Weise wie die Adressen berechnet werden.

    Dann werden die Daten nur unvollständig übertragen:

    rank_num 0 of 2 started
    Rank 0 before:
    Rank 0: 0	0	0	0	0	0	0
    Rank 0: 1	2	3	4	5	6	1
    Rank 0: 2	4	6	8	10	12	2
    Rank 0: 3	6	9	12	15	18	3
    Rank 0: 4	8	12	16	20	24	4
    Rank 0: 5	10	15	20	25	30	5
    Rank 0: 6	12	18	24	30	36	6
    Rank 0: 7	14	21	28	35	42	7
    Rank 0: 8	16	24	32	40	48	8
    Rank 0: 9	18	27	36	45	54	9
    Rank 0 after:
    Rank 0: 0	0	0	0	0	0	0
    Rank 0: 1	2	3	4	5	6	1
    Rank 0: 2	4	6	8	10	12	2
    Rank 0: 3	6	9	12	15	18	3
    Rank 0: 4	8	12	16	20	24	4
    Rank 0: 5	10	15	20	25	30	5
    Rank 0: 6	12	18	24	30	36	6
    Rank 0: 7	14	21	28	35	42	7
    Rank 0: 8	16	24	32	40	48	8
    Rank 0: 9	18	27	36	45	54	9
    rank_num 1 of 2 started
    Rank 1 before:
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 0	0	0	0	0	0	0
    Rank 1 after:
    Rank 1: 0	0	0	0	0	0	0
    Rank 1: 1	2	3	4	5	0	1
    Rank 1: 2	4	6	8	10	0	2
    Rank 1: 3	6	9	12	15	0	3
    Rank 1: 4	8	12	16	20	0	4
    Rank 1: 5	10	15	20	25	0	5
    Rank 1: 6	12	18	24	30	0	6
    Rank 1: 7	14	21	28	35	0	7
    Rank 1: 8	16	24	32	40	0	8
    Rank 1: 9	18	27	36	45	0	9
    

    Wie man sieht, wird der dritte Eintrag des zweiten Arrays nicht übertragen.

    Woran liegt das denn? Kann ich beim konstruieren von MPI Datentypen keine negativen Verschiebungen benutzen? Warum wird dann ausgerechnet dieser Eintrag nicht übertragen?

    Wenn das tatsächlich das Problem sein sollte: Wie kann ich das richtig machen? Bei so einem einfachen Struct ist klar, wie die Daten im Speicher liegen, aber diese Reihenfolge ist
    a) nicht vom C Sprachstandard definiert (oder doch? weiß nicht) und somit möglicherweise plattformabhängig
    b) Bei wirklich komplexen Typen mit Vererbung&Co ist nicht mehr einsichtig, wie die Daten liegen.


  • Mod

    Also ein paar Sachen habe ich inzwischen rausgefunden:

    Das mit den negativen displacements war totaler Quatsch. Es ist ja immer alles Positiv.

    Eine stillschweigende Annahme die ich gemacht habe, die aber nicht allgemein gilt, ist, dass es vor dem ersten Element des structs kein padding geben kann.

    Außerdem scheint es Probleme bei Padding am Ende des structs zu geben.

    Meine gedruckte MPI Anleitung erwähnt diese Probleme natürlich mit keinem Wort 😡 . Ich habe aber inzwischen rausgefunden, dass man dies mit MPI_LB und MPI_UB als Pseudotypen am Anfang und Ende des MPI-Datentyps beheben kann. Das klappt bei mir zwar gerade nicht, aber das ist wohl nur eine Frage der Zeit bis ich das richtig hingetüftelt habe. Ich melde mich dann wieder.


  • Mod

    Bin leider das Wochenendeüber nicht zum Programmieren gekommen, hier eine richtige, vollständig portable Lösung.

    struct bead
    {
      char padding[3];      // Ein paar Füllvariablen
      double foo[3];
      int number;
      char nichtsnutz[5];   // Ein paar Füllvariablen
      double bar[3];
      char dummy[3];        // Ein paar Füllvariablen
    };
    
    [...]
    
       // Erstelle den MPI Datentyp. Dazu:
      MPI_Aint  displ_bead[5];
      {  
        bead b;   // Erzeuge ein Objekt der Klasse
    
        MPI_Aint addr_foo, addr_bar, addr_number;
        MPI_Get_address(&b.foo     , &addr_foo);  // finde die Adressen
        MPI_Get_address(&b.bar     , &addr_bar);
        MPI_Get_address(&b.number  , &addr_number);
    
        MPI_Aint addr_begin;
        MPI_Get_address(&b  , &addr_begin);       // Adresse des Anfangs
    
        displ_bead[0] = 0;                        // Der Anfang entspricht einer Verschiebung von 0
        displ_bead[1] = addr_foo    - addr_begin; // Rechne die Abstände zum Anfang aus
        displ_bead[2] = addr_bar    - addr_begin; 
        displ_bead[3] = addr_number - addr_begin;
        displ_bead[4] = sizeof(bead);             // Das Ende. Ende - Anfange = Größe
    
      }
      int        blockl_bead[5]={1     ,  3        , 3        , 1      , 1    };// 3 Datenfelder, mit Längen 3, 3 und 1. Davor der Anfang, danach das Ende
      MPI_Datatype type_bead[5]={MPI_LB, MPI_DOUBLE,MPI_DOUBLE, MPI_INT,MPI_UB};// Die Typen der Felder. LB=lower bound ist der Anfang. UB= upper bound ist das Ende
      MPI_Datatype(P_BEAD);
      MPI_Type_struct(5, blockl_bead   , displ_bead   , type_bead   , &P_BEAD);
      MPI_Type_commit(&P_BEAD);
    
    [...]
    

    Und mein MPI Buch wäre wohl ein Fall für den Müll, wenn ich es nicht aus einer Bibliothek ausgeliehen hätte wo es jetzt meinetwegen wieder im Regal verstauben darf.



  • Was heißt MPI ausgeschrieben, wenn man fragen darf?



  • Message Passing Interface, ein Standard für den Nachrichtenaustausch bei parallelen Berechnungen auf verteilten Computersystemen


Anmelden zum Antworten