C言語で使用メモリをカウントする。

どうもひろきのだいちです。

最近、初心者にとっていちばんよい言語は・・みたいな話が出てますが、
いちばんよい言語というのはたぶん「近くに一番優しくわかりやすく丁寧に教えてくれる人のいる言語」だと思います。

別に言語なんて関係ないです。

でも、いろいろとできることが違うという意味ではC言語はすごいです。
今日はC言語を使ってプログラムの利用メモリを出力してくれるようなソフトを書いてみました。

はじめに

こんな風にランダムに生成した数字をquick sortして順番に出力するプログラムがあったとします。

$ ./qsort 10
      133557644      467172391
      587048537      700406542
      924651875      942877877
     1231884300     1236726036
     1563773570     1858205801

こんな風にランダムに生成した数字をquick sortして順番に出力するプログラムがあったとします。
これにfleafeedとつけるだけで・・・

$fleafeed ./qsort 10
      133557644      467172391
      587048537      700406542
      924651875      942877877
     1231884300     1236726036
     1563773570     1858205801

-----------fleafeed results------------
--USING STACK   :    1160 bytes
--USING HEAP    :       0 bytes
--ALLOC times   :       0
--FREE times    :       0

こんな付加情報を追加してくれるやつを作ってみました。

さらにただ、mallocして放置するというドSなプログラムを

C:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(void){
  4.    int i=0;
  5.    for(;i<100;i++){
  6.       void *p=malloc(100);
  7.       printf("%x",p);
  8.    }
  9.    printf("hello!world");
  10. }

試してみると・・・

-----------fleafeed results------------
--USING STACK   :    1076 bytes
--USING HEAP    :   10000 bytes
--ALLOC times   :     100
--FREE times    :       0

こんなかんじで、計算どおりヒープの数とmallocの数がカウントされてます。

これの作り方の前にちょっと基礎知識を。
わかる人は読み飛ばしてください。まちがった説明するかもなので><

スタックとかヒープとかってなに?

スタックとかヒープとかってのは、プログラムの管理領域であるメモリの役割につけられた名前です。

スタックは関数呼び出しや引数、関数内自動変数などを管理するメモリの領域です。
面倒なので、wikipedia先生に頼ると
スタック
コールスタック

こんなかんじのものです。

ヒープとは固定サイズじゃなくて自由に別途に確保できるところにあるメモリーです。
通常mallocでメモリーをもらってfreeで返します。

もらったものは必ず返さないといけないんですが、最近の言語ではGC(ガーベジコレクション)というステキ機能がついていて
変わりに返してくれます。連帯保証人みたいなひとです。

これだけだとあれなのでまたまた、wikipedia先生に・・
ヒープ領域
ヒープ構造

スタック編

理屈は簡単です。
一番最初に一度使われる前のスタック領域を俺色に染め上げて、
プログラムを実行し、なんらかの形で利用されると別の値になってしまうので
一番最後に俺色になっていないところの数を数え上げるという方法です。

そのために

C:
  1. #define FF_FIRST __attribute__ ((constructor))
  2. #define FF_LAST  __attribute__ ((destructor))

こんな宣言をしてmain関数より前に関数を実行させます。

C:
  1. #define FF_MAGIC 0xf1eafeed
  2. #define FF_MAX 1024*1024
  3. FF_FIRST int fleafeed_fillstack(){
  4.    int p[FF_MAX];
  5.    int i=0;
  6.    for(;i<FF_MAX;i++){
  7.       p[i]=FF_MAGIC;
  8.    }
  9. }
  10. FF_LAST void fleafeed_checkstack(){
  11.    int p[FF_MAX];
  12.    int i=0;
  13.    for(;i<FF_MAX;i++){
  14.       FF_USESTACK+=(p[i]==FF_MAGIC)?0:4;
  15.    }
  16. }

このときに埋め立てる値を0xf1eafeedとう値にしたので
このプログラムの名前はfleafeedになりました。

適当に16進数で表現できる8文字の英語をいれただけです。
意味はわかりません><

これで、スタックの仕様数はわかりました。
これを

$gcc fleafeed.c -share -fPIC -o libfleafeed.so

のようにしてコンパイルします。これでできたライブラリをリンクしてやれば、スタックの使用量がわかります。

次はヒープです。

ヒープ領域編

ヒープは、よほど無理をしないかぎり、mallocなどから呼ばれるのでこの関数をwrapperしてやればヒープの使用量はわかります。
というわけで、

C:
  1. void *malloc(size_t size){
  2.    FF_ALLOCCOUNT++;
  3.    FF_USEHEAP+=size;
  4.    return malloc(size);//本物のmalloc?
  5. }

というような感じでラッパしたいのですが、これだとmallocという名前が衝突してしまいます。
そんなわけで

C:
  1. #define GNU_SOURCE
  2. #include <dlfcn.h>
  3. static void*(*libc_malloc)(size_t);
  4. static void*(*libc_calloc)(size_t,size_t);
  5. static void (*libc_free  )(void *);
  6.  
  7. void *malloc(size_t size){
  8.    FF_ALLOCCOUNT++;
  9.    FF_USEHEAP+=size;
  10.    return (*libc_malloc)(size);
  11. }
  12. FF_FIRST int fleafeed_fillstack(){
  13.    libc_malloc=dlsym((void *)-1L,"malloc");
  14. }

こんな感じにdlsymのRTLD_NEXTをつかって、mallocのポインタをlibc_mallocという代理人にあずけておきます。
なぜかRTLD_NEXTが#defineされてなかったので即値で対応しました。

共有ライブラリを事前に読み込む

ソースコードがあればそこからビルドするときになんとかすればいいんですが、そうではないので事前にライブラリを読み込むようにしてコマンドを呼び出すという方法をとります。

alias fleafeed=LD_PRELOAD=./libfleafeed.so

こんなかんじに。LD_PRELOADはこのライブラリは最初に読み込んじゃって!という環境変数です。
これで準備は万端。

ためしにgccでコンパイルをするときの利用状況を見てみましょう。

$ fleafeed gcc test.c

-----------fleafeed results------------
--USING STACK   :    6072 bytes
--USING HEAP    :  832510 bytes
--ALLOC times   :    1776
--FREE times    :     833

-----------fleafeed results------------
--USING STACK   :    2272 bytes
--USING HEAP    : 1810165 bytes
--ALLOC times   :     863
--FREE times    :      48

-----------fleafeed results------------
--USING STACK   :    5980 bytes
--USING HEAP    : 1715525 bytes
--ALLOC times   :    1185
--FREE times    :     355

-----------fleafeed results------------
--USING STACK   :    2012 bytes
--USING HEAP    :   25998 bytes
--ALLOC times   :     128
--FREE times    :      35

-----------fleafeed results------------
--USING STACK   :    5840 bytes
--USING HEAP    :   33885 bytes
--ALLOC times   :     336
--FREE times    :     181

うわなんかいっぱい出たw

プロセス分だけ終わりがあるので、プロセスごとに出力されるようです。

最後に全体のソースコード。

C:
  1. #define GNU_SOURCE
  2.  
  3.  
  4. #include <dlfcn.h>
  5. #include <stdio.h>
  6. #define FF_FIRST __attribute__ ((constructor))
  7. #define FF_LAST  __attribute__ ((destructor))
  8. #define FF_MAGIC 0xf1eafeed
  9. #define FF_MAX 1024*1024
  10.  
  11. int FF_ALLOCCOUNT=0;
  12. int FF_FREECOUNT=0;
  13. int FF_USEHEAP=0;
  14. int FF_USESTACK=0;
  15.  
  16.  
  17. static void*(*libc_malloc)(size_t);
  18. static void*(*libc_calloc)(size_t,size_t);
  19. static void (*libc_free  )(void *);
  20.  
  21. void *malloc(size_t size){
  22.    FF_ALLOCCOUNT++;
  23.    FF_USEHEAP+=size;
  24.    return (*libc_malloc)(size);
  25. }
  26. void *calloc(size_t nmemb,size_t size){
  27.    FF_ALLOCCOUNT++;
  28.    FF_USEHEAP+=size*nmemb;
  29.    return (*libc_calloc)(nmemb,size);
  30. }
  31. void free(void *ptr){
  32.    FF_FREECOUNT++;
  33.    (*libc_free)(ptr);
  34. }
  35. /*void *realloc(void *ptr,size_t size){
  36. }*/
  37.  
  38. FF_FIRST int fleafeed_fillstack(){
  39.    int p[FF_MAX];
  40.    int i=0;
  41.  
  42.    libc_malloc=dlsym((void *)-1L,"malloc");
  43.    libc_free=dlsym((void *)-1L,"free");
  44.    libc_calloc=dlsym((void *)-1L,"calloc");
  45.  
  46.    for(;i<FF_MAX;i++){
  47.       p[i]=FF_MAGIC;
  48.    }
  49. }
  50. FF_LAST void fleafeed_checkstack(){
  51.    int p[FF_MAX];
  52.    int i=0;
  53.  
  54.    for(;i<FF_MAX;i++){
  55.       FF_USESTACK+=(p[i]==FF_MAGIC)?0:4;
  56.    }
  57.    fflush(stdout);
  58.    fprintf(stderr,"\n-----------fleafeed results------------");
  59.    fprintf(stderr,"\n--USING STACK\t:%8d bytes",FF_USESTACK);
  60.    fprintf(stderr,"\n--USING HEAP\t:%8d bytes",FF_USEHEAP);
  61.    fprintf(stderr,"\n--ALLOC times\t:%8d",FF_ALLOCCOUNT);
  62.    fprintf(stderr,"\n--FREE times\t:%8d\n",FF_FREECOUNT);
  63. }

« プログラマのための英文法(笑) – twitterの正しい使い方 »

1 Comment »

  1. [...] 次はメモリの消費量です。 これは昨日作ったfleafeedを使ってみます。 C言語でメモリ使用量をカウントする! [...]

    Pingback: 日本野望の会-Yabooo.org » Javascriptでカプセル化のコスト | – 07. February 2008 @ 9:33 pm

Leave a comment

 

WP-Design: Vlad -- Powered by WordPress -- XHTML 1.0