Boost.MPI: Получено не то, что было отправлено! - PullRequest
3 голосов
/ 26 октября 2010

Я относительно новичок в использовании Boost MPI.У меня установлены библиотеки, код компилируется, но я получаю очень странную ошибку - некоторые целочисленные данные, полученные подчиненными узлами, не совпадают с отправленными мастером.Что происходит?

Я использую версию Boost 1.42.0, компилирую код, используя mpic ++ (который оборачивает g ++ в одном кластере и icpc в другом).Ниже приведен сокращенный пример, включая вывод.

Код:

#include <iostream>
#include <boost/mpi.hpp>

using namespace std;
namespace mpi = boost::mpi;

class Solution
{
public:
  Solution() :
  solution_num(num_solutions++)
  {
    // Master node's constructor
  }

  Solution(int solutionNum) :
  solution_num(solutionNum)
  {
    // Slave nodes' constructor.
  }

  int solutionNum() const
  {
    return solution_num;
  }

private:
  static int num_solutions;
  int solution_num;
};

int Solution::num_solutions = 0;

int main(int argc, char* argv[])
{
  // Initialization of MPI
  mpi::environment env(argc, argv);
  mpi::communicator world;

  if (world.rank() == 0)
  {
    // Create solutions
    int numSolutions = world.size() - 1;  // One solution per slave
    vector<Solution*> solutions(numSolutions);
    for (int sol = 0; sol < numSolutions; ++sol)
    {
      solutions[sol] = new Solution;
    }

    // Send solutions
    for (int sol = 0; sol < numSolutions; ++sol)
    {
      world.isend(sol + 1, 0, false);  // Tells the slave to expect work
      cout << "Sending solution no. " << solutions[sol]->solutionNum() << " to node " << sol + 1 << endl;
      world.isend(sol + 1, 1, solutions[sol]->solutionNum());
    }

    // Retrieve values (solution numbers squared)
    vector<double> values(numSolutions, 0);
    for (int i = 0; i < numSolutions; ++i)
    {
      // Get values for each solution
      double value = 0;
      mpi::status status = world.recv(mpi::any_source, 2, value);
      int source = status.source();

      int sol = source - 1;
      values[sol] = value;
    }
    for (int i = 1; i <= numSolutions; ++i)
    {
      world.isend(i, 0, true);  // Tells the slave to finish
    }

    // Output the solutions numbers and their squares
    for (int i = 0; i < numSolutions; ++i)
    {
      cout << solutions[i]->solutionNum() << ", " << values[i] << endl;
      delete solutions[i];
    }
  }
  else
  {
    // Slave nodes merely square the solution number
    bool finished;
    mpi::status status = world.recv(0, 0, finished);
    while (!finished)
    {
      int solNum;
      world.recv(0, 1, solNum);
      cout << "Node " << world.rank() << " receiving solution no. " << solNum << endl;

      Solution solution(solNum);
      double value = static_cast<double>(solNum * solNum);
      world.send(0, 2, value);

      status = world.recv(0, 0, finished);
    }

    cout << "Node " << world.rank() << " finished." << endl;
  }

  return EXIT_SUCCESS;
}

Запуск этого на 21 узле (1 ведущий, 20 ведомых) приводит к:

Sending solution no. 0 to node 1
Sending solution no. 1 to node 2
Sending solution no. 2 to node 3
Sending solution no. 3 to node 4
Sending solution no. 4 to node 5
Sending solution no. 5 to node 6
Sending solution no. 6 to node 7
Sending solution no. 7 to node 8
Sending solution no. 8 to node 9
Sending solution no. 9 to node 10
Sending solution no. 10 to node 11
Sending solution no. 11 to node 12
Sending solution no. 12 to node 13
Sending solution no. 13 to node 14
Sending solution no. 14 to node 15
Sending solution no. 15 to node 16
Sending solution no. 16 to node 17
Sending solution no. 17 to node 18
Sending solution no. 18 to node 19
Sending solution no. 19 to node 20
Node 1 receiving solution no. 0
Node 2 receiving solution no. 1
Node 12 receiving solution no. 19
Node 3 receiving solution no. 19
Node 15 receiving solution no. 19
Node 13 receiving solution no. 19
Node 4 receiving solution no. 19
Node 9 receiving solution no. 19
Node 10 receiving solution no. 19
Node 14 receiving solution no. 19
Node 6 receiving solution no. 19
Node 5 receiving solution no. 19
Node 11 receiving solution no. 19
Node 8 receiving solution no. 19
Node 16 receiving solution no. 19
Node 19 receiving solution no. 19
Node 20 receiving solution no. 19
Node 1 finished.
Node 2 finished.
Node 7 receiving solution no. 19
0, 0
1, 1
2, 361
3, 361
4, 361
5, 361
6, 361
7, 361
8, 361
9, 361
10, 361
11, 361
12, 361
13, 361
14, 361
15, 361
16, 361
17, 361
18, 361
19, 361
Node 6 finished.
Node 3 finished.
Node 17 receiving solution no. 19
Node 17 finished.
Node 10 finished.
Node 12 finished.
Node 8 finished.
Node 4 finished.
Node 15 finished.
Node 18 receiving solution no. 19
Node 18 finished.
Node 11 finished.
Node 13 finished.
Node 20 finished.
Node 16 finished.
Node 9 finished.
Node 19 finished.
Node 7 finished.
Node 5 finished.
Node 14 finished.

Итакв то время как мастер отправляет 0 на узел 1, 1 на узел 2, 2 на узел 3 и т. д., большинство подчиненных узлов (по какой-то причине) получают число 19. Поэтому вместо того, чтобы производить квадраты чисел от 0 до 19, мыполучите 0 в квадрате, 1 в квадрате и 19 в квадрате 18 раз!

Заранее спасибо всем, кто может это объяснить.

Алан

Ответы [ 4 ]

11 голосов
/ 27 октября 2010

Хорошо, я думаю, что у меня есть ответ, который требует некоторых знаний о базовых вызовах MPI в стиле C.Функция Boost 'isend' по сути является оберткой вокруг MPI_Isend, и она не защищает пользователя от необходимости знать некоторые подробности о том, как работает MPI_Isend.

Один из параметров MPI_Isend - этоуказатель на буфер, который содержит информацию, которую вы хотите отправить.Однако важно отметить, что этот буфер НЕ МОЖЕТ использоваться повторно, пока вы не узнаете, что сообщение было получено.Итак, рассмотрим следующий код:

// Get solution numbers from the solutions and store in a vector
vector<int> solutionNums(numSolutions);
for (int sol = 0; sol < numSolutions; ++sol)
{
  solutionNums[sol] = solutions[sol]->solutionNum();
}

// Send solution numbers
for (int sol = 0; sol < numSolutions; ++sol)
{
  world.isend(sol + 1, 0, false);  // Indicates that we have not finished, and to expect a solution representation
  cout << "Sending solution no. " << solutionNums[sol] << " to node " << sol + 1 << endl;
  world.isend(sol + 1, 1, solutionNums[sol]);
}

Это прекрасно работает, так как каждый номер решения находится в своем собственном месте в памяти.Теперь рассмотрим следующую незначительную корректировку:

// Create solutionNum array
vector<int> solutionNums(numSolutions);
for (int sol = 0; sol < numSolutions; ++sol)
{
  solutionNums[sol] = solutions[sol]->solutionNum();
}

// Send solutions
for (int sol = 0; sol < numSolutions; ++sol)
{
  int solNum = solutionNums[sol];
  world.isend(sol + 1, 0, false);  // Indicates that we have not finished, and to expect a solution representation
  cout << "Sending solution no. " << solNum << " to node " << sol + 1 << endl;
  world.isend(sol + 1, 1, solNum);
}

Теперь базовый вызов MPI_Isend предоставляется с указателем на solNum.К сожалению, этот бит памяти перезаписывается каждый раз в цикле, поэтому, хотя может показаться, что число 4 отправлено на узел 5, к моменту фактической отправки новое содержимое этой ячейки памяти (например, 19)вместо этого передаются.

Теперь рассмотрим исходный код:

// Send solutions
for (int sol = 0; sol < numSolutions; ++sol)
{
  world.isend(sol + 1, 0, false);  // Tells the slave to expect work
  cout << "Sending solution no. " << solutions[sol]->solutionNum() << " to node " << sol + 1 << endl;
  world.isend(sol + 1, 1, solutions[sol]->solutionNum());
}

Здесь мы передаем временный код.Снова, местоположение этого временного в памяти перезаписывается каждый раз во время цикла.Опять же, неверные данные отправляются на подчиненные узлы.

Как это случилось, я смог реструктурировать свой «реальный» код, чтобы использовать «send» вместо «isend».Однако, если мне понадобится использовать isend в будущем, я буду немного осторожнее!

4 голосов
/ 08 апреля 2011

Я думаю, что наткнулся на подобную проблему сегодня.При сериализации пользовательского типа данных я заметил, что он (иногда) был поврежден с другой стороны.Исправление состояло в том, чтобы сохранить mpi::request возвращаемое значение isend.Если вы посмотрите на communicator::isend_impl(int dest, int tag, const T& value, mpl::false_) в communicator.hpp повышения, вы увидите, что сериализованные данные помещаются в запрос как общий указатель.Если он будет удален снова, данные будут признаны недействительными, и может произойти что-либо.

2 голосов
/ 26 октября 2010

Ваш компилятор оптимизировал все ваши "решения [sol] = новое решение;"цикл и пришел к выводу, что он может перейти к концу всех приращений num_solution ++.Это, конечно, неправильно, но это то, что произошло.

Возможно, хотя и очень маловероятно, что автоматический многопоточный или автоматически распараллеливающий компилятор вызвал появление 20 экземпляров numsolutions ++ в полуслучайном порядкесоблюдайте 20 экземпляров solution_num = num_solutions в списке ctor для Solutions ().Гораздо более вероятно, что оптимизация прошла ужасно неправильно.

Замените

for (int sol = 0; sol < numSolutions; ++sol)
    {
      solutions[sol] = new Solution;
    }

на

for (int sol = 0; sol < numSolutions; ++sol)
    {
      solutions[sol] = new Solution(sol);
    }

, и ваша проблема исчезнет.В частности, каждое Решение получит свой собственный номер вместо того, чтобы получать какое-либо число, которое совместно используемая статическая имеет некоторое время во время неправильного переупорядочения компилятора из 20 приращений.

1 голос
/ 22 ноября 2011

Основываясь на ответе milianw: у меня сложилось впечатление, что правильным способом использования isend является сохранение объекта запроса, который он возвращает, и проверка его завершения с использованием методов test () или wait () перед другим вызовом isend.Я думаю, что это также будет работать, чтобы продолжать вызывать isend () и помещать объекты запроса в вектор.Затем вы можете проверить или дождаться этих запросов с помощью {test, wait} _ {any, some, all}.

В какой-то момент вам также нужно беспокоиться о том, отправляете ли вы сообщения быстрее, чем получатель может получить, потому что рано или поздно у вас закончатся буферы MPI.По моему опыту, это просто проявится как сбой.

...