使用 MPI 以 ASCII 格式并行输出

计算科学 并行计算 正则 mpi io
2021-12-12 08:01:12

经过一些并行计算后,我需要将结果写入文件中,出于绘图的原因,我希望它们采用 ASCII 格式。由于性能问题,我想避免MPI_SEND将数据发送到主进程并让他打印。因此,我想知道是否有一种方法可以使用 MPI 直接用 ASCII 编写。

Fortran 示例将非常好。

3个回答

并行进程的输出问题在于它们都经过 ssh 隧道,并且无法保证它们到达的顺序。即使您使用 Send/Recv 对它们进行排序。

您可以执行以下操作:

mpirun -np 8 program_script

其中程序脚本:

#!/bin/bash
your_program > program.out$PMPI_RANK

然后

for i in `seq 1 8` ; do cat program.out$i ; done

这使用了大多数 MPI 实现为每个生成的进程设置一个唯一的环境变量这一事实。

可以使用 VTK 库及其并行 IO 内置机制将每个级别的文件写入不同的文件,ParaView 可以再次组合它们以显示可视化。另外,我应该说我没有 FORTRAN 示例,不幸的是 VTK 不正式支持 FORTRAN。因此,我将向您展示一个 C++ 示例,我不确定它对您有多大用处。为了实现这一点,您需要使用启用 MPI 的标志构建 VTK 才能访问vtkMPIController类。我把整个函数是这样的:

首先,您需要定义名为的头文件VtkParallelWriter.h

// MPI Library
#include <mpi.h>

//VTK Library
#include <vtkXMLPStructuredGridWriter.h>
#include <vtkStructuredGrid.h>
#include <vtkSmartPointer.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>
#include <vtkMPIController.h>
#include <vtkProgrammableFilter.h>
#include <vtkInformation.h>

struct Args {
  vtkProgrammableFilter* pf;
  int local_extent[6];
};

void execute (void* arg);
void vtkParallelWriter(int argc, char *argv[],std::vector<double*>colors, std::vector<char*> names, int LX, int LY, int LZ, double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, double local_origin_x, double local_origin_y, double local_origin_z, int nn, int timesnapshot);

然后这个函数的主体进入VtkParallelWriter.cc文件:

#include <vector>
#include <string>
#include <iostream>
#include "VtkParallelWriter.h"

// function to operate on the point attribute data
void execute (void* arg) {
  Args* args = reinterpret_cast<Args*>(arg);
  auto info = args->pf->GetOutputInformation(0);
  auto output_tmp = args->pf->GetOutput();
  auto input_tmp  = args->pf->GetInput();
  vtkStructuredGrid* output = dynamic_cast<vtkStructuredGrid*>(output_tmp);
  vtkStructuredGrid* input  = dynamic_cast<vtkStructuredGrid*>(input_tmp);
  output->ShallowCopy(input);
  output->SetExtent(args->local_extent);
}

void vtkParallelWriter(int argc, char *argv[],std::vector<double*>colors, std::vector<char*> names, int LX, int LY, int LZ, double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, double local_origin_x, double local_origin_y, double local_origin_z, int nn, int timesnapshot) {
  int global_extent[6] = {x_min, x_max, y_min, y_max, z_min, z_max};
  bool flagX, flagY, flagZ;
  int new_local_origin_x;
  int new_local_origin_y;
  int new_local_origin_z;
  flagX = false;
  flagY = false;
  flagZ = false;
  if (local_origin_x == 0) {
    flagX = true;
  }
  if (local_origin_y == 0) {
        flagY = true;
  }
  if (local_origin_z == 0) {
        flagZ = true;
  }
  if (flagX == false) {
    new_local_origin_x = local_origin_x - 1;
  }
  if (flagY == false) {
    new_local_origin_y = local_origin_y - 1;
  }
  if (flagZ == false) {
    new_local_origin_z = local_origin_z - 1;
  }
  if (flagX == true) {
    new_local_origin_x = local_origin_x;
  }
  if (flagY == true) {
    new_local_origin_y = local_origin_y;
  }
  if (flagZ == true) {
    new_local_origin_z = local_origin_z;
  }
  int local_extent[6] = {new_local_origin_x, local_origin_x+LX-1, new_local_origin_y, local_origin_y+LY-1, new_local_origin_z, local_origin_z+LZ-1};
  int dims[3] = {local_origin_x+LX-new_local_origin_x, local_origin_y+LY-new_local_origin_y, local_origin_z+LZ-new_local_origin_z};

  // Create and Initialize vtkMPIController
  auto contr = vtkSmartPointer<vtkMPIController>::New();
  if (timesnapshot == 0) {
  contr->Initialize(&argc, &argv, 1);
  }
  int nranks = contr->GetNumberOfProcesses();
  int rank   = contr->GetLocalProcessId();

  // Create grid points, allocate memory and Insert them
  auto points = vtkSmartPointer<vtkPoints>::New();
  for (int k=0; k<dims[2]; ++k) {
    for (int j=0; j<dims[1]; ++j) {
      for (int i=0; i<dims[0]; ++i) {
        points->InsertNextPoint(new_local_origin_x+i, new_local_origin_y+j, new_local_origin_z+k);

   }
   }
   }

  // Create a density field. Note that the number of cells is always less than
  // number of grid points by an amount of one so we use dims[i]-1
  std::vector<vtkSmartPointer<vtkDoubleArray>> vtkColors;
  for (int iterator = 0; iterator < colors.size(); iterator++) {
  auto tempColor = vtkSmartPointer<vtkDoubleArray>::New();
  tempColor->SetNumberOfComponents(1);
  tempColor->SetName(names[iterator]);
  int LXP = nn+LX+nn;
  int LYP = nn+LY+nn;
  int LZP = nn+LZ+nn;
  for (int k=0; k<dims[2]; ++k) {
        int K;
        if (flagZ == true) {
    K = k + nn;
        }
        if (flagZ == false) {
        K = k;
        }
    for (int j=0; j<dims[1]; ++j) {
                int J;
                if (flagY == true) {
        J = j + nn;
                }
                if (flagY == false) {
                J = j;
                }
      for (int i=0; i<dims[0]; ++i) {
            int I;
            if (flagX == true) {
            I = i + nn;
            }
            if (flagX == false) {
            I = i;
            }
                int IDflattened = I+J*LXP+K*LXP*LYP;    
        tempColor->InsertNextTuple1(colors[iterator][IDflattened]);
      }
      }
      }
  vtkColors.push_back(tempColor);
  }

  // Create a vtkProgrammableFilter
  auto pf = vtkSmartPointer<vtkProgrammableFilter>::New();

  // Initialize an instance of Args
  Args args;
  args.pf = pf;
  for(int i=0; i<6; ++i) args.local_extent[i] = local_extent[i];

  pf->SetExecuteMethod(execute, &args);

  // Create a structured grid and assign point data and cell data to it
  auto structuredGrid = vtkSmartPointer<vtkStructuredGrid>::New();
  structuredGrid->SetExtent(global_extent);
  pf->SetInputData(structuredGrid);
  structuredGrid->SetPoints(points);
  for (int iterator = 0; iterator < vtkColors.size(); iterator++) {
  structuredGrid->GetPointData()->AddArray(vtkColors[iterator]);
  }

  std::string fileName = std::string("./out/output_") + std::to_string(timesnapshot) + ".pvts";

  // Create the parallel writer and call some functions
  auto parallel_writer = vtkSmartPointer<vtkXMLPStructuredGridWriter>::New();
  parallel_writer->SetInputConnection(pf->GetOutputPort());
  parallel_writer->SetController(contr);
  parallel_writer->SetFileName(fileName.c_str());
  parallel_writer->SetNumberOfPieces(nranks);
  parallel_writer->SetStartPiece(rank);
  parallel_writer->SetEndPiece(rank);
  parallel_writer->SetDataModeToBinary();
  parallel_writer->Update();
  parallel_writer->Write();

}

此示例适用于 3D 结构化网格,但如果您设置LZ为 1 和z_min0 z_max,它也应该适用于 2D 结构化网格。您基本上将color2D 中的变量展平以存储在由定义的一维数组中double*,如果您有其中一些,则将它们中的每一个存储在数组向量中,以基本上将其提供给std::vector<double*>colors. 此代码已针对使用启用 MPI 构建的 VTK 8.9 的 3D 结构化网格进行测试和验证。

替代方法是让每个 MPI 进程确定它要写入的字节数,使用 MPI_Allgather 分发此信息,并使用 MPI_File_open、MPI_File_seek、MPI_File_write 和 MPI_File_close 使用此信息并行写入一个大文件.

不过,请提前考虑如何阅读此文件。设计一种独立于分区的文件格式是有意义的,因为它可以被与原始模拟大小无关的任意数量的进程读取。如果可能,将必要的标头信息放在开头,并避免让每个进程将自己的标头放在其数据的前面。然后,您可以在许多进程使用独立读取之前读取一个级别的标题并 MPI_Bcast 它。

对于 VTK 的特殊情况,每个 MPI 进程编写一个文件可能并不总是可行的。曾经接到超算中心的电话,因为我不小心每秒写了 32768 个 VTU 文件。