3

私は Boost MPI の使用に比較的慣れていません。ライブラリをインストールし、コードをコンパイルしましたが、非常に奇妙なエラーが発生しました - スレーブ ノードによって受信された一部の整数データが​​、マスターによって送信されたものではありません。何が起こっている?

私はブースト バージョン 1.42.0 を使用しており、mpic++ を使用してコードをコンパイルしています (これは、1 つのクラスターで g++ をラップし、もう 1 つのクラスターで 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.

そのため、マスターがノード 1 に 0、ノード 2 に 1、ノード 3 に 2 などを送信している間、ほとんどのスレーブ ノードは (何らかの理由で) 数値 19 を受信します。 0 の 2 乗、1 の 2 乗、19 の 2 乗を 18 回取得します。

これを説明できる人に事前に感謝します。

アラン

4

4 に答える 4

10

わかりました、私は答えを持っていると思います。それには、基礎となる C スタイルの MPI 呼び出しに関する知識が必要です。Boost の「isend」関数は、本質的に「MPI_Isend」のラッパーであり、「MPI_Isend」の動作に関する詳細を知る必要からユーザーを保護するものではありません。

「MPI_Isend」の 1 つのパラメーターは、送信する情報を含むバッファーへのポインターです。ただし、重要なのは、メッセージが受信されたことを知るまで、このバッファを再利用できないことです。したがって、次のコードを検討してください。

// 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());
}

ここでは一時的なものを渡しています。繰り返しますが、メモリ内のこの一時的な場所は、ループのたびに上書きされます。この場合も、間違ったデータがスレーブ ノードに送信されます。

たまたま、「isend」の代わりに「send」を使用するように「実際の」コードを再構築することができました。ただし、将来「isend」を使用する必要がある場合は、もう少し注意します!

于 2010-10-27T14:35:10.623 に答える
4

今日も同様の問題に遭遇したと思います。カスタム データ型をシリアル化するときに、反対側で (時々) 破損していることに気付きました。修正は、 のmpi::request戻り値を格納することでしisendた。communicator::isend_impl(int dest, int tag, const T& value, mpl::false_)in of boost を見るcommunicator.hppと、シリアル化されたデータが共有ポインターとしてリクエストに入れられていることがわかります。再び削除されると、データが無効になり、何かが起こる可能性があります。

つまり: isend の戻り値を常に保存してください!

于 2011-04-08T16:43:39.113 に答える
2

あなたのコンパイラは、あなたの「solutions[sol] = new Solution;」のがらくたを最適化しました。ループし、すべての num_solution++ インクリメントの最後にジャンプできると結論付けました。もちろんそうするのは間違っていますが、それが起こったことです。

可能性は非常に低いですが、自動スレッド化または自動並列化コンパイラによって、Solutions() の ctor リスト内の solution_num = num_solutions の 20 個のインスタンスに対して半ランダムな順序で numsolutions++ の 20 個のインスタンスが発生する可能性があります。最適化がひどく間違っていた可能性が高いです。

交換

for (int sol = 0; sol < numSolutions; ++sol)
    {
      ソリューション[sol] = 新しいソリューション;
    }

for (int sol = 0; sol < numSolutions; ++sol)
    {
      ソリューション[sol] = 新しいソリューション(sol);
    }

そしてあなたの問題はなくなります。特に、各ソリューションは、コンパイラが 20 個のインクリメントを誤って並べ替えている間に、共有静的にたまたまある番号を取得するのではなく、独自の番号を取得します。

于 2010-10-26T15:22:59.783 に答える
1

milianw の回答に基づく: 私の印象では、isend を使用する正しい方法は、返された要求オブジェクトを保持し、別の isend 呼び出しの前に test() または wait() メソッドを使用して完了したことを確認することです。isend() を呼び出し続けて、リクエストオブジェクトをベクターにプッシュすることもうまくいくと思います。その後、{test,wait}_{any,some,all} を使用してこれらのリクエストをテストまたは待機できます。

遅かれ早かれ MPI バッファーが不足するため、ある時点で、受信者が受信できるよりも速く送信を投稿しているかどうかについても心配する必要があります。私の経験では、これは単にクラッシュとして現れます。

于 2011-11-21T22:46:05.857 に答える