事情经过
昨天在 天河二号 上运行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_Isend
和MPI_Irecv
两个异步通信函数,最后用MPI_Waitall
来确保数据接收正确。会不会是通信过程中造成的内存泄漏呢?
再次查阅这两个函数的文档,发现上述代码确实会造成资源泄漏:每一个异步的MPI_Request
必须要被显示或者隐式释放,否则其会一直占用内存。上面代码为了提高效率,只对接收的MPI_Request
用了MPI_Wait
,MPI_Isend
的MPI_Request
没有释放,将一直占用内存。随着时间的积累,最终程序因内存耗尽而被kill。
解决办法也很简单,MPI_Waitall
中增加对MPI_Isend
中的MPI_Request
等待:
MPI_Waitall(2*np, reqs, stats);
重新编译代码运行程序,一切正常,问题解决。
释放MPI_Request
异步请求会用到MPI_Request
,使用完毕后需要显式或隐式释放,上面代码中的MPI_Waitall
便是隐式释放。具体来说,MPI_Request
有如下三种被释放方式:
MPI_Wait
类函数,隐式释放。调用MPI_Wait
,通信完成的MPI_Request
内存空间被释放;MPI_Test
类函数,隐式方式。MPI_Test
用来测试请求是否完成,如果完成则释放其内存;MPI_Request_free
函数,显式释放。
更多信息请参考文末链接。