Linux プログラムのメモリ消費量を測る

結論: Linux プログラムのメモリ消費量を見積もるには /proc/pid/status の VmHWM が良いと思います。(コメントで教えていただいたが、`$ /usr/bin/time -f "%M" プログラム` のようにすると簡単に測れます)

とある Linux プログラムのメモリ消費量を測りたいのだが、色々な測り方があって微妙に結果が違うので調べてみた。

* ps コマンド
* proc ファイルシステム /proc/pid/status の結果
* Valgrind というツールを使う

の３つの方法を比較して見る。以下の単純なプログラムを使った。

```
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#define HEAP_SIZE  800000000
#define USED_SIZE  400000000
#define STACK_SIZE   2000000

int main(void)
{
    char * heap;
    char stack[STACK_SIZE];
    char dummy[2];
    
    heap = malloc(HEAP_SIZE);
    memset(heap, 0, USED_SIZE);

    printf("Heap: %d bytes, Used: %d byes, Stack: %d bytes, PID: %d\n", HEAP_SIZE, USED_SIZE, STACK_SIZE, getpid());
    printf("Press enter.\n");
    fgets(dummy, 2, stdin);

    free(heap);
    return 0;
}
```

このプログラムでは、malloc で 800 MB メモリを割り当てるがそのうち 400 MB しか使わない。ついでにスタックを 2MB 確保する。という事をやっている。動作中を観察したいのでその後 Enter を押すまでプログラムを止めている。これで色んな方法でメモリを測ってみる。

```
$ ./simple-memory
Heap: 800000000 bytes, Used: 400000000 byes, Stack: 2000000 bytes, PID: 26484
Press enter.
```

別のシェルで

```
$ ps -o pid,rss,sz,vsz,command -p 26484
  PID   RSS    SZ    VSZ COMMAND
26484 390936 196064 784256 ./simple-memory

$ cat /proc/26484/status | grep -e VmHWM -e VmRSS -e VmSize -e VmStk -e VmData -e VmStk -e VmExe
VmSize:	  784256 kB
VmHWM:	  390936 kB
VmRSS:	  390936 kB
VmData:	  781288 kB
VmStk:	    1964 kB
VmExe:	       4 kB
```

マニュアルを調べると各数値の意味は以下の通り

| ps  | proc   | 結果 | 意味 |
|-----|--------|------|-----|
| RSS | VmRSS  | 390936 | 物理メモリサイズ消費量 |
|     | VmHWM  | 390936 | 最大物理メモリサイズ |
| SZ  |        | 196064 | テキスト + データ + スタック? |
| VSZ | VmSize | 783280 | 仮想メモリ領域 |
|     | VmData | 781288 | data size |
|     | VmStk  | 1964   | stack size |
|     | VmExe  | 4      | text size |

これを見ると、ps の結果の一部は proc と同じ事が分かる。
このうちどれを「メモリ消費量」とみなすかは良くわからないのだが、「実際に必要とされているメモリ」という意味では VmRSS (その瞬間の物理メモリ) または VmHWM (最大で使ったメモリ) で良いのではと思う。VmStk はスタックサイズが出る。

ここで面白いのは、今回 malloc で割り当てた 800 MB のうち実際は半分しか書き込んでいないのだが、VmSize として割り当てたメモリが、VmRSS として書き込んだメモリが表示された。という事は malloc しただけでは物理メモリは使われないようだ。

さらに Valgrind で測ると次のようになる。

```
$ valgrind --tool=massif --time-unit=B --stacks=yes --max-stackframe=2000028 ./simple-memory
...
Heap: 800000000 bytes, Used: 400000000 byes, Stack: 2000000 bytes, PID: 8266
Press enter.

==8266== 

$ ms_print massif.out.8266
...
--------------------------------------------------------------------------------
Command:            ./simple-memory
Massif arguments:   --time-unit=B --stacks=yes
ms_print arguments: massif.out.8266
--------------------------------------------------------------------------------

    MB
764.9^                                    :                                   
     |                                    #:::::::::::::::::::::::::::::::::: 
     |                                    #                                   
     |                                    #                                   
     |                                    #                                   
     |                                    #                                   
   0 +----------------------------------------------------------------------->GB
     0                                                                   1.494
...
--------------------------------------------------------------------------------
  n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 74    802,097,776      802,002,440      800,000,000         2,024    2,000,416
 75    802,097,776      802,002,440      800,000,000         2,024    2,000,416
99.75% (800,000,000B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->99.75% (800,000,000B) 0x85B0: main (simple-memory.c:16)
```

Valgrind は malloc を横取りして計測しているらしく、malloc した分のメモリを正確に表示する。Valgrind はコードの中で malloc していても実際に使われないメモリがある場合、使われるメモリよりも多めに報告する可能性がある。

この場合最大が heap + stack で 802,002,440 (764.9 MB) という事で、 --stacks=yes オプションを付けるとアスキーアートのグラフに出てくるメモリサイズはスタックサイズも反映している事が分かる。