我有一个我希望并行化的串行C程序.我知道MPI,MPI_Send,MPI_Recv等基础知识.基本上,我的数据生成算法运行速度明显快于数据处理算法.目前它们是串行运行的,但我认为在根进程中运行数据生成,在从进程上完成数据处理,并从根发送消息到包含要处理的数据的从属.这样,每个从属处理数据集,然后等待其下一个数据集.
问题是,一旦根进程完成生成数据,程序就会挂起,因为从属程序正在等待更多.
这是问题的一个例子:
#include "mpi.h"
#include <cassert>
#include <cstdio>
class Generator {
public:
Generator(int min, int max) : value(min - 1), max(max) {}
bool NextValue() {
++value;
return value < max;
}
int Value() { return value; }
private:
int value, max;
Generator() {}
Generator(const Generator &other) {}
Generator &operator=(const Generator &other) { return *this; }
};
long fibonnaci(int n) {
assert(n > 0);
if (n == 1 || n == 2) return 1;
return fibonnaci(n-1) + fibonnaci(n-2);
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int rank, num_procs;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
if (rank == 0) {
Generator generator(1, 2 * num_procs);
int proc = 1;
while (generator.NextValue()) {
int value = generator.Value();
MPI_Send(&value, 1, MPI_INT, proc, 73, MPI_COMM_WORLD);
printf("** Sent %d to process %d.\n", value, proc);
proc = proc % (num_procs - 1) + 1;
}
} else {
while (true) {
int value;
MPI_Status status;
MPI_Recv(&value, 1, MPI_INT, 0, 73, MPI_COMM_WORLD, &status);
printf("** Received %d from process %d.\n", value, status.MPI_SOURCE);
printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10)));
}
}
MPI_Finalize();
return 0;
}
显然,并非上述所有内容都是“良好实践”,但足以说明这一点.
如果我从slave进程中删除while(true),则程序在每个从服务器退出时退出.我希望程序只在根进程完成其工作后退出并且所有从属服务器都处理了已发送的所有内容.
如果我知道将生成多少数据集,我可以运行许多进程,一切都会很好地退出,但这不是这里的情况.
有什么建议? API中有什么可以做到这一点吗?这可以通过更好的拓扑结构更好地解决吗? MPI_Isend或MPI_IRecv会做得更好吗?我对MPI很新,所以请耐心等待.
谢谢
解决方法:
通常的做法是向所有工作进程发送一条带有特殊标记的空消息,该消息表示它们退出无限处理循环.假设这个标签是42.您可以在工作循环中执行类似的操作:
while (true) {
int value;
MPI_Status status;
MPI_Recv(&value, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
if (status.MPI_TAG == 42) {
printf("Process %d exiting work loop.\n", rank);
break;
}
printf("** Received %d from process %d.\n", value, status.MPI_SOURCE);
printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10)));
}
在生成器循环之后,管理器进程会执行类似的操作:
for (int i = 1; i < num_procs; i++)
MPI_Send(&i, 0, MPI_INT, i, 42, MPI_COMM_WORLD);
关于你的下一个问题.在主进程中使用MPI_Isend()将反序列化执行并提高性能.但事实是,您发送的是非常小的消息,而这些消息通常是内部缓冲的(警告 – 依赖于实现!)因此您的MPI_Send()实际上是非阻塞的,并且您已经进行了非串行执行. MPI_Isend()返回一个MPI_Request句柄,您需要稍后处理.您可以等待它完成MPI_Wait()或MPI_Waitall(),但您也可以在其上调用MPI_Request_free(),并在操作结束时自动释放它.这通常是在您想要异步发送许多消息并且不关心何时完成发送时完成的,但这是一种不好的做法,因为有大量未完成的请求会占用大量宝贵的内存.至于工作进程 – 他们需要数据才能继续计算,因此不需要使用MPI_Irecv().
欢迎来到MPI编程的精彩世界!