2017-05-11 6 views
0

À LA LUMIÈRE DES ÉDITÉE COMMENTAIRESErreur dans parallélisation MPI_Allgather

J'apprends MPI et je fais quelques exercices pour comprendre certains aspects de celui-ci. J'ai écrit un code qui devrait effectuer un simple Monte-Carlo.

Il y a deux boucles principales qui doivent être accomplies: une sur les pas de temps T et une plus petite à l'intérieur de celle-ci sur le nombre de molécules N. Donc, après avoir essayé de déplacer chaque molécule, le programme passe à l'étape suivante.

J'ai essayé de paralléliser en divisant les opérations sur les molécules sur les différents processeurs. Malheureusement, le code, qui fonctionne pour 1 processeur, imprime les mauvais résultats pour total_E lorsque p> 1. Le problème réside probablement dans la fonction suivante et plus précisément est donné par un appel à MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD);

Je ne comprends absolument pas pourquoi. Qu'est-ce que je fais mal? (En plus d'une stratégie de parallélisation primitive)

Ma logique était que pour chaque pas de temps je pouvais calculer les mouvements sur les molécules sur les différents processeurs. Malheureusement, pendant que je travaille avec les vecteurs locaux local_r sur les différents processeurs, pour calculer la différence d'énergie local_DE, j'ai besoin du vecteur global r puisque l'énergie de la molécule i-ième dépend de tous les autres. Par conséquent j'ai pensé appeler MPI_Allgather puisque je dois mettre à jour le vecteur global aussi bien que les locaux.

void Step(double (*H)(double,double),double* local_r,double* r,double *E_,int n,int my_rank){ 

    int i; 
    double* local_rt = calloc(n,sizeof(double)); 
    double local_DE; 

    for(i=0;i<n;i++){ 

     local_rt[i] = local_r[i] + delta*((double)lrand48()/RAND_MAX-0.5); 
      local_rt[i] = periodic(local_rt[i]); 

      local_DE = E_single(H,local_rt,r,i,n,my_rank) - E_single(H,local_r,r,i,n,my_rank); 

     if (local_DE <= 0.0 || exp(-local_DE) > (double) lrand48()/RAND_MAX ) {    
      (*E_) += local_DE; 
      local_r[i] = local_rt[i]; 

     } 
MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD); 

    } 


    return ; 

} 

Ici, il est le complet « de travail » Code:

#define _XOPEN_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
#include <time.h> 
#include <mpi.h> 

#define N 100 
#define L 5.0 
#define T_ 5000 
#define delta 2.0 


void Step(double (*)(double,double),double*,double*,double*,int,int); 
double H(double ,double); 
double E(double (*)(double,double),double* ,double*,int ,int); 
double E_single(double (*)(double,double),double* ,double*,int ,int ,int); 
double * pos_ini(void); 
double periodic(double); 
double dist(double , double); 
double sign(double); 

int main(int argc,char** argv){ 

    if (argc < 2) { 
     printf("./program <outfile>\n"); 
     exit(-1); 
    } 

    srand48(0); 
     int my_rank; 
    int p; 
    FILE* outfile = fopen(argv[1],"w"); 
    MPI_Init(&argc,&argv); 
    MPI_Comm_rank(MPI_COMM_WORLD,&my_rank); 
    MPI_Comm_size(MPI_COMM_WORLD,&p); 
    double total_E,E_; 
    int n; 
    n = N/p; 
    int t; 
    double * r = calloc(N,sizeof(double)),*local_r = calloc(n,sizeof(double)); 

    for(t = 0;t<=T_;t++){ 
     if(t ==0){ 
      r = pos_ini(); 
      MPI_Scatter(r,n,MPI_DOUBLE, local_r,n,MPI_DOUBLE, 0, MPI_COMM_WORLD); 
       E_ = E(H,local_r,r,n,my_rank); 
     }else{ 
      Step(H,local_r,r,&E_,n,my_rank); 
     } 

     total_E = 0; 
     MPI_Allreduce(&E_,&total_E,1,MPI_DOUBLE,MPI_SUM,MPI_COMM_WORLD); 

      if(my_rank == 0){ 
       fprintf(outfile,"%d\t%lf\n",t,total_E/N); 
      } 

    } 

    MPI_Finalize(); 

    return 0; 

} 



double sign(double a){ 

    if(a < 0){ 
     return -1.0 ; 
    }else{ 
     return 1.0 ; 
    } 

} 

double periodic(double a){ 

    if(sqrt(a*a) > L/2.0){ 
     a = a - sign(a)*L; 
    } 

    return a; 
} 


double dist(double a, double b){ 

    double d = a-b; 
    d = periodic(d); 

    return sqrt(d*d); 
} 

double * pos_ini(void){ 

    double * r = calloc(N,sizeof(double)); 
    int i; 

    for(i = 0;i<N;i++){ 
    r[i] = ((double) lrand48()/RAND_MAX)*L - L/2.0; 
    } 

    return r; 

} 

double H(double a,double b){ 

     if(dist(a,b)<2.0){ 
     return exp(-dist(a,b)*dist(a,b))/dist(a,b); 
    }else{ 

    return 0.0; 

    } 
} 



double E(double (*H)(double,double),double* local_r,double*r,int n,int my_rank){ 

    double local_V = 0; 
    int i; 

    for(i = 0;i<n;i++){ 
      local_V += E_single(H,local_r,r,i,n,my_rank); 
    } 
    local_V *= 0.5; 

    return local_V; 
} 

double E_single(double (*H)(double,double),double* local_r,double*r,int i,int n,int my_rank){ 

    double local_V = 0; 
    int j; 

     for(j = 0;j<N;j++){ 

     if((i + n*my_rank) != j){ 
      local_V+=H(local_r[i],r[j]); 
     } 

    } 

    return local_V; 
} 


void Step(double (*H)(double,double),double* local_r,double* r,double *E_,int n,int my_rank){ 

    int i; 
    double* local_rt = calloc(n,sizeof(double)); 
    double local_DE; 

    for(i=0;i<n;i++){ 

     local_rt[i] = local_r[i] + delta*((double)lrand48()/RAND_MAX-0.5); 
      local_rt[i] = periodic(local_rt[i]); 

      local_DE = E_single(H,local_rt,r,i,n,my_rank) - E_single(H,local_r,r,i,n,my_rank); 

     if (local_DE <= 0.0 || exp(-local_DE) > (double) lrand48()/RAND_MAX ) {    
      (*E_) += local_DE; 
      local_r[i] = local_rt[i]; 

     } 
MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD); 

    } 


    return ; 

} 
+1

Un 'MPI_Allgather' conditionnel? Cela ne bloquerait-il pas votre programme au cas où il ne serait pas invoqué dans tous les processus? –

+0

Vous êtes en train de le sortir de la si supprime le bloc, malheureusement le programme ne fonctionne toujours pas – Fra

+0

Alors, quel est le nouveau problème? –

Répondre

2

Vous ne pouvez pas vous attendre à obtenir la même énergie nombre donné différents des processus MPI pour une raison simple - les configurations générées sont très différents selon le nombre de processus. La raison n'est pas MPI_Allgather, mais la façon dont les balayages de Monte-Carlo sont effectués.

Pour un processus, vous essayez de déplacer l'atome 1, puis l'atome 2, puis l'atome 3, et ainsi de suite, jusqu'à atteindre l'atome N. Chaque tentative voit la configuration résultant de la précédente, ce qui est bien. Étant donné deux processus, vous tentez de déplacer l'atome 1 tout en essayant de déplacer l'atome N/2. Aucun des deux atomes ne voit le déplacement éventuel de l'atome N/2 ni l'inverse, mais alors les atomes 2 et N/2 + 1 voient le déplacement de l'atome 1 et de l'atome N/2. Vous vous retrouvez avec deux configurations partielles que vous fusionnez simplement avec le tout-rassembler. Ceci est et non équivalent au cas précédent lorsqu'un seul processus effectue toutes les tentatives MC. La même chose s'applique dans le cas de plus de deux processus.

Il existe une autre source de différence - la séquence du nombre pseudo-aléatoire (PRN). La séquence produite par les appels répétés à lrand48() dans un processus n'est pas la même que la séquence combinée produite par plusieurs appels indépendants à lrand48() dans différents processus, donc même si vous séquentiez les essais, l'acceptation différera encore en raison de la PRN localement différente séquences. Oubliez les valeurs spécifiques de l'énergie produite après chaque étape. Dans une simulation MC appropriée, ceux-ci sont insignifiants. Ce qui compte, c'est la valeur moyenne sur un grand nombre d'étapes. Ceux-ci devraient être les mêmes (dans une certaine marge proportionnelle à 1/sqrt(N)) quel que soit l'algorithme de mise à jour utilisé.

+0

Cela signifie-t-il que ce code est intrinsèquement en série? Après tout, les atomes doivent être traités séquentiellement. – Shibli

+1

MCMC est intrinsèquement en série, mais il existe des méthodes qui permettent d'exécuter plusieurs chaînes de Markov et d'échanger périodiquement des informations entre eux, le résultat final étant très similaire à ce que donne un MCMC séquentiel. Voir [ici] (https://arxiv.org/pdf/1312.7479v1.pdf) pour une description détaillée d'une technique ou [ici] (https://www2.stat.duke.edu/~scs/Parallel.pdf) pour la version simplifiée. –

0

Cela a été assez long depuis la dernière fois que je MPI, mais il semble que votre arrête programme lorsque vous essayez de « rassembler » et mettre à jour les données dans plusieurs de tous les processus et il est imprévisible quels processus devraient faire la collecte.

Donc, dans ce cas, une solution simple est de laisser le reste des processus envoyer des données factices afin qu'ils puissent simplement ignorer par d'autres. Par exemple,

if (local_DE <= 0.0 || exp(-local_DE) > (double) lrand48()/RAND_MAX ) {    
    (*E_) += local_DE; 
    local_r[i] = local_rt[i]; 
    MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD); 
    // filter out the dummy data out of "r" here 
} else { 
    MPI_Allgather(dummy_sendbuf, n, MPI_DOUBLE, dummy_recvbuf, n, MPI_DOUBLE, MPI_COMM_WORLD); 
} 

des données fictives pourraient être des faux numéros exceptionnels qui ne devraient pas être présents dans les résultats, pour que d'autres processus pourraient les filtrer. Mais comme je l'ai mentionné, c'est un gaspillage car vous n'avez pas vraiment besoin de recevoir autant de données de tous les processus et nous aimerions l'éviter surtout quand il y a beaucoup de données à envoyer.

Dans ce cas, vous pouvez recueillir des « drapeaux » d'autres processus afin que nous puissions savoir quels processus propres à envoyer des données.

// pseudo codes 
// for example, place 1 at local_flags[my_rank] if it's got data to send, otherwise 0 
MPI_Allgather(local_flags, n, MPI_BYTE, recv_flags, n, MPI_BYTE, MPI_COMM_WORLD) 
// so now all the processes know which processes will send 
// receive data from those processes 
MPI_Allgatherv(...) 

Je me souviens avec MPI_Allgatherv, vous pouvez spécifier le nombre d'éléments à recevoir d'un processus spécifique. Voici un exemple: http://mpi.deino.net/mpi_functions/MPI_Allgatherv.html

Mais gardez à l'esprit que cela pourrait être une surutilisation si le programme n'est pas bien parallélisé. Par exemple, dans votre cas, ceci est placé dans une boucle, de sorte que les processus sans données doivent encore attendre la prochaine réunion des drapeaux.

+0

J'ai essayé votre solution mais cela ne semble pas fonctionner – Fra

+0

@Fra Ensuite, je vous suggère de tester quelques exemples plus simples pour voir s'il y a des problèmes avec votre environnement en premier lieu. –

0

Vous devriez prendre MPI_Allgather() à l'extérieur pour la boucle. J'ai testé avec le code suivant mais notez que j'ai modifié les lignes impliquant RAND_MAX afin d'obtenir des résultats cohérents. En conséquence, le code donne la même réponse pour le nombre de processeurs 1, 2 et 4.

void Step(double (*H)(double,double),double* local_r,double* r,double *E_,int n,int my_rank){ 

    int i; 
    double* local_rt = calloc(n,sizeof(double)); 
    double local_DE; 

    for(i=0;i<n;i++){ 

     //local_rt[i] = local_r[i] + delta*((double)lrand48()/RAND_MAX-0.5); 
     local_rt[i] = local_r[i] + delta*((double)lrand48()-0.5); 
     local_rt[i] = periodic(local_rt[i]); 

     local_DE = E_single(H,local_rt,r,i,n,my_rank) - E_single(H,local_r,r,i,n,my_rank); 

     //if (local_DE <= 0.0 || exp(-local_DE) > (double) lrand48()/RAND_MAX ) 
     if (local_DE <= 0.0 || exp(-local_DE) > (double) lrand48() ) 
     { 
      (*E_) += local_DE; 
      local_r[i] = local_rt[i]; 
     } 

    } 

    MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD); 

    return ; 
} 
+0

Cela change la physique et produit un ensemble complètement différent car l'atome «i + 1» ne voit pas la nouvelle position de l'atome «i». –

+0

Oui, vous avez raison. – Shibli