【问题标题】:Error in parallelization MPI_Allgather并行化 MPI_Allgather 中的错误
【发布时间】:2017-10-10 12:23:16
【问题描述】:

根据评论编辑

我正在学习 MPI,我正在做一些练习来了解它的某些方面。我已经编写了一个应该执行简单蒙特卡洛的代码。

其中有两个主要的循环需要完成:一个是时间步长T,另一个是分子数N。因此,在我尝试移动每个分子后,程序进入下一个时间步。

我试图通过在不同处理器上划分对分子的操作来并行化它。不幸的是,适用于 1 个处理器的代码在 p>1 时为total_E 打印错误的结果。 问题可能在于以下函数,更准确地说是调用MPI_Allgather(local_r,n,MPI_DOUBLE,r,n,MPI_DOUBLE,MPI_COMM_WORLD);

我完全不明白为什么。我究竟做错了什么? (除了原始的并行化策略)

我的逻辑是,对于每个时间步,我都可以计算不同处理器上分子的移动。不幸的是,当我在各种处理器上使用局部向量 local_r 来计算能量差 local_DE 时,我需要全局向量 r,因为第 i 个分子的能量取决于所有其他分子。因此我想打电话给MPI_Allgather,因为我必须更新全局向量以及本地向量。

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 ;

}

这是完整的“工作”代码:

#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 ;

}

【问题讨论】:

  • 有条件的MPI_Allgather?如果没有在所有进程中调用它,那不会阻止您的程序吗?
  • 你把它从 if 移除块中取出来是对的,不幸的是程序仍然无法工作
  • 那么新问题是什么?
  • 当p改变total_E的值改变(如果ones改变随机数生成器的种子为固定数就清楚了),当p = 2时它仍然类似于p = 1但是当p =4 完全不同
  • 我并没有真正得到您的化学或物理资料,但如果您想对所有流程的不可预测部分进行收集,您可以简单地让其余流程发送一些虚拟数据。有点浪费但直截了当。

标签: c mpi physics montecarlo markov-chains


【解决方案1】:

自从我上次使用 MPI 以来已经很长时间了,但是当您尝试“收集”并更新所有进程中的数据时,您的程序似乎停止了,并且无法预测哪些进程需要执行聚会。

因此,在这种情况下,一个简单的解决方案是让其余进程发送一些虚拟数据,以便其他进程可以简单地忽略它们。例如,

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);
}

虚拟数据可能是一些不应该出现在结果中的异常错误数字,因此其他进程可以将它们过滤掉。

但正如我所提到的,这非常浪费,因为您实际上并不需要从所有进程中接收那么多数据,我们希望避免这种情况,尤其是在要发送大量数据的情况下。

在这种情况下,您可以从其他进程中收集一些“标志”,以便我们知道哪些进程拥有要发送的数据。

// 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(...)

我记得MPI_Allgatherv,您可以指定从特定进程接收的元素数量。这是一个例子:http://mpi.deino.net/mpi_functions/MPI_Allgatherv.html

但请记住,如果程序没有很好地并行化,这可能是一种矫枉过正的做法。例如,在您的情况下,它被放置在一个循环中,因此那些没有数据的进程仍然需要等待下一次收集标志。

【讨论】:

  • 我尝试了您的解决方案,但它似乎不起作用
  • @Fra 那我建议你先测试一些更简单的例子,看看你的环境是否有任何问题。
【解决方案2】:

您应该将MPI_Allgather() 放在for 循环之外。我使用以下代码进行了测试,但请注意我修改了涉及RAND_MAX 的行以获得一致的结果。因此,对于处理器数量 1、2 和 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 ;
}

【讨论】:

  • 这会改变物理并产生完全不同的整体,因为原子i+1 看不到原子i 的新位置。
【解决方案3】:

由于一个简单的原因,您不能指望在 MPI 进程数量不同的情况下获得相同的能量 - 生成的配置因进程数量而异。原因不是MPI_Allgather,而是蒙特卡洛扫描的执行方式。

给定一个进程,您尝试移动原子 1,然后是原子 2,然后是原子 3,以此类推,直到到达原子 N。每次尝试都会看到前一次产生的配置,这很好。

给定两个进程,您尝试移动原子 1,同时尝试移动原子 N/2。原子 1 既没有看到原子 N/2 的最终位移,也没有看到相反的情况,但是原子 2 和 N/2+1 看到了原子 1 和原子 N/2 的位移。您最终会得到两个部分配置,您只需将它们与 all-gather 合并。这等同于前一种情况,即单个进程完成所有 MC 尝试。这同样适用于两个以上进程的情况。

还有另一个差异来源 - 伪随机数 (PRN) 序列。在一个进程中重复调用lrand48() 产生的序列与在不同进程中多次独立调用lrand48() 产生的组合序列不同,因此即使您对试验进行排序,由于以下原因,接受度仍然会有所不同局部不同的PRN序列。

忘记每一步后产生的能量的具体值。在适当的 MC 模拟中,这些是微不足道的。重要的是大量步骤的平均值。无论使用何种更新算法,这些都应该是相同的(在与1/sqrt(N) 成比例的一定范围内)。

【讨论】:

  • 这是否意味着这段代码本质上是串行的?毕竟,原子应该按顺序处理。
  • MCMC 本质上是串行的,但是有一些方法允许运行多个马尔可夫链并在它们之间定期交换信息,最终结果与顺序 MCMC 给出的非常相似。有关一种技术的详细说明,请参阅here,或参阅here,了解简化版本。
猜你喜欢
  • 1970-01-01
  • 2017-06-04
  • 2012-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-26
  • 2019-06-02
相关资源
最近更新 更多