2010-10-26 2 views
3

Я относительно новичок в использовании 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 раз!

Заранее благодарим любого, кто может это объяснить.

Алан

ответ

2

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

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

Заменить

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

с

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

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

+0

Не повезло, я боюсь. Номера решений верны на главном узле - они просто не отправляются на ведомые устройства правильно. Хотя по-прежнему может быть какая-то оптимизация, ошибочно - извлечение номеров решений в вектор перед отправкой их на подчиненные устройства, похоже, работает в этом простом коде, но не в моем «реальном» коде. –

11

Хорошо, я думаю, у меня есть ответ, который требует некоторых знаний о базовых вызовах MPI в стиле C. Функция «isend» Boost по существу является оберткой вокруг «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» в будущем, я буду немного осторожнее!

+1

Я действительно задаюсь вопросом, следует ли повысить эффективность «isend», чтобы повысить безопасность пользователя от некоторых из этих проблем, если он нацелен на то, чтобы быть более дружественным интерфейсом к MPI. Думаю, это, вероятно, обычный баланс между эффективностью и безопасностью? –

+1

+1 для документирования ответа для потомков – Gorgen

4

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

так: всегда сохранять возвращенные значения isend!

1

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

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

Смежные вопросы