【问题标题】:How to send nested structures in MPI using MPI_Send如何使用 MPI_Send 在 MPI 中发送嵌套结构
【发布时间】:2015-09-09 13:37:05
【问题描述】:

我需要使用 MPI_SEND 发送 plista 数组:

Traza* plista;

    struct Evento{
    char* evento;
    unsigned char cant;
    };

    struct Traza
    {
            char* nombre;
            Evento* eventos;
        unsigned int cantEventos;
            bool revisado;
            unsigned int idTraza;
    };

我读了一些关于人们使用 MPI_PACKED 的文章,但这是一个复杂的结构。

【问题讨论】:

  • 您不能将其作为单个结构发送。您必须分别发送两个结构。检查stackoverflow.com/questions/5972018/…
  • 我尝试了那个选项,但在那篇文章中只使用了一个独特的结构,我使用了 Traza 的结构数组。此外,我不知道它到底是如何工作的,因为在帖子中使用 sizeof 在结构上发送,我发送了 Evento 类型的结构列表
  • 由于您有一个元素数组,您将不得不对其进行迭代并作为循环发送。用户建议使用 sizeof(),因为该结构被序列化为字节并发送。您的另一个选择是声明您自己的MPI_Datatype,然后发送结构。
  • 谢谢 Pooja Nilangekar,我读过一些关于 MPI_Datatype 的文章,但我不清楚如何去做,你能帮我用这个复杂的结构来做吗,因为我是 MPI 的新手,谢谢。

标签: c++ mpi


【解决方案1】:

您要发送的数据结构很复杂,因为它包含指向子结构的指针。传递指针显然没有意义,相反,您必须以深拷贝的方式求助于子结构。此外,一些子结构包含动态长度的数组,只有发送者事先知道,所以接收者无法分配正确的内存量。

您可能想创建自己的复合 MPI 数据类型,实际上可以通过使用MPI_BOTTOM 并对数据结构的所有子部分的绝对地址进行编码来实现。每次要进行通信时,您都需要重新创建此自定义数据类型,因为您可能使用不同的数据,因此绝对地址会发生变化。然而,这只适用于发送方,因为在接收方你仍然不知道为数据结构的每个部分分配多少内存。

解决此问题的一种方法是进行多次通信,先发送大小,然后分别发送数据。但是,这样做的缺点是会增加通信次数,并且会引入不必要的延迟。

输入您已经提到的MPI_PACKEDMPI_PACKED 允许您将要一次发送的数据打包到缓冲区中,然后在一次通信中发送整个缓冲区。在接收端,可以单次通信接收,然后逐段解包。

以下是使用MPI_PACKED 的数据结构解决方案。

注意:我对拉丁语言几乎一无所知,所以我不得不猜测你的一些标识符的含义。即我解释了

  • cant 作为某种元素计数
  • Evento::evento 是恰好适合char 的数字数组,数组的长度在Evento::cant 中。
  • Traza::nombre 作为数字数组,Traza::eventos 作为事件数组,两个数组的长度由 Traza::cantEventos 给出

纠正这些不正确的解释应该很容易(只需从正确的来源获取数组的大小,然后按照我在消息中打包Traza 的数量的方式分别打包它们)。

#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>

#include <mpi.h>

struct Evento {
  char* evento;
  unsigned char cant;
};

struct Traza
{
  char* nombre;
  Evento* eventos;
  unsigned int cantEventos;
  bool revisado;
  unsigned int idTraza;
};

void pack_plista(int incount, Traza* data, MPI_Comm comm,
                 std::vector<char> &buf)
{
  int pos = 0;
  buf.clear();
  int size;

  MPI_Pack_size(1, MPI_INT, comm, &size);
  buf.resize(pos+size);
  MPI_Pack(&incount, 1, MPI_INT, buf.data(), buf.size(), &pos, comm);

  for(int t = 0; t < incount; ++t)
  {
    MPI_Pack_size(2, MPI_UNSIGNED, comm, &size);
    buf.resize(pos+size);
    MPI_Pack(&data[t].cantEventos, 1, MPI_UNSIGNED,
             buf.data(), buf.size(), &pos, comm);
    MPI_Pack(&data[t].idTraza, 1, MPI_UNSIGNED,
             buf.data(), buf.size(), &pos, comm);

    MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
    buf.resize(pos+size);
    { // MPI does not know about C++ bool
      unsigned char revisado = data[t].revisado;
      MPI_Pack(&revisado, 1, MPI_UNSIGNED_CHAR,
               buf.data(), buf.size(), &pos, comm);
    }

    // This interprets Traza::nombre as a character code, which is probably incorrect
    // However, that is unlikely to be a problem, unless you are in a
    // heterogeneous ASCII/EBCDIC environment
    MPI_Pack_size(data[t].cantEventos, MPI_CHAR, comm, &size);
    buf.resize(pos+size);
    MPI_Pack(data[t].nombre, data[t].cantEventos, MPI_CHAR,
             buf.data(), buf.size(), &pos, comm);

    for(unsigned int e = 0; e < data[t].cantEventos; ++e)
    {
      // send count (interpret as a number)
      MPI_Pack_size(1, MPI_UNSIGNED_CHAR, comm, &size);
      buf.resize(pos+size);
      MPI_Pack(&data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR,
               buf.data(), buf.size(), &pos, comm);

      // send events (interpret as character codes)
      MPI_Pack_size(data[t].eventos[e].cant, MPI_CHAR, comm, &size);
      buf.resize(pos+size);
      MPI_Pack(data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
               buf.data(), buf.size(), &pos, comm);
    }
  }
  buf.resize(pos);
}

void unpack_plista(int &outcount, Traza* &data, MPI_Comm comm,
                   // buf cannot be reference-to-const since MPI_Unpack takes
                   // pointer-to-nonconst
                   std::vector<char> &buf)
{
  int pos = 0;

  MPI_Unpack(buf.data(), buf.size(), &pos, &outcount, 1, MPI_INT, comm);

  data = new Traza[outcount];
  for(int t = 0; t < outcount; ++t)
  {
    MPI_Unpack(buf.data(), buf.size(), &pos,
               &data[t].cantEventos, 1, MPI_UNSIGNED, comm);
    MPI_Unpack(buf.data(), buf.size(), &pos,
               &data[t].idTraza, 1, MPI_UNSIGNED, comm);

    { // MPI does not know about C++ bool
      unsigned char revisado;
      MPI_Unpack(buf.data(), buf.size(), &pos,
                 &revisado, 1, MPI_UNSIGNED_CHAR, comm);
      data[t].revisado = revisado;
    }

    // This interprets Traza::nombre as a character code, which is probably incorrect
    // However, that is unlikely to be a problem, unless you are in a
    // heterogeneous ASCII/EBCDIC environment
    data[t].nombre = new char[data[t].cantEventos];
    MPI_Unpack(buf.data(), buf.size(), &pos,
               data[t].nombre, data[t].cantEventos, MPI_CHAR, comm);

    data[t].eventos = new Evento[data[t].cantEventos];
    for(unsigned int e = 0; e < data[t].cantEventos; ++e)
    {
      // receive count (interpret as a number)
      MPI_Unpack(buf.data(), buf.size(), &pos,
                 &data[t].eventos[e].cant, 1, MPI_UNSIGNED_CHAR, comm);

      // receive events (interpret as character codes)
      data[t].eventos[e].evento = new char[data[t].eventos[e].cant];
      MPI_Unpack(buf.data(), buf.size(), &pos,
                 data[t].eventos[e].evento, data[t].eventos[e].cant, MPI_CHAR,
                 comm);
    }
  }
}

void send_plista(int incount, Traza* data, int dest, int tag, MPI_Comm comm)
{
  std::vector<char> buf;
  pack_plista(incount, data, comm, buf);
  MPI_Send(buf.data(), buf.size(), MPI_PACKED, dest, tag, comm);
}

void recv_plista(int &outcount, Traza* &data,
                 int src, int tag, MPI_Comm comm)
{
  MPI_Status status;
  MPI_Probe(src, tag, comm, &status);
  int size;
  MPI_Get_count(&status, MPI_PACKED, &size);
  std::vector<char> buf(size);
  MPI_Recv(buf.data(), buf.size(), MPI_PACKED, src, tag, comm, &status);

  unpack_plista(outcount, data, comm, buf);
}

void make_test_data(int &count, Traza *&data) {
  count = 2;
  data = new Traza[2] {
    {
      new char[3] { char(0), char(1), char(3) },
      new Evento[3] {
        {
          new char[4] { 'a', 'b', 'c', 'd' },
          (unsigned char)4,
        },
        {
          new char[3] { 'e', 'f', 'g' },
          (unsigned char)3,
        },
        {
          new char[2] { 'h', 'i' },
          (unsigned char)2,
        },
      },
      3u,
      true,
      0u,
    },
    {
      new char[1] { char(4) },
      new Evento[1] {
        {
          new char[1] { 'j' },
          (unsigned char)1,
        },
      },
      1u,
      false,
      1u,
    },
  };
}

void print_data(std::ostream &out, int count, const Traza *data)
{
  for(int t = 0; t < count; ++t)
  {
    std::cout << "{\n"
              << "  nombre = { ";
    for(unsigned e = 0; e < data[t].cantEventos; ++e)
      std::cout << int(data[t].nombre[e]) << ", ";
    std::cout << "},\n"
              << "  eventos = {\n";
    for(unsigned e = 0; e < data[t].cantEventos; ++e)
    {
      std::cout << "    {\n"
                << "      evento = { ";
      for(int c = 0; c < data[t].eventos[e].cant; ++c)
        std::cout << "'" << data[t].eventos[e].evento[c] << "', ";
      std::cout << "},\n"
                << "      cant = " << int(data[t].eventos[e].cant) << ",\n"
                << "    },\n";
    }
    std::cout << "  },\n"
              << "  cantEventos = " << data[t].cantEventos << ",\n"
              << "  revisado = " << data[t].revisado << ",\n"
              << "  idTraza = " << data[t].idTraza << ",\n"
              << "}," << std::endl;
  }
}

int main(int argc, char **argv)
{
  int rank;
  Traza *plista = nullptr;
  int count = 0;

  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  if(rank == 1) {
    make_test_data(count, plista);
    send_plista(count, plista, 0, 0, MPI_COMM_WORLD);
  }
  else if(rank == 0) {
    recv_plista(count, plista, 1, 0, MPI_COMM_WORLD);

    std::cout << std::boolalpha;
    print_data(std::cout, count, plista);
  }
  // don't bother freeing allocated memory, the process is ending anyway
  MPI_Finalize();
}

示例运行:

-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Wed Sep  9 20:04:24

set -ex; mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc; mpirun -n 2 ./check
+ mpic++ -std=c++11 -Wall -Wno-literal-suffix -o check check.cc
+ mpirun -n 2 ./check
{
  nombre = { 0, 1, 3, },
  eventos = {
    {
      evento = { 'a', 'b', 'c', 'd', },
      cant = 4,
    },
    {
      evento = { 'e', 'f', 'g', },
      cant = 3,
    },
    {
      evento = { 'h', 'i', },
      cant = 2,
    },
  },
  cantEventos = 3,
  revisado = true,
  idTraza = 0,
},
{
  nombre = { 4, },
  eventos = {
    {
      evento = { 'j', },
      cant = 1,
    },
  },
  cantEventos = 1,
  revisado = false,
  idTraza = 1,
},

Compilation finished at Wed Sep  9 20:04:26

【讨论】:

  • 谢谢前巴特,我会尝试你的建议,非常感谢你花时间在我身上,非常感谢。
猜你喜欢
  • 2011-08-23
  • 2011-08-23
  • 1970-01-01
  • 2011-08-23
  • 1970-01-01
  • 2014-12-20
  • 2015-11-19
  • 2021-06-11
  • 1970-01-01
相关资源
最近更新 更多