Measure memory consumption of programs in Linux
在衡量程序性能时不仅需要测量运行时间,还常常需要测量其内存使用。比如如果一个程序比更一个程序快,有可能是以空间换取时间,因此单纯比较运行速度就失去了意义。我们这里演示如何在Linux环境下测量程序的内存占用。
我们先要弄清楚内存的含义:对于运行中的程序而言,它有四个部分:数据,文本,堆,栈(data, text, stack, heap)。对于操作系统而言,所有分配给程序的内存都用虚拟内存表示(Virtual Memory)。对硬件而言,内存分为物理内存(Physical)和交换内存(Swap)。这三种环境的关系有两种:(1)程序内存会被映射到虚拟内存,即虚拟内存中分段对应数据、文本、堆、栈; (2)虚拟内存中某些部分放在物理内存中,另一些放到交换内存中。
为了程序运行有空间上的效率,Linux系统下会共享内存。例如共享程序库(比如libc)或者是内存映射(memory map)。据我的理解,共享部分在程序空间中可以是文本部分,在虚拟内存中放在相同的地址,在硬件中可放在物理内存或交换内存。
对应上面不同的概念,Linux常用的内存相关术语有:
常驻内存(Resident memory), 表示硬件中物理内存的占用。
虚拟内存,Virtual memory,操作系统使用内存的一种抽象。
页(page),操作系统以页为单位管理(分配,回收)内存
测量程序的内存占用可以用不同的统计量。从程序的生命周期来看,可以分为最大(peak)内存占用,即时内存占用,和平均(average)内存占用。从内存的含义来讲,可以在程序本身的语境(context),或是操作系统,或是硬件的语境,比如:栈内存使用(heap memory),虚拟内存占用,物理内存占用。
测量一个程序的内存占用时,最理想的是我们知道(每时每刻)程序的四个部分(在虚拟内存中)分别有多大,对应的,在物理内存或者交换内存中占用了多少空间。但是这是一个很难达到的目标,因为:(1)操作系统以页方式分配内存,很难得到每一个页中具体多少空间被占用;(2)虚拟内存往往比物理内存大很多,当操作系统给一个运行的程序分配内存时总是提供多余程序精确需要的内存;(3)注程序在物理内存的占用加上程序在交换内存的占用构成了程序总的使用情况,但是现有的工具并不直接给出这一数值,需要手动把Resident Memory和Shared Memory相加 。
在实际情况下,通过一些现有的工具,我们往往更关心这几个易于获得统计量:
(1)max resident memory size
可以使用time -v命令(要指定全部路径,否则bash的time命令不识别-v)
注意这个测量值往往远远高于程序运行时消耗的内存。
(这是time 1.7版本的bug,time 报告的内存用量是实际的4倍,因为wait3/wait4返回的实际内存用量,单位是kbyte,但time错把单位设定为page)
本质上time使用了wait3或者wait4命令来获取程序的resources (见 ‘man wait3’)。
使用Python实现时,可以用resources module来获取。
另外,使用这个思路还可以得到程序的运行时间(用户时间,内核时间,实际时间)
(2)top 或者ps
可以查看程序的Resident Size(RES)列,Share Memory(SHR)以及Virtual Memory Size(VIRT),这一统计量默认每秒更新一次。这一方式的缺点是测量值为即时测量,每时每刻这个值都有可能变化。优点是使用起来很方便。
使用Python实现时,可以先使用multiprocesing.Process启动一个程序,然后使用psutil module,构造psutl.Process(
(3)与(2)类似,但为了更多的信息,还可以读取/proc
这是通过读取/proc/
在Python中,需要另外写程序来解释这两个文件,例如下面这样:
zhanxw@amd: ~> cat /proc/15648/status Name: takeMem State: R (running) Tgid: 15648 Pid: 15648 PPid: 15163 TracerPid: 0 Uid: 248396 248396 248396 248396 Gid: 248396 248396 248396 248396 FDSize: 256 Groups: 500 1007 1013 1017 1028 1033 248396 VmPeak: 12752 kB VmSize: 12752 kB VmLck: 0 kB VmHWM: 1756 kB VmRSS: 1756 kB VmData: 1112 kB VmStk: 136 kB VmExe: 4 kB VmLib: 3232 kB VmPTE: 40 kB VmSwap: 0 kB Threads: 1
在实践中,可以使用已有的程序,例如tmem【4】,以及lh3的runit【6】(下载udp,在runit/下,用make编译)。输出如下。
tmem ./tmem ./takeMem 1000000000 3908 3908 84 84 Allocating 1000000000 memory 988336 988336 148012 148012 988336 988336 308524 308524 988336 988336 442636 442636 988336 988336 567244 567244 988336 988336 686308 686308 988336 988336 796396 796396 988336 988336 889588 889588 988336 988336 949516 949516
runit ./runit ~/mycode/smake/takeMem 1000 Allocating 1000 memory -- CMD: ./runit /net/fantasia/home/zhanxw/mycode/smake/takeMem 1000 -- totalmem 198345252.000 kB -- available 190966956.000 kB -- free 1569216.000 kB -- retval 0 -- real 15.749 sec -- user 15.350 sec -- sys 0.170 sec -- maxrss 704.000 kB -- avgrss 704.000 kB -- maxvsize 11904.000 kB -- avgvsize 11904.000 kB
参考资料:
【1】Python resource 模块
【2】Python os 模块,介绍wait3,wait4
【3】Python multiprocessing模块,介绍了Process这个易于使用的类,与subprocess相似,但有更灵活的用法
【4】Measuring Memory Usage,介绍了用C来读取/proc/
【5】Understanding memory,非常详细的介绍Linux内存分配,优化
【6】Heng Li’s personal website 可以下载比较hash library的程序,里面有一个runint/文件夹,编译之后可以用runit来测量内存使用