GNU timeでプロセスの最大メモリ使用量を取得するシェルスクリプトを書いてみた

GNU timeコマンドで,プロセスの最大メモリ使用量(Maximum Resident Set Size:以下,RSS)が取得できることを教えていただいた.例えば'ls'のRSSが知りたい場合にはこんな感じ.

$ /usr/bin/time -f "%M KB" ls
> Dir1  Dir2  file1  file2  file3   <- lsの実行結果(標準出力)
> 3424 KB                           <- timeの実行結果(エラー出力)

ちょこっと解説すると,lsコマンドが普通に実行された後,標準エラー出力RSSが表示されます.まぁこのコマンドについては,いろいろな方がちょくちょく紹介しているようです.ちなみにGNU timeはフルパス(/usr/bin/time)で呼び出してください.


で,ちょこちょこ使ってみたんですが,便利だなーと思う反面,少々柔軟性に欠けると思いました.例えば,KB単位でしかサイズを返してくれなかったり,解析対象のコマンドの実行結果も出力に現れるので邪魔だなぁと思ってみたり(まぁこれは/dev/nullへリダイレクトすればいいんですが)

という経緯で,ちょっと手を動かしてシェルスクリプトを書いてみました.どちらかというとシェルスクリプトの勉強も兼ねてやってみた,という感じなので,出来の方は期待できませんが(笑

#!/bin/sh
# mem.sh
# GNU GPL version 2 copyright@N_Nao

# オプションの取得
while getopts :M opt
do
    case $opt in
        "M") size_f="TRUE";;   # MB表示を行う
        *) error_f="TRUE";;
    esac
done

# 未知のオプションに関するエラー処理
if [ $error_f ]
then
    echo "Unknown option:"
    echo "    if you don't know how to use,"
    echo "    please use -h option to show the help."
    exit
fi

# コマンドを取得
COMMAND=""
while [ $1 ]
do
    case $1 in
        -*) ;;
        *)  COMMAND=$1
            shift
            while [ $1 ]
            do
                COMMAND=$COMMAND' '$1
                shift
            done
            break;;
    esac
    shift
done

# 使用メモリ量をtimeコマンドで取得
MEM=`/usr/bin/time -f "\n%M" $COMMAND 2>&1 > /dev/null | tail -n 1`

# 結果の表示
if [ $size_f ]
then
    MEM=`echo "scale=2; $MEM / 1024" | bc`   # 1024で割りMBへ
    echo \'$COMMAND\' \-\> Max RSS = $MEM MB.
else
    echo \'$COMMAND\' \-\> Max RSS = $MEM KB.
fi


これを実行すると,以下のように-Mオプションな有無で表示するオーダを変更することができます.

$ ./mem.sh ls
> 'ls' -> Max RSS = 3424 KB.
$ ./mem.sh -M ls
> 'ls' -> Max RSS = 3.35 MB.


次に,動作を検証してみます.
以下のような,メモリを適当にがめてきて,使用メモリ量のおおよその見積もりを出力するようなプログラムを用います.ついでなので,getrusage関数による,RSSの取得もやっておきます.getrusage関数では,プロセスの様々な情報が取得できるのですが,今回はRSSの情報を取ってきて出力しているだけです.

詳しくは→Man page of GETRUSAGE

// test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/resource.h>

int main(int argc, char **argv)
{
  // 大量のメモリ領域を確保
  long i;
  long arraySize = atol(argv[1]);
  long *array = (long *)malloc(sizeof(long) * arraySize);
  for(i = 0; i < arraySize; i++){
    array[i] = 100;
  }
  // 最大消費メモリ量のおおよその見積もり
  printf("About %lf MB.\n", arraySize * sizeof(long) / 1024.0 / 1024);


  // getrusage関数による最大消費メモリ量のチェック
  int chk;
  struct rusage usage;
  chk = getrusage(RUSAGE_SELF, &usage);
  if(chk != 0){
    printf("error\n");
    exit(-1);
  }
  // このプロセスが消費した最大メモリ領域
  printf("Max RSS = %lf MB\n", usage.ru_maxrss / 1024.0);

  return 0;
}

実行結果↓

$ ./test 1000000
> About 7.629395 MB.           <- がめてきたメモリ量の見積もり
> Max RSS = 8.074219 MB                 <- getrusage関数の結果
$ ./mem.sh -M ./test 1000000
> './test 1000000' -> Max RSS = 32.35 MB.      <- mem.shの結果

$ ./test 10000000
> About 76.293945 MB.
> Max RSS = 76.750000 MB
$ ./mem.sh -M ./test 10000000
> './test 10000000' -> Max RSS = 307.00 MB.

$ ./test 100000000
> About 762.939453 MB.
> Max RSS = 763.390625 MB
$ ./mem.sh -M ./test 100000000
> './test 100000000' -> Max RSS = 3053.59 MB.

$ ./test 1000000000
> About 7629.394531 MB.
> Max RSS = 7629.843750 MB
$ ./mem.sh -M ./test 1000000000
> './test 1000000000' -> Max RSS = 30519.42 MB.

あれ?
なんか全てにおいておよそ4倍の値が出てるんですけど...
おかしいな,と思ってwebで調べてたら,以下のような記事を見つけました.

marisa-build のメモリ消費はどのくらい? - やた@はてな日記

timeコマンドで得られるRSSがgetrusage()で得られる値の4倍...だと...
ん〜なんでだろう.なんか気持ち悪いけど,mem.shの最後の部分でさらに4で割っておけば使えるってことかなぁ.というかtimeコマンドのバグなのだろうか.今度時間があるときにtimeコマンドのソースを読んでみてもいいかもしれない.

ソースはここにあった→ Time - Free Software Directory


[注記]
ちなみに,こんな怪しいシェルスクリプト使えないよ!って方のために,もっときっちりと調べて,RSSを測定するプログラムを書かれている方がいますので,付記しておきます.

(Mac OS X / LINUX での) 外部コマンドの消費メモリのモニタリング - ny23の日記