事情经过

昨天在 天河二号 上运行MPI程序,迭代到某个步数后会被kill掉。因之前出现过运行中被莫名终止的现象,以为这次也是调度系统出意外。再次运行程序,执行相同步数后还是被kill。查看运行日志,提示内存耗尽被强制终止,所以问题原因是程序出现了内存泄漏。

仔细检查了代码,确认程序中没出现申请内存未释放的问题。但运行事实表明肯定哪里有问题,不得已只好用stdlib.h中的system函数调用free -m定位内存泄漏的代码段。经过对比输出,发现执行线性方程组求解后内存占用显著提升。查看线性方程组迭代求解代码,确认期间没有任何内存分配,到底是什么原因呢?

原因和解决办法

一番思索后,目标转向线性方程组中用到的MPI通信,其主要代码是:

const int TAG = 1;
const int np  = neighRegions.size();

for (int p = 0; p < np; ++ p) {
  MPI_Isend(sendBuffer, 1, sendType[p], neighRegions[p], TAG, MPI_COMM_WORLD, reqs + p);
  MPI_Irecv(recvBuffer, 1, recvType[p], neighRegions[p], TAG, MPI_COMM_WORLD, reqs + p + np);
}

// only ensure receive
MPI_Waitall(np, reqs + np, stats);

代码中用了MPI_IsendMPI_Irecv两个异步通信函数,最后用MPI_Waitall来确保数据接收正确。会不会是通信过程中造成的内存泄漏呢?

再次查阅这两个函数的文档,发现上述代码确实会造成资源泄漏:每一个异步的MPI_Request必须要被显示或者隐式释放,否则其会一直占用内存。上面代码为了提高效率,只对接收的MPI_Request用了MPI_WaitMPI_IsendMPI_Request没有释放,将一直占用内存。随着时间的积累,最终程序因内存耗尽而被kill。

解决办法也很简单,MPI_Waitall中增加对MPI_Isend中的MPI_Request等待:

MPI_Waitall(2*np, reqs, stats);

重新编译代码运行程序,一切正常,问题解决。

释放MPI_Request

异步请求会用到MPI_Request,使用完毕后需要显式或隐式释放,上面代码中的MPI_Waitall便是隐式释放。具体来说,MPI_Request有如下三种被释放方式:

  1. MPI_Wait类函数,隐式释放。调用MPI_Wait,通信完成的MPI_Request内存空间被释放;
  2. MPI_Test类函数,隐式方式。MPI_Test用来测试请求是否完成,如果完成则释放其内存;
  3. MPI_Request_free函数,显式释放。

更多信息请参考文末链接。

参考

  1. Will my program require increasing amounts of memory if I don’t pair my MPI_Isend’s with MPI_Wait?
  2. Friends don’t let friends leak MPI_Requests