Boost.MPI:收到的不是发送的!

发布于 2024-09-29 07:46:28 字数 4914 浏览 2 评论 0原文

我对使用 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.

因此,当主节点向节点 1 发送 0、向节点 2 发送 1、向节点 3 发送 2 等时,大多数从节点(由于某种原因)都会收到该数字19. 因此,我们不是生成从 0 到 19 的数字的平方,而是得到 0 的平方、1 的平方和 19 的平方 18 次!

预先感谢任何可以解释这一点的人。

艾伦

I am relatively new to using Boost MPI. I have got the libraries installed, the code compiles, but I am getting a very odd error - some integer data received by the slave nodes is not what was sent by the master. What is going on?

I am using boost version 1.42.0, compiling the code using mpic++ (which wraps g++ on one cluster and icpc on the other). A reduced example follows, including the output.

Code:

#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;
}

Running this on 21 nodes (1 master, 20 slaves) produces:

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.

So while the master sends 0 to node 1, 1 to node 2, 2 to node 3 etc, most of the slave nodes (for some reason) receive the number 19. So rather than producing the squares of the numbers from 0 to 19, we get 0 squared, 1 squared and 19 squared 18 times!

Thanks in advance to anyone who can explain this.

Alan

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

煮茶煮酒煮时光 2024-10-06 07:46:28

好吧,我想我已经有了答案,这需要对底层 C 风格 MPI 调用有一些了解。 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”,我会更加小心!

Ok, I think I have the answer, which requires some knowledge of the underlying C-style MPI calls. Boost's 'isend' function is essentially a wrapper around 'MPI_Isend', and it does not protect the user from needing to know some of the details about how 'MPI_Isend' works.

One parameter of 'MPI_Isend' is a pointer to a buffer that contains the information you wish to send. Importantly, however, this buffer CANNOT be reused until you know that the message has been received. So consider the following code:

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

This works perfectly, as each solution number is in its own location in memory. Now consider the following minor adjustment:

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

Now the underlying 'MPI_Isend' call is provided with a pointer to solNum. Unfortunately, this bit of memory is overwritten each time around the loop, so while it may look like the number 4 is sent to node 5, by the time the send actually takes place, the new contents of that memory location (19 for example) are passed instead.

Now consider the original code:

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

Here we are passing a temporary. Again, the location of this temporary in memory gets overwritten each time around the loop. Again, the wrong data is sent to the slave nodes.

As it happens, I've been able to restructure my 'real' code to use 'send' instead of 'isend'. However, if I need to use 'isend' in future, I'll be a little more careful!

荒岛晴空 2024-10-06 07:46:28

我想我今天偶然发现了类似的问题。当序列化自定义数据类型时,我注意到它(有时)在另一侧被损坏。修复方法是存储 isendmpi::request 返回值。如果您查看 boost 的 communicator.hpp 中的 communicator::isend_impl(int dest, int tag, const T& value, mpl::false_),您会看到序列化数据作为共享指针放入请求中。如果再次删除,数据就会失效,并且可能会发生任何情况。

所以:总是保存 isend 返回值!

I think I have stumbled upon a similar problem today. When serializing a custom datatype I noticed it was (sometimes) corrupted on the other side. The fix was to store the mpi::request return value of isend. If you look at communicator::isend_impl(int dest, int tag, const T& value, mpl::false_) in communicator.hpp of boost, you'll see that the serialized data is put into the request as a shared pointer. If it gets removed again, the data is invalidated and anything might happen.

so: always save isend return values!

情愿 2024-10-06 07:46:28

你的编译器已经优化了你的“solutions[sol] = new Solution;”中的垃圾。循环并得出结论,它可以跳转到所有 num_solution++ 增量的末尾。这样做当然是错误的,但事实就是这样。

自动线程化或自动并行化编译器有可能(尽管可能性很小)导致 numsolutions++ 的 20 个实例相对于 Solutions() 的构造函数列表中的 Solution_num = num_solutions 的 20 个实例以半随机顺序出现。更有可能的是,优化出现了严重错误。

替换

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

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

,您的问题就会消失。特别是,每个解决方案将获得自己的编号,而不是在编译器对 20 个增量进行错误重新排序期间碰巧获得共享静态的任何编号。

Your compiler has optimized the crap out of your "solutions[sol] = new Solution;" loop and concluded that it can jump to the end of all the num_solution++ increments. It is of course wrong to do so, but that's what's happened.

It's possible, though very unlikely, that an automatically threading or automatically parallelizing compiler has cause the 20 instances of numsolutions++ to occur in semi-random order with respect to the 20 instances of solution_num = num_solutions in the ctor list of Solutions(). It's much more likely that an optimization went horribly wrong.

Replace

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

with

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

and your problem will go away. In particular, each Solution will get its own number instead of getting whatever number the shared static happens to have some time during the compiler's incorrect reordering of the 20 increments.

冷了相思 2024-10-06 07:46:28

基于 milianw 的答案:我的印象是,使用 isend 的正确方法是保留它返回的请求对象,并在再次调用 isend 之前使用其 test() 或 wait() 方法检查它是否已完成。我认为继续调用 isend() 并将请求对象推送到向量上也可以。然后,您可以使用 {test,wait}_{any,some,all} 测试或等待这些请求。

在某些时候,您还需要担心您发送的速度是否比收件人接收的速度快,因为您迟早会用完 MPI 缓冲区。根据我的经验,这只会表现为崩溃。

Building on milianw's answer: My impression is that the correct way of using isend is to keep the request object it returns and check that it has completed by using its test() or wait() methods before another call to isend. I think it would also work to keep calling isend() and pushing the request objects onto a vector. You could then test or wait on those requests with {test,wait}_{any,some,all}.

At some point, you also need to worry about whether you are posting sends faster than the recipient can receive, because sooner or later you'll run out of MPI buffers. In my experience, this will just manifest itself as a crash.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文