我正在尝试使用MPI编写矩阵向量乘法程序.我正在尝试将矩阵的列发送到单独的进程并在本地计算结果.最后我做了一个MPI_Reduce使用MPI_SUM操作.
发送矩阵的行很容易,因为C按行主顺序存储数组,但列不是(如果不逐个发送).我在这里读到了这个问题:
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)
这很完美,但我不能说我真的明白它是如何工作的.
MPI_Type_vector存储在内存中?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而不是分散来明确指定要发送的位置.