12

我正在尝试使用 MPI 编写矩阵向量乘法程序。我正在尝试将矩阵的列发送到单独的进程并在本地计算结果。最后我做了一个MPI_Reduce使用MPI_SUM操作。

发送矩阵的行很容易,因为 C 以行优先顺序存储数组,但列不是(如果您不逐个发送它们)。我在这里读到了这个问题:

MPI_Scatter - 发送二维数组的列

Jonathan Dursi 建议使用新的 MPI 数据类型,这就是我根据自己的需要调整他的代码所做的:

  double matrix[10][10];
  double mytype[10][10];
  int part_size; // stores how many cols a process needs to work on
  MPI_Datatype col, coltype;
  // ...
  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);
  MPI_Type_commit(&col);
  MPI_Type_create_resized(col, 0, 1*sizeof(double), &coltype);
  MPI_Type_commit(&coltype);
  // ...
  MPI_Scatter(matrix, part_size, coltype,
              mypart, part_size, coltype,
              0, MPI_COMM_WORLD);

  // calculations...
  MPI_Reduce(local_result, global_result,
             N, MPI_DOUBLE,
             MPI_SUM,
             0, MPI_COMM_WORLD);

这工作得很好,但我不能说我真的理解它是如何工作的。

  1. 是如何MPI_Type_vector存储在内存中的?
  2. 它是如何MPI_Type_create_resized()工作的,它到底是做什么的?

请记住,我是 MPI 的初学者。提前致谢。

4

1 回答 1

39

在我对这个问题的回答中对这个问题有很长的描述:很多人有这些问题的事实证明它并不明显,而且这些想法需要一些时间来适应。

重要的是要知道 MPI 数据类型描述的内存布局。调用顺序MPI_Type_vector为:

int MPI_Type_vector(int count,
                   int blocklength,
                   int stride, 
                   MPI_Datatype old_type,
                   MPI_Datatype *newtype_p)

它创建了一个新类型,它描述了内存布局,其中每个stride项目,有一个项目块blocklength被拉出,以及count这些块的总和。这里的项目以任何单位为单位old_type。因此,例如,如果您调用(在此处命名参数,您实际上不能在 C 中这样做,但是:)

 MPI_Type_vector(count=3, blocklength=2, stride=5, old_type=MPI_INT, &newtype);

然后newtype会像这样描述内存中的布局:

   |<----->|  block length

   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
   | X | X |   |   |   | X | X |   |   |   | X | X |   |   |   |
   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

   |<---- stride ----->|

   count = 3

其中每个正方形是一个整数大小的内存块,大概是 4 个字节。请注意,步幅是从一个块的开始到下一个块的开始的整数距离,而不是块之间的距离。

好的,所以在你的情况下,你打电话给

  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);

这将采用count = N大小为blocklength=1 MPI_DOUBLEs 的块,在每个 s 块的开头之间有一个空格stride=N MPI_DOUBLE。换句话说,它需要每 N'th double,总共 N 次;非常适合从(连续存储的)NxN 双精度数组中提取一列。一个方便的检查是查看有多少数据被跨过(count*stride = N*N这是矩阵的完整大小,检查)以及实际包含了多少数据(count*blocksize = N,这是一列的大小,检查。)

如果您所要做的只是调用 MPI_Send 和 MPI_Recv 来交换各个列,那么您就完成了;你可以使用这种类型来描述列的布局,你会没事的。但还有一件事。

您想调用MPI_Scatter,它将第一个 coltype(例如)发送到处理器 0,将下一个 coltype 发送到处理器 1,等等。如果您使用简单的 1d 数组执行此操作,则很容易找出“下一个”数据类型的位置是; 如果您将 1 个 int 分散到每个处理器,则“下一个”int 在第一个 int 结束后立即开始。

但是您的新 coltype 列的总范围从列的开头到N*N MPI_DOUBLEs 之后 - 如果 MPI_Scatter 遵循相同的逻辑(确实如此),它将开始完全在矩阵内存之外寻找“下一个”列,以此类推,下一个和下一个。你不仅不会得到你想要的答案,程序很可能会崩溃。

解决此问题的方法是告诉 MPI,该数据类型的“大小”用于计算“下一个”所在的位置是内存中一列开始位置和下一列开始位置之间的大小;也就是说,正好一个MPI_DOUBLE。这不会影响发送的数据量,仍然是 1 列数据;它只影响“下一个”计算。对于数组中的列(或行),您可以将此大小发送为内存中适当的步长,MPI 将选择正确的下一列进行发送。如果没有这个 resize 运算符,您的程序可能会崩溃。

当您有更复杂的数据布局时,例如在上面链接的二维数组示例的二维块中,“下一个”项目之间没有一个步长;您仍然需要调整大小以使大小成为一些有用的单位,但是您需要使用MPI_Scatterv而不是 scatter 来明确指定要发送的位置。

于 2012-05-28T17:24:26.123 回答