分段错误 11:带有 MPI 的 C

Segmentation fault 11: C with MPI

正在使用 MPI 实现 Game of Life 的并行版本,出现分段错误(信号 11)。 MPI 的新手,并不能真正让 valgrind 告诉我错误到底在哪里。简化了我的代码,发现粗体代码段存在问题。

编辑:标记了存在问题的代码块

#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>


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

  if(argc!=5)
  printf("Incorrect number of arguments.\n");
  else{
  // program logic here
  int m, n, sum, pid, nprocs;
  char outfilename[16];
  FILE *outfile;
  FILE *infile;
  int r=atoi(argv[3]);
  int c=atoi(argv[4]);
  int gens=atoi(argv[2]);
  int **old, **new, *new1d, *old1d;
  int i,j;
  MPI_Status status;


  MPI_Init(&argc,&argv);
  MPI_Comm_size(MPI_COMM_WORLD,&nprocs);
  MPI_Comm_rank(MPI_COMM_WORLD,&pid); //initializing MPI here

//prevented segmentation error by using atoi

//domain decomposition start

// problem arisis here
int seg=c/nprocs; //divide by width
int ghost=seg+1;
int row=r+1;
int tsize=ghost*row; //ghost cells

 old1d = malloc(tsize*sizeof(int));
   new1d = malloc(tsize*sizeof(int));
  old   = malloc(r*sizeof(int*));
  new   = malloc(r*sizeof(int*));

   for(i=0; i<ghost; i++){
    old[i] = &old1d[i*row];
    new[i] = &new1d[i*row];
  }
// problem ends 

if(pid==0){
        MPI_Send(&old[0][seg], c, MPI_INT, 1,  0, MPI_COMM_WORLD);
        MPI_Recv(&old[0][ghost],c, MPI_INT, 1,  1, MPI_COMM_WORLD, &status);
        MPI_Send(&old[0][1],   c, MPI_INT, 1,  2, MPI_COMM_WORLD);
        MPI_Recv(&old[0][0],   c, MPI_INT, 1,  3, MPI_COMM_WORLD, &status);
}
else{
        MPI_Recv(&old[0][0],    c, MPI_INT, 0,  0, MPI_COMM_WORLD, &status);
        MPI_Send(&old[0][1],    c, MPI_INT, 0,  1, MPI_COMM_WORLD);
        MPI_Recv(&old[0][ghost],c, MPI_INT, 0,  2, MPI_COMM_WORLD, &status);
        MPI_Send(&old[0][seg], c, MPI_INT, 0,  3, MPI_COMM_WORLD);

}
infile=fopen(argv[1],"r");

if(infile==NULL){
    printf("Could not locate file.\n");
    exit(1);
}

  while(fscanf(infile,"%d %d",&m, &n)!=EOF){
    old[m][n]=1;
  }
  fclose(infile);
//repeat for number of generations
  for(n=0; n<gens; n++){

    for(i=1; i<=r; i++){
      for(j=1; j<=c; j++){

        sum =  old[i-1][j-1] + old[i-1][j] + old[i-1][j+1]
          + old[i][j-1] + old[i][j+1]
          + old[i+1][j-1] + old[i+1][j] + old[i+1][j+1];

        if(sum==2 || sum==3)
            new[i][j]=1;
        else
            new[i][j]=0;
      }
    }
   //copying old state into new state
    for(i=1; i<=r; i++){
      for(j=1; j<=c; j++){
        old[i][j] = new[i][j];
      }
    }
  }

  //create new output file
  sprintf(outfilename,"output_%d",pid);
  outfile=fopen(outfilename,"w");
  for(i=1; i<=r; i++){
    for(j=1; j<=c; j++){
     if(new[i][j]==1){
     fprintf(outfile,"%d\t%d\n",i ,j);
     printf("%d %d",i,j);
     }
    }
  }

  fclose(outfile);
  MPI_Finalize();
  }
  return 0;
}

编辑:输入文件 life.data.1 具有表示活细胞的 X Y 坐标。

正如 wildplasser 在 中暗示的那样,崩溃源于您在此处的循环(该循环已超出您指出的问题来源):

for(i=1; i<=r; i++){
  for(j=1; j<=c; j++){

    sum =  old[i-1][j-1] + old[i-1][j] + old[i-1][j+1]
      + old[i][j-1] + old[i][j+1] //This line causes a segfault
      + old[i+1][j-1] + old[i+1][j] + old[i+1][j+1]; //This line causes a segfault

    if(sum==2 || sum==3)
        new[i][j]=1; //This line causes a segfault
    else
        new[i][j]=0; //This line causes a segfault
  }
}

注释指示的所有行都由于访问索引而导致段错误。您的循环分别给出 ij1r1c(含)。但是,仅访问 old 数组中的元素 0r-1new 数组中的元素 0r-1 才有效。要解决此问题,我们可以将外循环更改为

for(i = 1; i < r-1; i++){
    //...code here...
}

值得注意的是,这不会设置新的每个值。您可能打算将 old 声明为大小 row,即 r+1。在这种情况下,您可以在此循环中将继续条件设置为 i < r。不过,我不确定情况是否如此。这会处理因访问 old.

中的元素而产生的错误

但是,oldold1d的使用仍然存在问题。 old 中有 r (250) 个元素,但在这种情况下只有 ghost (63) 个条目被初始化。 old1d 从未初始化任何非活动值,并且未初始化的值与 MPI_SendMPI_Recv 一起发送。您需要选择 old 数组的大小并确保初始化其中的所有值,还需要选择 old1d 数组的大小并确保其所有值都已初始化。 newnew1d.

也是如此

通过更改循环,使 oldnew 始终在索引 0ghost-1 之间访问(包括在内),并更改所有循环,以便每个元素oldnew(类似于 old[i]new[i])总是在 0r-1 之间访问,程序不会崩溃。这是通过在主循环中保留 i < ghost(或 < ghost - 1)并在每个循环中保留 j < r(或类似地,< r -1)来完成的。但是,这几乎肯定不会提供您想要的行为。看起来您当前版本的循环是为串行程序设计的,并且忽略了您试图通过将列拆分为 seg 大小的块来引入的并行性。循环需要完全重新设计,以便它们的访问使用程序中的并行性,并且进程进行通信,以便跨不同处理器的相邻单元具有适当的通信。这需要对程序进行重大改革。

还有两个问题想指出:

  1. 如果您 运行 程序在超过 2 个进程上挂起,因为发送和接收被硬编码为仅在进程 0 和进程 1 之间通信。您需要确保进程 2 及以上进程要么不尝试 send/receive,要么在 send/receive.

  2. 时实际进行通信
  3. 当您计算 seg = c/nprocs 时,此除法的余数将被忽略。有250列,但是seg变成250/4 = 62(整数除法),62*4 = 248,小于全部列数。您需要确保处理将列不干净地划分为进程的情况。