我正在尝试使用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);
这很有效,但我不能说我真的明白它是如何运作的。
MPI_Type_vector
如何存储在内存中?MPI_Type_create_resized()
如何运作以及它究竟做了什么?请记住,我是MPI的初学者。提前谢谢。
答案 0 :(得分:33)
在my answer到this 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而不是分散来明确指定要发送的位置。