std::threadとstd::vectorでboost::thread_groupのようにスレッドをまとめて起動

最近C++でのスレッド処理の勉強をしていて,C++11(C++0x)からboostを入れなくてもthreadクラスが使えるようになったようなのでそちらを使ってみる.んで,同じような処理をするスレッドをまとめてcreate/joinできるようにしたいなーと考えて色々調べていたところ,boost::thread_groupなるものがあるという事を知った.

C++, boost::thread : スレッドグループの生成と実行 | Yukun's Blog

しかしthread_groupはboostにしかないクラスのようだ.んーでも似たような処理がしたいなーと更に調べていたところ,次のサイトにある方法を使えばできそうだという事がわかった.

thread

要は,std::threadクラスを格納するvectorコンテナを作り,そこにcreateしたスレッドをpush_backしていく.終了時は,コンテナの中にあるstd::thread一つ一つに対してjoin操作を行なっていく.

以下,利用例
やたら長いが,引数にとったxzファイルの中身をただ表示するだけのプログラム.一応-nオプションでスレッド数を変更できるようになっている.

/*
 * readline_using_threads.cc
 */

#include <iostream>
#include <unordered_map>
#include <ext/stdio_filebuf.h>
#include <thread>
#include <mutex>
#include <list>
#include <vector>
#include <cstring>
#include <unistd.h>

namespace Original
{
  class Counter{
    private:
      std::list<std::string> files;
      std::mutex locker;

    public:
      Counter(std::list<std::string> input_files){
        files = input_files;
      }

      void counter(std::string filename);

      void file_allocator(){
        while(!files.empty()){
          locker.lock();
          std::string temp_filename(files.front());
          files.pop_front();
          locker.unlock();

          counter(temp_filename);
        }
        return;
      }
  };
}

void Original::Counter::counter(std::string target_file){
  // popenでコマンド実行後の出力をfdで受け取る
  std::string command("xzcat " + target_file);
  FILE *fd = popen(command.c_str(), "r");

  // streambufを作成し,istreamのコンストラクタに渡す
  __gnu_cxx::stdio_filebuf<char> *p_fb = new __gnu_cxx::stdio_filebuf<char>(fd, std::ios_base::in);
  std::istream input(static_cast<std::streambuf *>(p_fb));

  // getlineでストリームからコマンド出力を受け取る
  std::string buffer;
  while(getline(input, buffer)){
    std::cout << "id:" << std::this_thread::get_id() << " out > " << " " << buffer << std::endl;
    usleep(100);
  }
  return;
}

int main(int argc, char** argv)
{
  // デフォルトのスレッド数
  int num_of_threads = 4;

  int option;
  while((option = getopt(argc, argv, "n:")) != -1){
    switch(option){
      case 'n':{
        int num = atoi(optarg);
        if(num < 1 || num > 16){
          std::cout << "Number of threads should be 1 to 16!" << std::endl;
          exit(1);
        }
        num_of_threads = num;
        break;
      }

      case ':':
        std::cout << "option needs value!" << std::endl;
        break;

      case '?':
        std::cout << "unknown option!" << std::endl;
        break;
    }
  }
  argc -= optind - 1;
  argv += optind - 1;


  std::list<std::string> input_files;
  for(int i = 1; i < argc; ++i){
    input_files.push_back(argv[i]);
  }

  // スレッド数と処理対象ファイルの記述
  std::cout << "Number of threads: " << num_of_threads << std::endl;
  std::cout << "Processing files" << std::endl;
  for(auto iter = input_files.begin(); iter != input_files.end(); ++iter){
    std::cout << *iter << " ";
  }
  std::cout << "\n" <<std::endl;

  // threadをまとめて起動する ============================================
  Original::Counter count_object(input_files);
  std::vector<std::thread> count_threads;
  // スレッドの起動
  for(int i = 0; i < num_of_threads; ++i){
    count_threads.push_back(std::thread(std::bind(&Original::Counter::file_allocator, &count_object)));
  }
  // スレッドのjoin
  for(auto iter = count_threads.begin(); iter != count_threads.end(); ++iter){
    iter->join();
  }
  // ====================================================================
  return 0;
}


4つのスレッドで実行した結果.
ファイルの中身は,それぞれ「あいうえおあいうえおあいうえお」みたいなのが間に改行をはさんで書いてあるだけ.

$ ./readline_using_threads -n 4 test1.txt.xz test2.txt.xz test3.txt.xz test4.txt.xz
Number of threads: 4
Processing files
test1.txt.xz test2.txt.xz test3.txt.xz test4.txt.xz

id:1146394944 out > か
id:1114925376 out > あ
id:1135905088 out > ア
id:1125415232 out > さ
id:1114925376 out > い
id:1135905088 out > イ
id:1125415232 out > し
id:1146394944 out > き
id:1114925376 out > う
id:1135905088 out > ウ
id:1125415232 out > す
id:1146394944 out > く
id:1114925376 out > え
id:1135905088 out > エ
id:1125415232 out > せ
id:1146394944 out > け
id:1114925376 out > お
id:1135905088 out > オ
id:1125415232 out > そ
id:1146394944 out > こ
id:1114925376 out > あ
id:1135905088 out > ア
id:1125415232 out > さ
id:1146394944 out > か
id:1114925376 out > い
id:1135905088 out > イ
id:1125415232 out > し
id:1146394944 out > き
id:1114925376 out > う
id:1135905088 out > ウ
id:1125415232 out > す
id:1146394944 out > く
id:1114925376 out > え
id:1135905088 out > エ
id:1125415232 out > せ
id:1146394944 out > け
id:1114925376 out > お
id:1135905088 out > オ
id:1125415232 out > そ
id:1146394944 out > こ
id:1114925376 out > あ
id:1135905088 out > ア
id:1125415232 out > さ
id:1146394944 out > か
id:1114925376 out > い
id:1135905088 out > イ
id:1125415232 out > し
id:1146394944 out > き
id:1114925376 out > う
id:1135905088 out > ウ
id:1125415232 out > す
id:1146394944 out > く
id:1114925376 out > え
id:1135905088 out > エ
id:1125415232 out > せ
id:1146394944 out > け
id:1114925376 out > お
id:1135905088 out > オ
id:1125415232 out > そ
id:1146394944 out > こ

これでthread周りが少し簡潔にコーディングできるかな...

filepointerの内容をistreamで受け取る

プログラム中で実行したコマンドの出力をストリームで受け取りたい.
というのも,プログラム中でxzcatした出力を標準入力からきたもののように処理したいためである.

とりあえずpopen()を使うことで,出力内容をファイルディスクリプタとして受け取ることができることはわかった.このまま,fgetsなどを利用して読み込んでもいいのだが,出来れば標準入力みたいに受け取りたいのである.というわけで色々調べたところ,同じようなことをやっておられる方がいた.

C++でpopenで開いたプロセスをistreamから読む - 饅頭日誌 改 &#12316;となりの一茶鼓&#12316;

まさに,自分がやりたかったことだ!というわけで早速参考にさせていただいた.
そのままのではなぜか動かなかったので,istreamのコンストラクタの部分は少し変えてある.

/*
 * fd_to_stream.cc
 */
#include <iostream>
#include <ext/stdio_filebuf.h>
#include <cstdlib>

int main()
{
  // popenでコマンド実行後の出力をfdで受け取る
  FILE *fp = popen("xzcat fd_to_stream.cc.xz", "r");

  // streambufを作成し,istreamのコンストラクタに渡す
  __gnu_cxx::stdio_filebuf<char> *p_fb = new __gnu_cxx::stdio_filebuf<char>(fp, std::ios_base::in);
  std::istream input(static_cast<std::streambuf *>(p_fb));

  // getlineでストリームからコマンド出力を受け取る
  std::string buffer;
  while(getline(input, buffer)){
    std::cout << "out > " << buffer << std::endl;
  }

  return 0;
}


上記ファイルをxz圧縮したものを読み込んでみる.
実行結果

out > /*
out > * fd_to_stream.cc
out > */
out > #include
out > #include
out > #include
out >
out > int main()
out > {
out > // popenでコマンド実行後の出力をfdで受け取る
out > FILE *fp = popen("xzcat fd_to_stream.cc.xz", "r");
out >
out > // streambufを作成し,istreamのコンストラクタに渡す
out > __gnu_cxx::stdio_filebuf *p_fb = new __gnu_cxx::stdio_filebuf(fp, std::ios_base::in);
out > std::istream input(static_cast(p_fb));
out >
out > // getlineでストリームからコマンド出力を受け取る
out > std::string buffer;
out > while(getline(input, buffer)){
out > std::cout << "out > " << buffer << std::endl;
out > }
out >
out > return 0;
out > }

ちゃんと読めている.
よかったー.

mapを使って形態素にidをふる

C++の標準ライブラリであるmapを使ってシンボルにidを振っていく方法をメモ.自然言語処理をしているため,今回はシンボル=形態素ということにして書いていく.

以下のようなテストファイルに対して,重複なくidを振りたい.

//test.txt
取材

帰り


以前


買った
こと

ある
豆腐

さん

覗いたら
美味し
そうな
稲荷
寿司

売って
いた

C++では,mapという標準ライブラリを使うことで,以前使っていたPythonの辞書型のような処理を書くことが可能らしい.
mapの宣言にはテンプレートを用いて,の型を宣言する.例えば,今回は,単語をkeyとして,idをvalueとして格納したいので,std::mapのように宣言する.mapに対して逐次的にアクセスするためには,イテレータを宣言する.このイテレータは,扱う対象のmapのテンプレート型と同じ型で宣言をする.

  std::map<std::string, int> word_dictionary;         // 単語とid
  std::map<std::string, int>::iterator word_iterator; // word_dictionary を扱うイテレータ

idをふる部分では,find()メソッドによってmap内に既に対象の単語が存在するかどうかを確かめる.そして,存在しない場合にのみ,新たな要素として単語とidのペアを追加することで,重複なくidをふることが可能である.

  while(ifs && getline(ifs, word)){
    if(word_dictionary.find(word) == word_dictionary.end()){
      word_dictionary.insert(std::pair<std::string, int>(word, number));
      number++;
    }
  }

結果の表示は,先ほど作ったイテレータによる逐次アクセスで実現される.

  for (word_iterator = word_dictionary.begin(); word_iterator != word_dictionary.end(); word_iterator++){
    std::cout << word_iterator->first << " : " << word_iterator->second << std::endl;
  }


以上をまとめると,次のようなプログラムが書ける.

/*
 unigram_id.cc
 単語にidを振っていく
 */
#include <iostream>
#include <fstream>
#include <cstring>
#include <map>

int main()
{
  std::ifstream ifs("./test.txt");

  std::string word;                                   // 処理対象のword
  int id = 0;                                         // 何種類目の単語か
  std::map<std::string, int> word_dictionary;         // 単語とid
  std::map<std::string, int>::iterator word_iterator; // word_dictionary を扱うイテレータ

  // 一行ずつ単語を読み込む
  // 新しい単語の場合はidとともにmapに挿入
  while(ifs && getline(ifs, word)){
    if(word_dictionary.find(word) == word_dictionary.end()){
      word_dictionary.insert(std::pair<std::string, int>(word, id));
      id++;
    }
  }

  //結果の表示
  for (word_iterator = word_dictionary.begin(); word_iterator != word_dictionary.end(); word_iterator++){
    std::cout << word_iterator->first << " : " << word_iterator->second << std::endl;
  }

  return 0;
}

実行結果.
確かに,重複なくidをふることができた.

、 : 4
。 : 22
ある : 9
いた : 21
が : 19
こと : 8
さん : 12
そうな : 16
に : 3
の : 1
も : 6
を : 13
以前 : 5
取材 : 0
売って : 20
寿司 : 18
屋 : 11
帰り : 2
稲荷 : 17
美味し : 15
覗いたら : 14
豆腐 : 10
買った : 7

どうやら,イテレータによる逐次アクセスはkeyの昇順にアクセスされるらしい.明示的にソートしなければならないPythonの辞書型と少し異なる部分なのかな.


mapの使い方とかは以下を参考にした.

【C++】mapを使うサンプル。【STL】
http://www.cppll.jp/cppreference/cppmap_details.html


おまけ

mapを使ってペアを保存する際に,その最大保存数が気になったので調べてみた.ひとまず,以下のように様々なサイズの要素を持つmapを用意し,その最大保存数をmax_size()で出力してみる.

/*
 maxsize_of_map.cc
 mapの最大保存件数を表示
 */
#include <iostream>
#include <map>

struct Fivebytes{
  char c1;
  int i1;
};

struct Longs2{
  long l1;
  long l2;
};

struct Longs3{
  long l1;
  long l2;
  long l3;
};

int main()
{
  std::map<std::string, std::string> string_map;
  std::map<char, char> char_map;
  std::map<short, short> short_map;
  std::map<int, int> int_map;
  std::map<Fivebytes, Fivebytes> five_map;
  std::map<long, long> long_map;
  std::map<Longs2, Longs2> longs2_map;
  std::map<Longs3, Longs3> longs3_map;

  std::cout << "string: "     << string_map.max_size() << std::endl;
  std::cout << "char(1): "    << char_map.max_size()   << std::endl;
  std::cout << "short(2): "   << short_map.max_size()  << std::endl;
  std::cout << "int(4): "     << int_map.max_size()    << std::endl;
  std::cout << "five(5): "    << five_map.max_size()   << std::endl;
  std::cout << "long(8): "    << long_map.max_size()   << std::endl;
  std::cout << "longs2(16): " << longs2_map.max_size() << std::endl;
  std::cout << "longs3(32): " << longs3_map.max_size() << std::endl;

  return 0;
}

出力結果

> string: 384307168202282325
> char(1): 461168601842738790
> short(2): 461168601842738790
> int(4): 461168601842738790
> five(5): 384307168202282325
> long(8): 384307168202282325
> longs2(16): 288230376151711743
> longs3(32): 230584300921369395

括弧内はそれぞれの要素のbyte数.実際には,同じ型の要素2つをペアとしているので,その2倍の容量にしています.で,結果を見てみたのですが,どうも大きすぎる.今使ってるマシンのメモリが250G以上あることを考えてもちょっと多すぎないか.うーん,なんでだろうか.ひとまず今日のところは保留にする.(保留のままかもしれないが笑



2012/01/22 追記 --------------------------------
コメントで教えていただいたが,Pythonの辞書型とC++のstd::mapはデータ構造が違うらしい.勝手な思い込みできっと同じなんだろうと思ってた...気を付けなければ!
あと,今思えばstd::mapが木構造であることを考えれば,出力した際にkeyの順番で表示されるのももっともな気がする.

とにかく,コメントに感謝><

Galaxy S II LTE購入

今まで3年前に購入したガラケーを使っていたのですが,スペックが上がり,LTEにも対応し始めたということなので,スマートフォンに乗り換えることにしました.高い買い物なので,長い間使い続けたいという思いもあり,現状で最高のスペックをもつGalaxy S II LTEを購入することに.さすがというかなんというか,過去のスマートフォンにくらべてサクサク動いている感じがします.

いままでipod touchを持っていたので,スマホ的なデバイスに馴染みがなかったわけではないのですが,やはりiOSよりもAndroidの方が自由度が高いですね.自分好みの環境を構築するのがすごく楽しいです.地元ではLTEエリアに入っていないので,LTEの通信速度についてはわからないのですが,これは東京に帰ってからのお楽しみですね.

なんにせよ,しばらくはこの新しいおもちゃで遊べそうです(笑

boostの手動インストールメモ

実験サーバに入っているのboostのバージョンがバージョンが古すぎたので,最新のバージョンのものを自分用にインストールしました.

背景

boost/tokenizer.hppをインクルードしてコンパイルしたら,以下のようなエラーがズザーっと出た.

In file included from /usr/include/boost/mpl/apply.hpp:23:0,
from /usr/include/boost/iterator/iterator_facade.hpp:34,
from /usr/include/boost/iterator/iterator_adaptor.hpp:15,
from /usr/include/boost/token_iterator.hpp:21,
from /usr/include/boost/tokenizer.hpp:20,
from tokenize.cc:9:
/usr/include/boost/mpl/apply_wrap.hpp:81:31: エラー: トークン "(" の前に二項演算子がありません
/usr/include/boost/mpl/apply_wrap.hpp:173:31: エラー: トークン "(" の前に二項演算子がありません
In file included from /usr/include/boost/mpl/bind.hpp:27:0,
from /usr/include/boost/mpl/lambda.hpp:18,
from /usr/include/boost/mpl/apply.hpp:25,
from /usr/include/boost/iterator/iterator_facade.hpp:34,
from /usr/include/boost/iterator/iterator_adaptor.hpp:15,
from /usr/include/boost/token_iterator.hpp:21,
from /usr/include/boost/tokenizer.hpp:20,
from tokenize.cc:9:
/usr/include/boost/mpl/apply_wrap.hpp:81:31: エラー: トークン "(" の前に二項演算子がありません
/usr/include/boost/mpl/apply_wrap.hpp:173:31: エラー: トークン "(" の前に二項演算子がありません

こいつは,なんたるやと思って調べても情報がない.とりあえず,gcc4.6でコンパイルするのをやめて,gcc4.1でコンパイル...エラーはでない.これはもしやと思って,boostのバージョンを調べてみたら1.33.1.ちなみに最新版は1.48.0.うん,これはもう古いとしか考えられない.どうして実験サーバにプリインストールされているパッケージはこう古いのか.と嘆いていても仕方ないので,boostの手動インストールを試みます.

ちなみにboostのバージョンを調べる方法はここ.

銀天ライブラリ 導入方法

boostのインストール

まず,boostの最新版を手に入れます.場所は以下.

Boost C++ Libraries - Browse Files at SourceForge.net

ソースを手に入れたらビルドします.一部のライブラリはビルドしなくても使えるようですが,まぁどうせなのでちゃんとやることにします.以下のサイトを参考にしてやってみました.

Boost.Buildで複数のバージョンのgccを切り替えて使う方法 - fjnlの生存記録のような何か
Let's Boost - インストール方法
Boost C++ライブラリのビルドとインストール - reflux flow

実験サーバではgcc4.1とgcc4.6が共存していたので,bjamで使うコンパイラを指定します.設定ファイルを置く場所は,いくつかあるみたいですが,今回は$HOMEの場所に置きます.以下ではgcc4.6を指定しました.

$ cd ~
$ echo using gcc : 4.6: g++-4.6 \; > site-config.jam

次に,boostをビルドします.INSTALL_PATHには,ライブラリをインストールしたい場所を指定してください.

$ wget http://sourceforge.net/projects/boost/files/boost/1.48.0/boost_1_48_0.tar.gz/download
$ tar xzf boost_1_48_0.tar.gz
$ cd boost_1_48_0
$ ./bootstrap.sh    <- bjam, b2の生成
$ ./bjam --prefix=INSTALL_PATH --toolset=g++-4.6 <- toolsetにはsite-config.jamで設定したものを使う

これでOKかと思ったのですが,どうも指定先にインストールされていなかったので,下記のサイトにあったように./b2をやってみました.

https://sites.google.com/site/boostjp/howtobuild

$ ./b2 install --prefix=INSTALL_PATH

まぁこんな感じでインストールできたのですが,もしかしたら./bjamはいらない子だったのかも../bootstrap.shで「ビルドするためには./b2を走らせろ」とかかいてあったしなぁ.まぁとりあえずインストールできたのでよしとする.

# ./bootstrap.sh --help
# とかすると,色々設定項目があることに今更気づいた.
# 今度インストールする機会があったらもう少し考えてみよう.
# boost::pythonとか使いたい人はpythonのパスを指定する必要があるみたい
# インストールパスの指定もここですることができたから,
# もしかしたら,ここで指定することでbjamだけでもインストールできた?

boostへPathを通す

インストールは終わったのですが,このままではデフォルトで1.33.1のboostがインクルードされてしまうので,./bash_profileなどに,以下の記述をしておきます.

export CPLUS_INCLUDE_PATH=/disk5/nakashima/local/lib/gcc/x86_64-unknown-linux-gnu/4.6.2/boost/include:$CPLUS_INCLUDE_PATH

これで,今回インストールしたboostがインクルードされるようになりました.確認は,最初に書いた方法を利用すれば大丈夫です.

謹賀新年

明けましておめでとうございます.

昨日の夜は,C++のboostライブラリの導入作業をしながら年越しを迎えたという残念な感じでした.その上現在風邪気味でもあります(笑

まぁそんな感じで,2012年が絶好調のスタートとは行きませんでしたが,今年は去年以上に自分を成長させていけるといいなと思っている次第です.とりあえず,日々学ぶ姿勢を維持することだけは忘れたくないなぁ.


それと,なんか月並みな感じですが,去年は天災などもあって日本にとっていい年ではなかったと思うので,その分今年は笑顔があふれる一年になるといいですね.

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の日記