避免丢失在群集上运行的作业的数据

时间:2014-04-07 14:31:38

标签: c mpi hpc

最近,我开始研究群集以加快我的工作。目前我的工作是使用不同大小的输入数据来分析代码。以前我在每个输入文件的for循环中都这样做。在集群上,我使用MPI将每个不同输入的程序作为不同的进程运行。

MPI脚本的源代码可以在下面找到。它运行不同的进程,并且还有一个“服务器”进程写出结果,以避免两个进程同时写入输出文件的情况。由于代码现在是结构化的,我只能在所有进程按时结束时访问输出。这是一个问题,因为我试图尽可能低地保持工作的挂起时间以快速启动作业(群集不断满,所以如果我需要太多资源,很难开始工作)。所以有时我的工作会被调度程序过早打断。

我的想法是在服务器进程中添加一个计时器,如果当前的walltime接近最大值(下面代码中的两分钟),则关闭文件流。这样,至少我不会丢失已经收集的数据。但是这不起作用,因为定时器仅在服务器接收新数据时才更新。 应该避免仅在服务器接收新数据时打开文件,因为我更喜欢每次提交作业时都以空输出文件开头。还有哪些其他选项可以确保我不会丢失已经收集的输出?

#include <mpi.h>
#define RES 1

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

  int nprocs, myid, server, ndone;
  double WallTime;
  struct timeval start, end;
  double countTime, res[4];
  FILE *fpt;

  WallTime = 1*60+59;

  MPI_Comm world;
  MPI_Group world_group;
  MPI_Status status;
  MPI_Init(&argc, &argv);
  world = MPI_COMM_WORLD;
  MPI_Comm_size(world,&nprocs);
  MPI_Comm_rank(world,&myid);
  server = nprocs-1; /* last proc is server */
  MPI_Comm_group(world, &world_group);

  if(myid == server){ /* I store the output */
    ndone = 0;
    fpt = fopen(argv[2],"wt");
    gettimeofday(&start, NULL);
    do{
      MPI_Recv(res, 4, MPI_DOUBLE, MPI_ANY_SOURCE, RES, world, &status);
      fprintf(fpt,"%d\t%10.7f\t%10.7f\t%ld\n", (int) res[0], res[1], res[2], (long int) res[3]);
      gettimeofday(&end, NULL);
      countTime = (end.tv_sec+(end.tv_usec)*1.e-6)-(start.tv_sec+(start.tv_usec)*1.e-6);
      ndone++;
    } while (ndone < (nprocs-1) && countTime < WallTime);
    fclose(fpt);
  } else if(myid<(nprocs-1)){
    do sth with data according to myid ...
    MPI_Send(res, 4, MPI_DOUBLE, server, RES, world);
  }
  MPI_Finalize();
}

2 个答案:

答案 0 :(得分:2)

选项1:使用非阻塞探测来检查消息是否在等待,如果不是则稍微睡一下:

do {
  int flag;
  MPI_Iprobe(MPI_ANY_SOURCE, RES, &flag, world, &status);
  if (flag) {
    MPI_Recv(res, 4, MPI_DOUBLE, status.MPI_SOURCE, RES, world, &status);
    ...
    ndone++;
  }
  else
    usleep(10000);
  gettimeofday(&end, NULL);
  countTime = (end.tv_sec+(end.tv_usec)*1.e-6)-(start.tv_sec+(start.tv_usec)*1.e-6);
} while (ndone < (nprocs - 1) && countTime < WallTime);

您可以跳过usleep()调用,然后主进程将运行紧密循环,使CPU利用率几乎保持在100%。这在HPC系统上通常不是问题,其中每个MPI等级都绑定到单独的CPU核心。

选项2:大多数资源管理器都可以配置为在作业即将被杀死之前的某个时间发送Unix信号。例如,Sun / Oracle Grid Engine和LSF都会在使用SIGKILL杀死作业之前的某个时间提供SIGUSR2。对于SGE,应该将-notify选项添加到qsub以使其发送SIGUSR2。 SIGUSR2与后续SIGKILL之间的时间量可由SGE管理员基于每个队列进行配置。 LSF在达到作业结束时间时发送SIGUSR2,如果作业在此之后的10分钟内没有终止,则发送SIGKILL。

选项3:如果您的资源管理器不合作,并且在裁员之前没有发送警告信号,您只需发送自己的SIGALRM即可。您通常会执行以下操作:

  • 使用timer_create();
  • 创建计时器
  • (重新)使用timer_settime();
  • 武装计时器
  • 最后使用timer_delete()销毁计时器。

您可以将计时器编程为在整个挂钟时间之前不久到期(但这是一个糟糕的编程习惯,因为您必须将该值与资源管理器请求的挂钟时间相匹配)或者您可能有计时器以短间隔点火,例如5分钟,然后每次重新安装。

选项2和3要求您为相应的信号编写和设置信号处理程序。关于信号的好处是它们通常是异步传递的,即使你的代码卡在像MPI_Recv这样的阻塞MPI调用中。我认为这是一个高级主题,并建议你现在坚持选项1,并记住选项2和3存在。

选项4:某些MPI库支持正在运行的作业的检查点/重新启动。检查点创建MPI作业运行状态的快照,然后可以使用特殊mpiexec(或MPI启动器的任何名称,如果有的话)命令行标志恢复状态。此方法无需更改程序的源代码,但通常无法广泛使用,尤其是在群集设置方面。

答案 1 :(得分:2)

当从作业调度程序到达异步终止信号时,您似乎正在通过fprintf()使用缓冲文件I / O.异步信号将中止作业,glibc将没有机会刷新其文件缓冲区。您可能想从信号处理程序使用fflush(),但fflush()不是异步信号处理程序安全。

以下是一些避免过于复杂的建议:

无缓冲I / O:

简单的解决方案是将文件描述符切换为非阻塞。您可以通过以下方式执行此操作:

setbuf(filehandle, NULL);

由于这是无缓冲的,因此glibc不会执行写入组合。如果fprintf()s很少见,那就不会有问题了。但是如果你正在写很多短的fprintf()调用,这可能不是一个很好的性能选择。

定期清除文件内容

glibc fflush()命令可以推出缓冲区中的数据。只有在每个fprintf()之后进行fflush时,才会模拟无缓冲的I / O情况。但是,fflush()提供了更多的灵活性。由于您似乎无法依赖最大MPI_Recv()时间,因此您可能会考虑定期刷新文件缓冲区。

实现此目的的一种方法是使用pthread_create()生成一个单独的线程,并让新线程定期调用fflush(filehandle)。一秒钟应该是一个很好的频率。您需要谨慎使用以确保文件句柄在两个线程之间保持有效。