使用MPI_Scatter发送矩阵的列
我正在尝试使用MPI编写矩阵向量乘法程序。 我正在尝试将矩阵的列发送到单独的进程并在本地计算结果。 最后,我使用MPI_SUM
操作执行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);
这很好用,但我不能说我真的明白它是如何工作的。
-
MPI_Type_vector
是MPI_Type_vector
存储在内存中的? -
MPI_Type_create_resized()
如何工作以及它到底做了什么?
请记住,我是MPI的初学者。 提前致谢。
在我对这个问题的 回答中有一个很长的描述这个问题 :许多人有这些问题的事实certificate它并不明显,这些想法需要一些时间来适应。
重要的是要知道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
s,每个stride=N
块的起点之间有一个空格stride=N
MPI_DOUBLE
s。 换句话说,它将每N次加倍,总共N次; 非常适合从(连续存储的)NxN双精度数组中提取一列。 一个方便的检查是查看正在count*stride = N*N
多少数据( 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将选择要发送的正确的下一列。 如果没有此resize运算符,您的程序可能会崩溃。
当您有更复杂的数据布局时,例如在上面链接的2d数组示例的2d块中,那么“next”项之间没有一个步长; 你仍然需要做大小调整技巧,使大小成为一些有用的单位,但是你需要使用MPI_Scatterv而不是分散来明确指定要发送的位置。