MPI BCast (трансляция) std:: вектор структур


У меня есть вопрос относительно прохождения std::вектора структур через MPI.

Во-первых, детали. Я использую OpenMPI 1.4.3 (MPI-2 совместимый) с gcc. Обратите внимание, что я не могу использовать boost MPI или OOMPI-я обязан использовать эту версию.

У меня есть структура для агрегирования некоторых данных:

  struct Delta {
    Delta() : dX(0.0), dY(0.0), dZ(0.0) {};
    Delta(double dx, double dy, double dz) :
      dX(dx), dY(dy), dZ(dz) {};
    Delta(const Delta& rhs) :
      dX(rhs.dX), dY(rhs.dY), dZ(rhs.dZ) {};

    double dX;
    double dY;
    double dZ;
  };

  typedef std::vector<Delta> DeltaLine;

И у меня есть DeltaLine, что я хотел бы передать, через MPI, для всех узлов.

Могу ли я сделать следующее безопасно и переносимо? Это работает для меня в моем тестовом случае. Я просто хотите убедиться, что это законно и кошерно на разных платформах и в соответствии со стандартами C++ и MPI.

Спасибо! Мадлен.

  //Create an MPI struct for the Delta class
  const int    nItems=3;
  int          blocklengths[nItems] = {1, 1, 1};
  MPI_Datatype types[nItems] = {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE};
  MPI_Datatype MPI_DeltaType;
  MPI_Aint     offsets[nItems];

  offsets[0] = offsetof(Delta, dX);
  offsets[1] = offsetof(Delta, dY);
  offsets[2] = offsetof(Delta, dZ);

  MPI_Type_create_struct(nItems, blocklengths, offsets, types, &MPI_DeltaType);
  MPI_Type_commit(&MPI_DeltaType);

  //This is the vector to be filled, and its size
  DeltaLine deltaLine;
  unsigned deltaLineSize;

  //If this is the master proc, get the DeltaLine and its size
  if(amMaster()) {
    deltaLine = getMasterDeltaLine();
    deltaLineSize = deltaLine.size();
  }

  //Send out the correct size
  MPI_Bcast(&deltaLineSize, 1, MPI_UNSIGNED, COMM_PROC, MPI_COMM_WORLD);

  //Size the delta line vector, and broadcast its contents
  deltaLine.reserve(deltaLineSize);
  MPI_Bcast(&deltaLine.front(), deltaLineSize, MPI_DeltaType, COMM_PROC, MPI_COMM_WORLD);

  //Free up the type
  MPI_Type_free(&MPI_DeltaType);
2 3

2 ответа:

Стандарт C++ гарантирует, что элементы std::vector хранятся последовательно в памяти и что std::vector::reserve() (re-)выделяет память, если это необходимо во время вызова, поэтому ваше решение совершенно корректно с точки зрения управления памятью. Хотя, как отметил Солкар, std::vector::reserve() только резервирует пространство памяти, но векторный объект не знает, что в этой памяти есть данные, записываемые непосредственно, и поэтому сохраняет предыдущее число элементов (ноль для вновь созданных векторов). Это может быть исправлено вызовом std::vector::resize() перед второй операцией широковещания.

Один комментарий, хотя это относится ко всем случаям, когда построенные типы данных MPI используются для отправки массивов - вы должны позаботиться о возможном заполнении между последовательными элементами массива. Другими словами, возможно следующее удержание из-за возможного заполнения в конце struct:

(char*)&deltaLine[1] - (char*)&deltaLine[0] != mpi_extentof(MPI_DeltaType)

, где mpi_extentof - экстент типа данных MPI, возвращаемый MPI_Type_get_extent(). Потому что пил использует такой степени, чтобы определить там, где начинается каждый элемент массива, рекомендуется явно задать его для любого типа структуры, который используется для отправки более одного элемента. С MPI-1 это обычно делается путем добавления одного специального структурного элемента псевдотипа MPI_UB, но в современном MPI-коде (или в MPI-2 вообще) для этой цели следует использовать MPI_Type_create_resized:

//Create an MPI struct for the Delta class
const int    nItems=3;
int          blocklengths[nItems] = {1, 1, 1};
MPI_Datatype types[nItems] = {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE};
MPI_Datatype MPI_DeltaType_proto, MPI_DeltaType;
MPI_Aint     offsets[nItems];

offsets[0] = offsetof(Delta, dX);
offsets[1] = offsetof(Delta, dY);
offsets[2] = offsetof(Delta, dZ);

MPI_Type_create_struct(nItems, blocklengths, offsets, types, &MPI_DeltaType_proto);

// Resize the type so that its length matches the actual structure length

// Get the constructed type lower bound and extent
MPI_Aint lb, extent;
MPI_Type_get_extent(MPI_DeltaType_proto, &lb, &extent);

// Get the actual distance between to vector elements
// (this might not be the best way to do it - if so, substitute a better one)
extent = (char*)&deltaLine[1] - (char*)&deltaLine[0];

// Create a resized type whose extent matches the actual distance
MPI_Type_create_resized(MPI_DeltaType_proto, lb, extent, &MPI_DeltaType);
MPI_Type_commit(&MPI_DeltaType);
В вашем случае в структуре есть только элементы double, и никакого заполнения не ожидается, поэтому делать все это не нужно. Но имейте это в виду для вашей будущей работы с MPI.

std::vector::reserve(N) не влияет на size, но (если вообще влияет) capacity (и, возможно, местоположение), поэтому для принимающих контейнеров deltaLine все равно будет вектор нулевого размера, независимо от его capacity равного deltaLineSize.

Это еще не проблема в коде как есть, но я предполагаю, что вы намерены выполнить некоторую обработку, используя полученные данные.

Я бы также проверил возвращаемое значение (по крайней мере) первого MPI_BCast, потому что если это по какой-либо причине не удается на одном процессе, размер вектора будет равен 0, и если он реагирует на вторую широковещательную передачу, границы которой будут нарушены.