使用MPI_Scatter发送矩阵的列

时间:2012-05-28 17:06:23

标签: c mpi

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

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

MPI_Scatter - sending columns of 2D array

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()如何运作以及它究竟做了什么?
  3. 请记住,我是MPI的初学者。提前谢谢。

1 个答案:

答案 0 :(得分:33)

my answerthis question中有一个关于这个问题的长篇描述:很多人都有这些问题的事实证明它并不明显,这些想法需要一些时间来适应。

要知道的重要事项是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_DOUBLE,每个块的开头位于stride=N MPI_DOUBLE s之间。换句话说,它将每N次加倍,总共N次;非常适合从(连续存储的)NxN双精度数组中提取一列。一个方便的检查是查看正在跨越多少数据(count*stride = N*N这是矩阵的完整大小,检查)以及实际包含多少数据(count*blocksize = N,这是一个大小专栏,检查。)

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

你想调用MPI_Scatter,它将第一个coltype(比方说)发送到处理器0,下一个coltype发送到处理器1,等等。如果你用一个简单的1d数组做这个,很容易计算输出“下一个”数据类型的位置;如果你将1 int分散到每个处理器,那么“next”int会在第一个int结束后立即开始。

但是你的新coltype列的总extent从列的开头到N*N MPI_DOUBLE s后面 - 如果MPI_Scatter遵循相同的逻辑(它),它将开始完全在矩阵内存之外寻找“下一个”列,依此类推下一个和下一个。你不仅没有得到你想要的答案,程序也可能崩溃。

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

当您有更复杂的数据布局时,例如在上面链接的2d数组示例的2d块中,那么“next”项之间没有一个步长;你仍然需要做大小调整技巧,使大小成为一个有用的单位,但是你需要使用MPI_Scatterv而不是分散来明确指定要发送的位置。