使用MPI_Scatter发送矩阵的列

hat*_*enn 11 c mpi

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

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

MPI_Scatter - 发送2D数组的列

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);
Run Code Online (Sandbox Code Playgroud)

这很完美,但我不能说我真的明白它是如何工作的.

  1. 如何MPI_Type_vector存储在内存中?
  2. MPI_Type_create_resized()工作如何以及它究竟做了什么?

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

Jon*_*rsi 33

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

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

int MPI_Type_vector(int count,
                   int blocklength,
                   int stride, 
                   MPI_Datatype old_type,
                   MPI_Datatype *newtype_p)
Run Code Online (Sandbox Code Playgroud)

它创建了一个新类型,它描述了每个stride项目的内存布局,其中有一块blocklength项目被拉出,以及count这些块的总数.这里的项目是以什么为单位old_type.所以例如,如果你打电话(在这里命名参数,你实际上不能在C中做,但:)

 MPI_Type_vector(count=3, blocklength=2, stride=5, old_type=MPI_INT, &newtype);
Run Code Online (Sandbox Code Playgroud)

然后newtype将在内存中描述这样的布局:

   |<----->|  block length

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

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

   count = 3
Run Code Online (Sandbox Code Playgroud)

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

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

  MPI_Type_vector(N, 1, N, MPI_DOUBLE, &col);
Run Code Online (Sandbox Code Playgroud)

这将采用count = N每个大小为blocklength=1 MPI_DOUBLEs的块,每个块的开始之间有一个空格stride=N MPI_DOUBLE.换句话说,它将每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列的总范围从列的开头到N*N MPI_DOUBLE稍后的 - 如果MPI_Scatter遵循相同的逻辑(它确实),它将开始在矩阵内存之外完全寻找"下一个"列,以及下一个和下一个等等.你不仅没有得到你想要的答案,程序可能会崩溃.

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

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