GCC-4.6の stack-usageを使ってみた

GCC 4.6 RCが 3月14日に出ました。なんでもうすぐ正式版も出るかと
思います。Changlogを見ていると気になる機能がありました。それが
stack-usage. 関数ごとのスタックの使用量が確認できるというものです。
これは Adaだとだいぶ前から実装されていたんですけど、4.6からは
どの言語からでも利用することができます。

環境

Ubuntu 10.10 x64

GCC4.6のインストール

GCC 4.5.1をビルドする - Life is very short
GCC 4.5.2をビルドする - Life is very short

と同じなので、これらの記事を参照してください。
準備さえできれば以下のコマンドで作成できます。

  % tar xf archives/gcc-4.6.0-RC-20110314.tar.bz2
  % cd gcc-4.6.0-RC-20110314
  % mkdir build ; cd build
  % ../configure --enable-languages=c --with-gmp=/usr/local \
  --with-mpfr=/usr/local --with-mpc=/usr/local --with-ppl=/usr/local \
  --with-cloog=/usr/local --prefix=/home/syohei/local/gcc-4.6.0 \
  --disable-nls --disable-multilib
  % make
  % make install
  % export PATH=/home/syohei/local/gcc-4.6.0/bin:$PATH

試す

以下のコードで試します。何の意味もないコードですが、最適化を
ごまかすためにやってます。

#include <stdio.h>
#include <stdlib.h>

static int func(int a)
{
    int array[1024];
    int i, sum = 0;

    for (i = 0; i < a; i++) {
        sum += array[i];
    }

    return sum;
}

int main (int argc, char *argv[])
{
    int a;
    if (argc >= 2) {
        a = atoi(argv[1]);
    } else {
        a = 1024;
    }

    return func(a);
}

コンパイルします。

  % gcc -O2 -fstack-usage stack_usage.c

するとカレントディレクトリに (ソース名).suというファイルが生成
されます。このファイルにスタック情報が書かれています。内容は以下のとおりです。

stack_usage.c:4:12:func	3984	static
stack_usage.c:16:5:main	16	static

まだ読み方があまりわかっていないのですが、funcという関数は静的に
3984バイト割り当てられており、mainは静的に 16バイト割り当てられて
いることがわかります。最適化をかけたせいで 4096ではないですね。


ファイル名のあとフィールドですが、一つ目は行数なんだけど、二つ目は
関数のカラムだそうです。(@さん情報ありがとうございます。)

ドキュメント

http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html#Debugging-Options

にありますが、まだデベロップメントフェーズなので、そのうちちゃんと
書かれるのではないかと思います。

おまけ1

本当は Linuxカーネルで試してみたかったんだけど、makeに失敗して
しまったので、Perl-5.13.10のビルド時に -fstack-usageをつけてみた。

  % sh Configure -Dcc=gcc -Doptimize='-O3 -pipe -fstack-usage' \ 
    -Dusedevel -des
  % make

スタック使用量の多いものを見てみると以下のような感じでした。

  % cat **/*.su | sort -n -r -k2 |head -n10
  Cwd.c:521:1:XS_Cwd_abs_path            16640    static
  bsd_glob.c:508:1:glob0                 16464    static
  sv.c:7492:1:Perl_sv_gets                8464    static
  bsd_glob.c:722:1:4                      8352    static
  bsd_glob.c:328:1:globexp2               8288    static
  bsd_glob.c:220:1:bsd_glob               8240    static
  huffman.c:63:6:BZ2_hbMakeCodeLengths    5264    static
  blocksort.c:751:6:1                     4800    static
  pp_sys.c:1577:1:Perl_pp_sysread         4288    static
  util.c:3385:1:Perl_find_script          4224    static

関数名がちゃんと出ないのがある。
bsd_glob.c(File::Glob)の4っていうのは glob3で
blocksort.c(Compress::Raw::Bzip2)の1っていうのは mainSort関数です。
ソースを見ると大抵 MAXPATHLENの長さの配列の確保が要因のようですね。

testgen

testgeテストスイートの結果は以下の通り

                === gcc Summary ===

# of expected passes            63844
# of unexpected failures        12
WARNING: gcc_version failed:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/home/syohei/local/gcc-4.6.0/libexec/gcc/x86_64-unknown-linu
x-gnu/4.6.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: ../configure --enable-languages=c --with-gmp=/usr/local --with-
mpfr=/usr/local --with-mpc=/usr/local --with-ppl=/usr/local --with-cloog=/usr/lo
cal --prefix=/home/syohei/local/gcc-4.6.0 --disable-nls --disable-multilib
Thread model: posix
gcc version 4.6.0 20110314 (prerelease) (GCC) 

まとめ

GCC 4.6の stack-usageについて紹介しました。組み込みではメモリはこんな
時代でも貴重な資源なので、スタック使用量というのは重要なものです。
なんでちゃんと計測を行う必要があります。それが少し楽になったという
ことですね。


課題としては、どのコールフローのときにスタックの使用量が最大になるかと
いうことです。その辺も自動でやってくれればいいのですが、今は関数レベル
なのでその辺はユーザが工夫する必要がありそうです。コールグラフ生成
ツール等と組み合わせればなんとかなるのではないかと思いますので、
そのあたりを調査しようかと思ってます。

(追記)dynamicに割り当てられる場合

dynamicにスタックを割り当てるってなんだって思って少し調べてみました。
C99だと配列のサイズに変数が指定できるからそれを使えば dynamicになり
ました。サンプルは以下のとおりです。

#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[])
{
    int n, sum = 0;

    if (argc >= 2) {
        n = atoi(argv[1]);
    } else {
        n = 1024;
    }

    int a[n];
    for (int i = 0; i < n; i++) {
        sum += a[i];
    }

    printf("sum = %d\n", sum);

    return 0;
}

コンパイル

  % gcc -std=c99 -fstack-usage stack_dynamic.c

得られた stack_dynamic.suを見ると以下のようになりました。

stack_dynamic.c:4:5:main        112     dynamic

112というのはどこから出た数字なんだろう?というよりこのようなコードの
場合、どうやってスタックの処理をしているんだろう?そのあたりの勉強が
必要そうですね。