(问题的可能解决方案是最后一段)
大多数具有虚拟内存的现代操作系统上的内存分配是一个两阶段过程.首先,保留过程的虚拟地址空间的一部分,并且过程的虚拟存储器大小(VmSize)相应地增加.这将在所谓的流程页面表中创建条目.页面最初与物理内存帧无关,即实际上没有使用物理内存.每当实际读取或写入此分配部分的某些部分时,就会发生页面错误,操作系统会从物理内存安装(映射)一个空闲页面.这增加了进程的驻留集大小(VmRSS).当某些其他进程需要内存时,操作系统可能会将一些不经常使用的页面的内容(“不经常使用的页面”的定义高度依赖于实现)存储到某些持久存储(大多数情况下是硬盘驱动器,或者通常是交换设备) )然后取消映射.此过程会降低RSS但保持VmSize不变.如果稍后访问此页面,将再次发生页面错误并将其恢复.虚拟内存分配仅在释放虚拟内存分配时减少.请注意,VmSize还会计算内存映射文件(即可执行文件及其链接到的所有共享库或其他显式映射文件)和共享内存块.
进程中有两种通用类型的内存 – 静态分配的内存和堆内存.静态分配的内存保留所有常量和全局/静态变量.它是数据段的一部分,其大小由VmData度量标准显示.数据段还承载程序堆的一部分,其中正在分配动态内存.数据段是连续的,即它从某个位置开始并朝向堆栈向上增长(从非常高的地址开始然后向下增长).数据段中堆的问题在于它由一个特殊的堆分配器管理,该分配器负责将连续的数据段细分为更小的内存块.另一方面,在Linux中,动态内存也可以通过直接映射虚拟内存来分配.这通常仅用于大型分配以节省内存,因为它仅允许分配页面大小(通常为4 KiB)的倍数的内存.
堆栈也是大量内存使用的重要来源,特别是如果在自动(堆栈)存储中分配大数组.堆栈从可用虚拟地址空间的最顶端开始并向下增长.在某些情况下,它可能会到达数据段的顶部,或者可能达到其他一些虚拟分配的末尾.那么糟糕的事情发生了堆栈大小在VmStack度量标准中以及VmSize中进行计算.
人们可以这样总结:
> VmSize考虑了所有虚拟内存分配(文件映射,共享内存,堆内存,无论内存),并且几乎每次分配新内存时都会增长.几乎是因为如果新的堆内存分配是在数据段中释放的旧分配的位置进行的,则不会分配新的虚拟内存.无论何时释放虚拟分配,它都会递减. VmPeak跟踪VmSize的最大值 – 它只能在时间上增加.
> VmRSS随着内存的访问而增长,随着内存被分页到交换设备而减少.
> VmData随着堆的数据段部分的使用而增长.它几乎从不收缩,因为当前的堆分配器保留释放的内存,以防将来的分配需要它.
如果您在具有InfiniBand或其他基于RDMA的结构的群集上运行,则会发挥另一种内存 – 锁定(已注册)内存(VmLck).这是不允许被分页的内存.它的增长和缩小取决于MPI的实现.有些人从不取消注册已经注册的块(有关为什么太复杂而无法在此处描述的技术细节),其他人这样做是为了更好地利用虚拟内存管理器.
在您的情况下,您说您正在遇到虚拟内存大小限制.这可能意味着此限制设置得太低,或者您遇到操作系统强加的限制.首先,Linux(和大多数Unix)都有通过ulimit机制强加人为限制的手段.在shell中运行ulimit -v会告诉你虚拟内存大小的限制在KiB中.您可以使用ulimit -v< KiB中的值>来设置限制.这仅适用于当前shell及其子级,grandchilren等生成的进程.如果要在远程节点上启动,则需要指示mpiexec(或mpirun)将此值传播到所有其他进程.如果您在某些工作负载管理器(如LSF,Sun / Oracle Grid Engine,Torque / PBS等)的控制下运行您的程序,则会有控制虚拟内存大小限制的作业参数.最后但并非最不重要的是,32位进程通常限制为2 GiB的可用虚拟内存.