在不增加 VmSize 的情况下增加虚拟内存

Posted

技术标签:

【中文标题】在不增加 VmSize 的情况下增加虚拟内存【英文标题】:Increase of virtual memory without increse of VmSize 【发布时间】:2012-10-29 19:24:56 【问题描述】:

我在谷歌和这个网站上搜索了我的问题,但我仍然不明白解决方案。

我有一段MPI 程序,其中RECV 一些数据。程序在大数组上崩溃并出现虚拟内存不足的错误,所以我开始考虑/proc/self/status文件。

MPI_RECV 之前是:

Name:   model.exe                                                               
VmPeak:   841640 kB
VmSize:   841640 kB
VmHWM:     15100 kB
VmRSS:     15100 kB
VmData:   760692 kB

之后:

Name:   model.exe                                                            
VmPeak:   841640 kB
VmSize:   841640 kB
VmHWM:    719980 kB
VmRSS:    719980 kB
VmData:   760692 kB

我在 Ubuntu 上对其进行了测试,通过系统监视器我看到内存在增加。但是我很困惑VmSize(和VmPeak)参数没有变化。

问题是——实际内存使用的指标是什么?

这是否意味着,真正的指标是VmRSS? (而VmSize 仅分配但仍未使用内存)

【问题讨论】:

查看/proc/self/maps内部,并考虑在使用gfortran -Wall -g编译后使用valgrind 那里的代码真的没有任何意义。我知道内部MPI 缓冲区并了解内存增长的原因。但我不明白如何跟踪这个过程。我以为我可以看看VmSize,但这个例子告诉我这个参数是一样的,而系统监视器显示增加。我的问题是关于VmSizeVmRss 和实际内存使用之间的关系。 VmRSS 测量进程内存的一部分,它实际上由物理内存页面支持。如果您的程序分配 1 GiB 并触摸所有这些,那么 VmRSS 将增加 1 GiB。如果那时您的程序处于休眠状态一段时间,操作系统可能会将该 1 GiB 的一部分分页到交换区域,并且VmRSS 会随着分页数量而减少。有关该过程的更详细说明,请参阅我的回答。 【参考方案1】:

(您的问题的可能解决方案是最后一段)

在大多数具有虚拟内存的现代操作系统上,内存分配是一个两阶段的过程。首先,进程的一部分虚拟地址空间被保留,进程的虚拟内存大小(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 <value in KiB> 设置限制。这仅适用于由当前 shell 生成的进程及其子、孙等。如果要在远程节点上启动,您需要指示 mpiexec(或 mpirun)将此值传播到所有其他进程。如果您在一些工作负载管理器(如 LSF、Sun/Oracle Grid Engine、Torque/PBS 等)的控制下运行程序,则有一些作业参数可以控制虚拟内存大小限制。最后但同样重要的是,32 位进程通常限制为 2 GiB 的可用虚拟内存。

【讨论】:

因此,这意味着 VmSize 是所有已分配数据(已使用或未使用)中的最大值。我很困惑,我们超级计算机的管理员写信给我,说他们每 8 个内核有 12 Gb。我查看VmSize 参数(它是所有内存类型中的最大值),我所有的 8 个处理器使用大约 800 mb-1000 mb。这是否意味着某处还有其他一些内存包,而/proc/self/status 没有显示它们? @vovo,你使用什么 MPI 库?通常用于节点内通信(即驻留在同一物理节点上的进程之间)共享内存。对于快速网络(例如 InfiniBand)上的节点间通信,内存被锁定。此外,并非所有 12 GiB 都可供用户进程使用 - 操作系统内核和支持用户空间工具也需要内存。 我在 Infiniband 上使用 intel 编译器和 impi。当然,我在数千个 kenels 上运行 - 每个节点 8 个内核(每个节点 12 Gb)。是否有任何可能的方法来了解我的任务的真实(没有操作系统、工具等)限制? @vovo,将ulimit -a 放入您提交到集群上的批处理系统并检查输出的作业脚本中。如果使用 Sun/Oracle Grid Engine,请注意它对初始任务设置了更大的限制,即来自ulimit -a 的输出可能显示出比实际 MPI 进程更大的限制(永远不明白这是一个错误还是上交所)。

以上是关于在不增加 VmSize 的情况下增加虚拟内存的主要内容,如果未能解决你的问题,请参考以下文章

Linux下怎样增加虚拟内存

sysinternals 进程资源管理器中的“虚拟大小”是啥

在不使用图像的情况下增加 UISlider 高度

如何在不检查 MySQL 中的 AUTOINCREMENT 的情况下增加主键

在不移动其他文本的情况下增加字体大小

在不更改公共 IP 的情况下增加 EC2 容量