首页 > 解决方案 > 并发非重叠 pwrite() 到使用多个 MPI 进程安装在 NFS 上的文件

问题描述

我有一个计算流体动力学代码,我正在编写并行读写实现。我想要实现的是多个 MPI 进程打开同一个文件并向其写入数据(没有数据重叠,我使用 pwrite() 和偏移信息)。当两个 MPI 进程在同一个计算节点上时,这似乎工作正常。但是,当我使用 2 个或更多计算节点时,一些数据无法到达硬盘。为了证明这一点,我编写了以下使用 mpicc 编译的 C 程序(我的 MPI 发行版是 MPICH):

#include <mpi.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

long _numbering(long i,long j,long k, long N) {
  return (((i-1)*N+(j-1))*N+(k-1));
}

int main(int argc, char **argv)
{
  int   numranks, rank,fd,dd;
  long i,j,k,offset,N;
  double value=1.0;
  MPI_Init(NULL,NULL);
  MPI_Comm_size(MPI_COMM_WORLD, &numranks);
  MPI_Comm_rank(MPI_COMM_WORLD,&rank);
  N=10;
  offset=rank*N*N*N*sizeof(double);
  fd=-1;
  printf("Opening file datasetparallel.dat\n");
  //while(fd==-1) {fd = open("datasetparallel.dat", O_RDWR | O_CREAT | O_SYNC,0666);}
  while(fd==-1) {fd = open("datasetparallel.dat", O_RDWR | O_CREAT,0666);}
  //while(dd==-1) {fd = open("/homeA/Desktop/", O_RDWR ,0666);}

  for(i=1;i<=N;i++) {
    for(j=1;j<=N;j++) {
      for(k=1;k<=N;k++) {
        if(pwrite(fd,&value,sizeof(double),_numbering(i,j,k,N)*sizeof(double)+offset)!=8) perror("datasetparallel.dat");
        //pwrite(fd,&value,sizeof(double),_numbering(i,j,k,N)*sizeof(double)+offset);
        value=value+1.0;
      }
    }
  }
  //if(close(fd)==-1) perror("datasetparallel.dat");
  fsync(fd); //fsync(dd);
  close(fd); //close(dd);
 
  printf("Done writing in parallel\n");
  if(rank==0) {
    printf("Beginning serial write\n");
    int ranknum;
    fd=-1;
    value=1.0;
    while(fd==-1) {fd = open("datasetserial.dat", O_RDWR | O_CREAT,0666);}
    for(ranknum=0;ranknum<numranks;ranknum++){
      offset=ranknum*N*N*N*sizeof(double); printf("Offset for rank %d is %ld\n",ranknum,offset);
      printf("writing for rank=%d\n",ranknum);
      for(i=1;i<=N;i++) {
        for(j=1;j<=N;j++) {
          for(k=1;k<=N;k++) {
            if(pwrite(fd,&value,sizeof(double),_numbering(i,j,k,N)*sizeof(double)+offset)!=8) perror("datasetserial.dat");
            //pwrite(fd,&value,sizeof(double),_numbering(i,j,k,N)*sizeof(double)+offset);
            value=value+1.0;
          }
        }
      }
      value=1.0;
    }
    //if(close(fd)==-1) perror("datasetserial.dat");
    fsync(fd);
    close(fd);
    printf("Done writing in serial\n");
  }
  MPI_Finalize();
  return 0;
}

上面的程序按升序将双精度数写入文件。每个 MPI 进程将相同的数字(1.0 到 1000.0)写入文件的不同区域。例如,rank 0 写入 1.0 到 1000.0,rank 1 从 rank 0 写入 1000.0 之后的位置开始写入 1.0 到 1000.0。该程序输出一个名为 datasetparallel.dat 的文件,该文件已通过并发 pwrite()s 写入。它还输出 datasetserial.dat 以供参考,以与 datasetparallel.dat 文件进行比较以检查其完整性(我通过在终端中使用 cmp 命令执行此操作)。当使用 cmp 发现差异时,我使用 od 命令检查文件的内容:

od -N <byte_number> -tfD <file_name>

例如,我使用上述程序发现了一些丢失的数据(文件中的漏洞)。在并行编写的文件中,使用od命令的输出:

.
.
.
0007660                      503                      504
0007700                      505                      506
0007720                      507                      508
0007740                      509                      510
0007760                      511                      512
0010000                        0                        0
*
0010620                        0
0010624

而在串行编写的参考文件中,od命令的输出:

.
.
.
0007760                      511                      512
0010000                      513                      514
0010020                      515                      516
0010040                      517                      518
0010060                      519                      520
0010100                      521                      522
0010120                      523                      524
0010140                      525                      526
0010160                      527                      528
0010200                      529                      530
0010220                      531                      532
0010240                      533                      534
0010260                      535                      536
0010300                      537                      538
0010320                      539                      540
0010340                      541                      542
0010360                      543                      544
0010400                      545                      546
0010420                      547                      548
0010440                      549                      550
0010460                      551                      552
0010500                      553                      554
0010520                      555                      556
0010540                      557                      558
0010560                      559                      560
0010600                      561                      562
0010620                      563                      564
.
.
.

到目前为止,解决此问题的唯一方法似乎是使用带有 O_SYNC 标志的 POSIX open() 函数,该函数可确保将文件物理写入硬盘驱动器,但这似乎慢得不切实际。另一种同样缓慢的方法似乎是使用内置的 MPI I/O 命令。我也不确定为什么 MPI I/O 很慢。存储已使用以下标志安装在 NFS 上:rw,nohide,insecure,no_subtree_check,sync,no_wdelay.我尝试在文件和目录上调用 fsync() 无济于事。因此,我需要有关如何解决此问题的建议。

标签: cparallel-processingmpinfsfile-writing

解决方案


NFS 是一个可怕的文件系统。如您所见,它的缓存行为使进程很容易“错误地共享”缓存块,然后破坏数据。

如果您被 NFS 困住,请并行执行计算,然后从一个等级执行所有 I/O。

像 OrangeFS/PVFS ( http://www.orangefs.org ) 这样的真正并行系统将在这里提供极大的帮助,尤其是在开始使用 MPI-IO 时(您已经在使用 MPI,所以您已经成功了!)。光泽是另一种选择。OrangeFS 是两者中更简单的配置,但我可能有偏见,因为我曾经在它上面工作过。

在集体 I/O 中解决随机内存是绝对可能的。您的所有数据都是 MPI_DOUBLE,因此您需要做的就是描述最坏 MPI_TYPE_CREATE_HINDEXED 的区域并提供地址。如果您将发出一个 MPI-IO 调用而不是 (if N == 10) 1000,那么您将看到性能的巨大提升。您的数据在文件中是连续的,因此您甚至不必担心关于文件视图。

此外,还记得我说过“从一个进程完成所有 I/O 吗?”。这有点高级,但如果您将“cb_nodes”提示(用于“集体缓冲”优化的节点数)设置为 1 MPI-IO 将为您做到这一点。


推荐阅读