我在计算群集上使用Boost MPI(1.55)而不是Open MPI(1.6.1)运行项目。
我们的集群有64个CPU的节点,我们在每个节点上产生一个MPI进程。我们的大多数通信都是在各个进程之间进行的,每个进程都有一系列irecv()请求打开(对于不同的标签),并且使用send()进行阻塞发送。
我们遇到的问题是,经过一段时间的处理(通常不到10分钟),我们就会收到导致程序结束的错误:
[btl_tcp_component.c:1114:mca_btl_tcp_component_accept_handler] accept() failed: Too many open files in system (23).
更紧密的调试显示它的网络套接字占用了这些文件句柄,我们的操作系统限制为65536个文件句柄打开。其中大多数都处于“TIME_WAIT”状态,这显然是TCP在套接字关闭后60秒内(通常)执行的操作(为了捕获任何延迟的数据包)。我的印象是Open MPI没有关闭套接字(http://www.open-mpi.org/faq/?category=tcp#tcp-socket-closing)并且只保持N ^ 2个套接字打开,以便所有进程可以相互通信。显然65536超过64 ^ 2(涉及MPI的这个错误的最常见原因仅仅是文件限制小于N ^ 2),并且大多数是最近处于关闭状态的套接字。
我们的C ++代码太大而不适合这里,但我已经编写了一些简化版本,至少显示我们的实现,看看我们的技术是否有任何问题。我们使用MPI会导致OpenMPI关闭并重新打开太多套接字吗?
namespace mpi = boost::mpi;
mpi::communicator world;
bool poll(ourDataType data, mpi::request & dataReq, ourDataType2 work, mpi::request workReq) {
if(dataReq.test()) {
processData(data); // do a bunch of work
dataReq = world.irecv(mpi::any_source, DATATAG, data);
return true;
}
if(workReq.test()) {
int target = assess(work);
world.send(target, DATATAG, dowork);
world.irecv(mpi::any_source, WORKTAG, data);
return true;
}
return false;
}
bool receiveFinish(mpi::request finishReq) {
if (finishReq.test()) {
world.send(0, RESULTS, results);
resetSelf();
finishReq = world.irecv(0, FINISH);
return true;
}
return false;
}
void run() {
ourDataType data;
mpi::request dataReq = world.irecv(mpi::any_source, DATATAG, data);
ourDataType2 work;
mpi::request workReq = world.irecv(mpi::any_source, WORKTAG, work);
mpi::request finishReq = world.irecv(0, FINISH); // the root process can call a halt
while(!receiveFinish(finishReq)) {
bool doWeContinue = poll(data, dataReq);
if(doWeContinue) {
continue;
}
// otherwise we do other work
results = otherwork();
world.send(0, RESULTS, results);
}
}
答案 0 :(得分:1)
这可能不是Open MPI打开这么多套接字的真正原因,但您似乎在poll()
函数的以下部分泄漏了请求:
if(workReq.test()) {
int target = assess(work);
world.send(target, DATATAG, dowork);
world.irecv(mpi::any_source, WORKTAG, data); // <-------
return true;
}
world.irecv()
返回的请求句柄永远不会保存,从而丢失。如果在同一个workReq
对象上定期调用,则此分支将在请求完成后每次执行,因为测试已完成的请求始终返回true
。因此,您将启动许多永远不会等待或测试的非阻塞接收。更不用说发送的消息了。
receiveFinish
中存在类似问题 - finishReq
按价值传递,而作业不会影响run()
中的值。
旁注:这真的是你使用的代码吗?您在poll()
中调用的run()
函数看起来有两个参数,而此处显示的函数有四个参数,并且没有默认值的参数。