在不增加 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
,但这个例子告诉我这个参数是一样的,而系统监视器显示增加。我的问题是关于VmSize
、VmRss
和实际内存使用之间的关系。
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 的情况下增加虚拟内存的主要内容,如果未能解决你的问题,请参考以下文章
sysinternals 进程资源管理器中的“虚拟大小”是啥