Boost.MPI: Получено не то, что было отправлено!
Я относительно новичок в использовании 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 ответа
Хорошо, я думаю, что у меня есть ответ, который требует некоторых знаний о базовых вызовах 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 в будущем, я буду немного осторожнее!
Я думаю, что наткнулся на подобную проблему сегодня. При сериализации пользовательского типа данных я заметил, что он (иногда) был поврежден с другой стороны. Исправление было хранить mpi::request
возвращаемое значение isend
, Если вы посмотрите на communicator::isend_impl(int dest, int tag, const T& value, mpl::false_)
в communicator.hpp
Кроме того, вы увидите, что сериализованные данные помещаются в запрос как общий указатель. Если он будет удален снова, данные будут признаны недействительными и что-то может произойти.
Итак: всегда сохраняйте возвращаемые значения isend!
Ваш компилятор оптимизировал все ваши "решения [sol] = новое решение;" Цикл и пришел к выводу, что он может перейти к концу всех приращений num_solution++. Это, конечно, неправильно, но так и случилось.
Возможно, хотя и очень маловероятно, что автоматически созданный многопоточность или автоматически распараллеливающий компилятор вызвал появление 20 экземпляров numsolutions++ в полуслучайном порядке по отношению к 20 экземплярам solution_num = num_solutions в списке ctor в Solutions(). Скорее всего, оптимизация пошла не так, как надо.
замещать
для (int sol = 0; solс
для (int sol = 0; solи ваша проблема исчезнет. В частности, каждое Решение получит свой собственный номер вместо того, чтобы получать какое-либо число, которое совместно используемая статическая имеет некоторое время во время неправильного переупорядочения компилятора из 20 приращений.
Основываясь на ответе milianw: у меня сложилось впечатление, что правильным способом использования isend является сохранение объекта запроса, который он возвращает, и проверка его завершения с использованием методов test() или wait() перед очередным вызовом isend. Я думаю, что это также будет работать, чтобы продолжать вызывать isend() и помещать объекты запроса в вектор. Затем вы можете проверить или дождаться этих запросов с помощью {test,wait}_{any,some,all}.
В какой-то момент вам также нужно беспокоиться о том, отправляете ли вы сообщения быстрее, чем получатель может получить, потому что рано или поздно у вас закончатся буферы MPI. По моему опыту, это просто проявится как крах.