株式会社シジャム・ビーティービー

cell

cellutils で性能評価 take.1

実機 (PS3) も入手でき、PS3 用 Linux (FC5) のインストールも完了という ことで、今回はバスの性能を測ってみようと思います。誰かもう作っているだろ うということで情報収集したところ、やっぱりありました。

試してみたのは rev:68 です。見てみると他にもいろいろなツールが入って いるのですが、基本的に IBM blade 等の 8 つの SPE が使用できる環境を前提に しているようで、 PS3 では 6 SPE なのでそのままでは使えません。ということ で、今回は bandwidth という RAM-LS 間の転送性能を測定するソフトに修正を加 えて、PS3 用に SPE 数が 1-6 まで使用 (-n) できるようにして、ついでにget、 put の選択機能 (-d) と転送に使用するファイルの領域を制限する機能(-f) をつ けてデータを取ってみました。

実際に試してみたい方は cellutils を co して以下の patch を適用し、 bandwidth を make してください。

patch-cellutils-r68-0.1

bandwidth 概要

  • 16MB のファイルを作成し、RAM -> LS (get)、LS -> RAM (put) を行う。 よって転送するデータのサイズは 32MB。
  • 使用する SPE 数によって上記のファイルを等分割して各 SPE が転送。よっ て SPE の数を増やしても転送するデータサイズは変わらない。
  • 上記の転送にどれだけ時間がかかるかを PPE 側で gettimeofday() で測定。 (測定結果は結構ばらつきがあります)
  • SPE 数が 1, 2, 4 以外だとバグるので 1-6 でいけるように修正。
  • get だけ、put だけができるように機能拡張 (-d)。この場合のデータサイズは 16MB。
  • TLB の影響を見るために、各 SPE がファイルの分担部分の先頭 32KB のみ を繰り返し転送できるよう機能拡張 (-f)。
  • なお、上記拡張機能を使用した場合はデータの verify (-v) は正しく動作 しません。

実行結果

$ ./main -n ? -d 3
SPE num direction file range bandwidth (GB)
1 get,put 16MB 13.543665
2 get,put 16MB 18.396072
3 get,put 16MB 18.792736
4 get,put 16MB 18.824366
5 get,put 16MB 18.824366
6 get,put 16MB 18.882629
$ ./main -n ? -d 1
SPE num direction file range bandwidth (GB)
1 get 16MB 10.198916
2 get 16MB 17.331835
3 get 16MB 19.633957
4 get 16MB 20.397833
5 get 16MB 20.397833
6 get 16MB 20.373062
$ ./main -n ? -d 2
SPE num direction file range bandwidth (GB)
1 put 16MB 15.258950
2 put 16MB 19.668484
3 put 16MB 19.996683
4 put 16MB 20.092474
5 put 16MB 19.960995
6 put 16MB 19.913609
$ ./main -n ? -d 3 -f
SPE num direction file range bandwidth (GB)
1 get,put 32KB 13.957749
2 get,put 64KB 20.360701
3 get,put 96KB 21.250431
4 get,put 128KB 21.277382
5 get,put 160KB 21.250431
6 get,put 192KB 21.183355
$ ./main -n ? -d 1 -f
SPE num direction file size bandwidth (GB)
1 get 32KB 13.288884
2 get 64KB 22.148140
3 get 96KB 23.497503
4 get 128KB 23.663208
5 get 160KB 23.431866
6 get 192KB 23.350336
$ ./main -n ? -d 2 -f
SPE num direction file size bandwidth (GB)
1 put 32KB 21.037260
2 put 64KB 22.550022
3 put 96KB 23.546970
4 put 128KB 23.399187
5 put 160KB 23.285519
6 put 192KB 23.188963

簡単な考察

16MB と 32KB*? (-f) の場合、32KB*? のほうが速い

  • 多分 TLB の変換回数の差が効いている。
  • hugetlbfs を使用すると新たな事がわかるかもしれないが、FC5 の kernel は disable になっているのでやってない。

SPE が 1 (or 2) つの時の bandwidth が少ない

  • ソフト自体の欠陥。このソフトは一旦 DMA 転送を行うと全てが終了するま で待ち、それから次の DMA 転送を行うため、効率が良くないです。
  • このソフトは SPE 毎に最大 4 つの DMA 転送を行いますが、スループット を考慮するとバスの帯域が埋まりきっていない。

get と put を同時に行うよりもどちらか一方のみのほうが若干速い

  • メモリコントローラの仕様を理解していないので何とも言えない。

SPE が 1 つのときの get と put の速度差が大きい

  • 論文に差があるということは書いてあった気がしますが、ここまで大きかっ たかな、、謎です。

そういえば、こんな論文もあるので読み直してみます。

このソフトを土台に機能を拡張するのはいろいろな面から厳しいものがある ので、やっぱり自分で作るかなあ、、

さて、RDT261WH のセットアップでもするか、、

libspe and libspe2 december release

libspe-1.2.0, libspe2-2.0.1 がリリースされました。tarball は以下のメールに添付されています。

PS3Linux is merged with linus

今週から v2.6.20 に向けて新機能マージ週間が始まっていますが、PS3 用 kernel patch が linus tree (upstream) にマージされました。詳細は以下をご覧下さ い。

その他のマージされていないものは以下にあるようです。

PS3Linux and libspe2

PS3Linux キタ

他にも spe-samples-1.0.tar.bz2 とか CELL-Linux-CL_20061110-ADDON.iso とか。

libspe2 のプロトタイプも来てます。

libc 解析 take.4 (libspe)

"PPE Serviced SPE C Library Functions" の 4 回目は libspe の処理につ いて解説します。

4.2 PPE Serviced SPE C Library Functions

  1. The SPE constructs a local store image of the input and outout parameters.
  2. The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
  3. The SPE executes a Stop and Signal instruction.
  4. The PPE detects the Stop and Signal.
  5. The PPE invoke a specialist to service the request according to the message opcode.
  6. The PPE services the requested function.
  7. The PPE returns results in the local store image array.
  8. The PPE resumes SPE execution.
  9. The SPE returns that results back to the caller.

この内、libspe で行うのは 5., 6., 7., 8. です。

1 回目に説明した通り、spu_run システムコールの返り値として SPE の stop 命令のオペランドが返ります。また、2, 3 回目に説明した通り、SPE の stop 命令の次のアドレスに OPCODE と 引数へのアドレスが格納されており、こ れらを以下のようにして処理していきます。

  • lispe の spe_create_thread() API で生成された PPE thread の本体であ る do_spe_run() が spu_run システムコールを発行して SPE thread を実行し ています。SPE thread が stop 命令を発行すると、SPE thread が停止し、 spu_run システムコールから復帰。
  • do_spe_run() は、返り値に含まれる stop 命令のオペランド (OPCLASS) を 見ることで、対応する libc のオペクラスハンドラ ( default_c99_handler() or default_posix1_handler() ) を実行。
  • オペクラスハンドラは、stop 命令の次に書かれた OPCODE を見ることで対 応する libc のオペコードハンドラ (default_{c99,posix1}_handler_*) を実行。
  • オペコードハンドラは libc 関数を実行し、実行結果 (返り値と errno) を SPE のメモリイメージに格納。
  • do_spe_run() が spu_run システムコールを発行して SPE を再実行。

以下は do_spe_run() の関連コードを抜粋したものにコメントで解説を付け たものです。

int do_spe_run (void *ptr)
{
        :
    do {
        // SPE thread を (再) 実行
        ret = spe_run(runfd, &npc, &status);
        // ret は SPU Status Register の値が入っている
        // - 下位 16 bit [0:15] が状態を表す値
        // - 上位 14 bit [16:29] が stop 命令のオペランド
        // よって code は stop 命令のオペランドとなる
        code = ret >> 16;
            :
        if (ret < 0) {
            :
        // code は libc のハンドラの場合は以下の値を取る
        // C の関数の場合:     SPE_C99_CLASS(0x2100)
        // POSIX の関数の場合: SPE_POSIX1_CLASS(0x2101)
        } else if ((code&0xff00)==0x2100) {
            int callnum = code&0xff;
                :
            if (!handlers[callnum])
            {
                :
            }
            else
            {
                // 対象となるオペクラスハンドラを実行
                // ちなみにオペクラスハンドラは 256 個作れますが、
                // 2 つしか使ってません
                int (*handler)(void *, unsigned int);
                handler=handlers[callnum];
                rc = handler(thread_store->mem_mmap_base, npc);
                if (rc) 
                {
                    :
                }
                else
                {
                    // npc がスタック上の bi 命令を指すように変更
                    npc += 4;
                }
            }
        }
        else if (code < 0x2000)
        {
            :
        }
    // 0x21?? の場合はエラーではなく、libc のハンドラ実行依頼
    // なので SPE thread を再実行するためにループの先頭へ
    } while (((code & 0xff00) == 0x2100 || ... );
}

お次は C99 のオペクラスハンドラ。

int default_c99_handler(unsigned long *base, unsigned long offset)
{
    int op, opdata; 

    if (!base) {
	DEBUG_PRINTF("%s: mmap LS required.\n", __func__);
	return 1;
    }
    // stop 命令の次のアドレスの値から OPCODE を得る
    offset = (offset & LS_ADDR_MASK) & ~0x1;
    opdata = *((int *)((char *) base + offset));
    op = SPE_C99_OP(opdata);
    // 値のチェック
    if ((op <= 0) || (op >= SPE_C99_NR_OPCODES)) {
        DEBUG_PRINTF("%s: Unhandled type %08x\n", __func__,
                     SPE_C99_OP(opdata));
        return 1;
    }

    // オペコードハンドラ実行
    default_c99_funcs[op-1] ((char *) base, opdata);

    return 0;
}

そして getc() に対応するオペコードハンドラである default_c99_handler_getc() です。

/**
 * default_c99_handler_getc
 * @ls: base pointer to SPE local-store area.
 * @opdata: per C99 call opcode & data.
 *
 * SPE C99 library operation, per: ISO/IEC C Standard 9899:1999,
 * implementing:
 *
 *	int getc(FILE *stream);
 */
int default_c99_handler_getc(char *ls, unsigned long opdata)
{
    // 引数へのポインタ arg0 を得る
    // SPE は 128 bit 単位であるため、arg0 はこれを吸収するため
    // の構造体 spe_reg128 へのポインタとなっている
    DECL_1_ARGS();
    // 返り値と errno を格納するポインタ (arg0 と同じ) を得る
    DECL_RET();
    FILE *stream;
    int rc;

    DEBUG_PRINTF("%s\n", __func__);
    CHECK_C99_OPCODE(GETC);
    // 引数から stream を得る
    stream = get_FILE(arg0->slot[0]);
    // getc() を実行
    rc = getc(stream);
    // 返り値 rc と errno を SPE のメモリイメージに格納
    PUT_LS_RC(rc, 0, 0, errno);
    return 0;
}

getc() 実行時の SPE thread スタックはこうなります。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |
         |------------|
         |            |
         |------------|
         |    lnop    |
         |    bi $2   |
         |   opdata   |        <---- ncp
         |    stop    |
         |------------|
         |   slot[3]  |   * slot[] は各 32 bit で有効なデータがない状態
         |   slot[2]  |     を表しています
         |   slot[1]  |
         |   stream   | <---- arg0, ret
         `------------'
                        low memory

getc() 実行後に 返り値 rc と errno を格納し、do_spe_run() 内で npc を +4 した後の SPE thread スタックに以下のようになります。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |
         |------------|
         |            |
         |------------|
         |    lnop    |
         |    bi $2   |        <---- ncp
         |   opdata   |
         |    stop    |
         |------------|
         |    errno   |
         |   slot[2]  |
         |   slot[1]  |
         |     rc     | <---- arg0, ret
         `------------'
                        low memory

可変引数関数の場合も同様ですが、以下に fprintf() 実行時 (PPE では vfprintf が実行されるため、対応するオペコードハンドラは default_c99_handler_vfprintf() となる) のスタックのみ示しておきます。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |<-----. <---- input SP
         |------------|      |
         |  Reg 79    |      |
         |------------|      |
         |  Reg 78    |      |
         |------------|      |
         //   ...    //      |
         |------------|      |
         |  Reg  6    |      |
         |------------|      |
         |  Reg  5    |<--.  |
         |------------|   |  |
         |   slot[3]  |   |  |
         |   slot[2]  |   |  |
         |   slot[1]  |   |  |   
         |caller_stack|------'
         |------------|   |
         |   slot[3]  |   |
         |   slot[2]  |   |
         |   slot[1]  |   |
         |  next_arg  |---'
         |------------| 
         |   slot[3]  |
         |   slot[2]  |
         |   slot[1]  |
         |   format   | <---- arg1
         |------------|
         |   slot[3]  |
         |   slot[2]  |
         |   slot[1]  |
         |   stream   | <---- arg0, ret
         |============|
         |            |
         |------------|
         |            |
         |------------|
         |    lnop    |
         |    bi $2   |
         |   opdata   |        <---- ncp
         |    stop    |
         `------------'
                        low memory

なお、現在は公開されてませんが、spe.c 内には

register_handler(void * handler, unsigned int callnum )

なる関数があり、ユーザー独自のハンドラを登録できるようにすることが検 討されていた形跡はあります。が、libspe の API としての公開はされていませ ん。

libc 解析 take.3 (可変引数関数)

"PPE Serviced SPE C Library Functions" の 3 回目、可変引数関数の場合 について解説します。

可変引数関数の場合

以下は fprintf() を例に説明します。まずは、生成されたソースコード (fprintf.S) です。

#include "spe_c99_ops.h"
#include "spe_posix1_ops.h"

#define OPCLASS SPE_C99_CLASS
#define NAME    fprintf
#define PARMS   2
#define OPCODE  SPE_C99_VFPRINTF
#define RETVAL  1

#include "spu_syscall_va.S"

非可変引数関数の場合は spu_syscall.S をインクルードしていましたが、可 変引数関数の場合は spu_syscall_va.S をインクルードしています。

注意すべき点としては、非可変引数関数と同様に命令列をスタック上にセー ブして、このスタック上の命令が実行されること以外にも、

可変引数関数の場合、引数がどれだけあるかわからないので、引数の書いて ある可能性のあるレジスタ (つまり全ての揮発性レジスタ) を全てスタックにセー ブする必要があります。

レジスタをセーブする命令もスタック上で実行されますが、メモリ使用量を 削減するため?に命令自体の書き換えを行っています。

これらのレジスタは va_list 形式 (パラメータと呼び出し関数のスタックへ のアドレス) で管理。

というところでしょうか。では実際に逆アセ結果を見てみます。まず前半の レジスタをセーブする部分です。

00000000 :
 0:   40 fd 70 02     il      $2,-1312     // save_regs_1: の命令列へのスタック相対値をロード
 4:   24 ff c0 cf     stqd    $79,-16($1)  // 内部使用するレジスタの値を先にセーブ
 8:   24 ff 80 ce     stqd    $78,-32($1)  //
 c:   24 ff 40 cd     stqd    $77,-48($1)  //
10:   24 ff 00 cc     stqd    $76,-64($1)  //
14:   33 80 0f cf     lqr     $79,90       // save_regs_1: の命令列を $79 にロード
18:   18 00 80 cc     a       $76,$1,$2    // save_regs_1: の命令列をおくアドレスをロード
1c:   33 80 10 ce     lqr     $78,a0       // save_regs_2: の命令列を $78 にロード
20:   42 00 09 02     ila     $2,12        // 命令列 (save_regs_*) のループ回数をロード
24:   33 80 11 cd     lqr     $77,b0       // save_regs_3: の命令列を $77 にロード
28:   24 00 26 4f     stqd    $79,0($76)   // 命令列 (save_regs_1) をスタックにセーブ
2c:   24 00 a6 4d     stqd    $77,32($76)  // 命令列 (save_regs_3) をスタックにセーブ
30:   1c f0 00 cd     ai      $77,$1,-64   // レジスタをセーブするアドレスをロード
34:   00 40 00 00     sync
38:   35 20 26 4f     bisl    $79,$76      // スタック上の命令列にジャンプ
3c:   1c 04 26 cd     ai      $77,$77,16
      ...

00000090 :
90:   24 00 66 4e     stqd    $78,16($76)  // 命令列 (save_regs_2) をスタックにセーブ
94:   00 40 00 00     sync
98:   1c ff c1 02     ai      $2,$2,-1     // ループカウンタをデクリメント
9c:   1c ff 27 4e     ai      $78,$78,-4   // 命令列 (save_regs_2) のレジスタ指定値を -4

000000a0 :
a0:   24 ff e6 cb     stqd    $75,-16($77) // 4 つのレジスタをセーブ
a4:   24 ff a6 ca     stqd    $74,-32($77) // レジスタ番号はループ毎に -4 される
a8:   24 ff 66 c9     stqd    $73,-48($77) // 1 回目: 75, 74, 73, 72
ac:   24 ff 26 c8     stqd    $72,-64($77) // 2 回目: 71, 70, 69, 68

000000b0 :
b0:   1c f0 26 cd     ai      $77,$77,-64  // レジスタをセーブするアドレスを設定 (-16*4)
b4:   21 7f fb 82     brnz    $2,90        // スタック上の save_regs_1 にジャンプ
b8:   35 00 27 80     bi      $79          // 3c: にジャンプ
bc:   00 00 00 00     stop

ということで 38: のジャンプ実行時のスタックが以下だとすると、

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |        <---- input SP
         |------------|
         |  Reg 79    |
         |------------|
         |  Reg 78    |
         |------------|
         |  Reg 77    |
         |------------|
         |  Reg 76    |
         |------------|
         |            |
         |------------|
         //   ...    //
         |------------|
         |            |
         |============|
         |save_regs_3 |
         |------------|
         |            |
         |------------|
         |save_regs_1 | <---- $76
         `------------'
                        low memory

39: 時点のスタックがこうなります。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |        <---- input SP
         |------------|
         |  Reg 79    |
         |------------|
         |  Reg 78    |
         |------------|
         |  Reg 77    |
         |------------|
         |  Reg 76    |
         |------------|
         |  Reg 75    |
         |------------|
         //   ...    //
         |------------|
         |  Reg  4    |
         |------------|
         |            |
         |------------|
         |            |
         |------------|
         |            |
         |============|
         |save_regs_3 |
         |------------|
         |save_regs_2 |
         |------------|
         |save_regs_1 | <---- $76
         `------------'
                        low memory

次に後半の引数セーブと stop を実行する部分です。

3c:   1c 04 26 cd     ai      $77,$77,16    // 2 つ以上引数がある場合のアドレス調整
                                            // この場合は Reg 4 分インクリメント
40:   24 ff e6 81     stqd    $1,-16($77)   // 現在の sp (call_stack) をセーブ
44:   24 ff a6 cd     stqd    $77,-32($77)  // パラメータへのアドレス (next_arg) をセーブ
48:   24 ff 66 84     stqd    $4,-48($77)   // 引数 2 (format) をセーブ
4c:   24 ff 26 83     stqd    $3,-64($77)   // 引数 1 (stream) をセーブ
50:   1c f0 26 cd     ai      $77,$77,-64   // 引数 1 のアドレスをロード
54:   41 11 80 03     ilhu    $3,8960       // OPCODE(0x23) をロード
58:   43 ff ff 84     ila     $4,3ffff      // マスク値を生成
5c:   33 80 0c cf     lqr     $79,c0        // call_ppe_1: の命令列をロード
60:   3e c1 00 85     cwd     $5,4($1)      // マスク値を生成
64:   80 73 41 84     selb    $3,$3,$77,$4  // [24:31] OPCODE, [0:17] 引数のアドレス
68:   b9 f3 c1 85     shufb   $79,$3,$79,$5 // $3 を命令列 2 word 目に挿入
6c:   24 00 26 4f     stqd    $79,0($76)    // 命令列をスタックにセーブ
70:   00 40 00 00     sync
74:   35 20 26 02     bisl    $2,$76        // スタック上の 命令列にジャンプ
78:   34 ff 00 83     lqd     $3,-64($1)    // 返り値を $3 にロード
7c:   3f e3 01 82     shlqbyi $2,$3,12
80:   23 80 00 02     stqr    $2,0          // errno をセーブ
84:   35 00 00 00     bi      $0            // 呼び出し関数に復帰
        ...
000000c0 :
c0:   00 00 21 00     stop                  // stop OPCLASS
c4:   00 00 00 00     stop                  // ここに OPCODE + 引数のアドレス が入る
c8:   35 00 01 00     bi      $2            // 78: にジャンプ
cc:   00 20 00 00     lnop

よって、スタック上の stop OPCLASS 命令を発行して SPE が停止した時のス タックはこのようになります。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |<-----. <---- input SP
         |------------|      |
         |  Reg 79    |      |
         |------------|      |
         |  Reg 78    |      |
         |------------|      |
         //   ...    //      |
         |------------|      |
         |  Reg  6    |      |
         |------------|      |
         |  Reg  5    |<--.  |
         |------------|   |  |
         | call_stack |------'
         |------------|   |   
         |  next_arg  |---'
         |------------| 
         | format (r4)|
         |------------|
         | stream (r3)| <---- start of parameters
         |============|
         |            |
         |------------|
         |            |
         |------------|
         | call_ppe_1 | <---- $76
         `------------'
                        low memory

libc 解析 take.2 (非可変引数関数)

"PPE Serviced SPE C Library Functions" の 2 回目は、SPE 側での処理に ついて解説します。

4.2 PPE Serviced SPE C Library Functions

  1. The SPE constructs a local store image of the input and outout parameters.
  2. The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
  3. The SPE executes a Stop and Signal instruction.
  4. The PPE detects the Stop and Signal.
  5. The PPE invoke a specialist to service the request according to the message opcode.
  6. The PPE services the requested function.
  7. The PPE returns results in the local store image array.
  8. The PPE resumes SPE execution.
  9. The SPE returns that results back to the caller.

この内、SPE で行うのは 1., 2., 3. ですが、呼び出す関数の引数の数が決 まっている場合 (非可変引数関数) と、可変の場合 (可変引数関数) の場合で処 理方法が変わります。

ソースコード

ソースコードは /opt/IBM/cell-sdk-1.1/src/lib/c/spu/ 以下にあるのです が、ほとんどのコードは mk_syscalls と syscall.def からコンパイル時に生成 されています。syscall.def の一部を抜き出してみると

#
#   CLASS             OPCODE             NAME        PARMS RETVAL   SYSCALL_TYPE
#

SPE_C99_CLASS     SPE_C99_GETC           getc          1     1      spu_syscall

SPE_C99_CLASS     SPE_C99_VFPRINTF       fprintf       2     1      spu_syscall_va

のように各関数の属性が書かれており、mk_syscall がこの属性情報をもとに 各関数用のソースファイルを生成します。で、SYSCALL_TYPE が spu_syscall の 場合は非可変引数関数、spu_syscall_va の場合は可変引数関数となります。

非可変引数関数の場合

以下は getc() を例に説明します。まずは、生成されたソースコード (getc.S) です。

#include "spe_c99_ops.h"
#include "spe_posix1_ops.h"

#define OPCLASS SPE_C99_CLASS              // C99 の関数
#define NAME    gets                       // 関数名
#define PARMS   1                          // 引数の個数
#define OPCODE  SPE_C99_GETS               // 関数の識別子 (13)
#define RETVAL  1                          // 返り値がある場合

#include "spu_syscall.S"

このように非可変引数関数の場合は spu_syscall.S をインクルードしていま す。次はオブジェクトファイルの逆アセ結果で説明をコメントとして追加してあ ります。

注意すべき点としては、stack_instr: に書かれた命令列は一旦レジスタにロー ドされ、加工された後にスタック上にセーブし、最終的にはスタック上の命令が 実行されるところです。

getc.o:     file format elf32-spu
Disassembly of section .text:

00000000 :
 0:   41 09 80 02     ilhu    $2,4864      // OPCODE(13) を $2 にロード
 4:   24 ff 40 83     stqd    $3,-48($1)   // 引数 ($3) を -48(sp) にセーブ
 8:   33 80 07 06     lqr     $6,40        // stack_instr: の命令列を $6 にロード
 c:   1c f4 00 83     ai      $3,$1,-48    // 引数をセーブするアドレスを $3 にロード
10:   43 ff ff 84     ila     $4,3ffff     // マスク値を生成
14:   80 40 c1 04     selb    $2,$2,$3,$4  // [24:31] OPCODE, [0:17] 引数のアドレス
18:   3e c1 00 85     cwd     $5,4($1)     // マスク値を生成
1c:   b0 41 81 05     shufb   $2,$2,$6,$5  // $2 を命令列 2 word 目に挿入
20:   1c f8 00 83     ai      $3,$1,-32    // 命令列をセーブするアドレスをロード
24:   24 ff 80 82     stqd    $2,-32($1)   // 命令列をスタックにセーブ
28:   00 40 00 00     sync
2c:   35 20 01 82     bisl    $2,$3        // スタック上の命令列にジャンプ
30:   34 ff 40 83     lqd     $3,-48($1)   // 返り値を $3 にロード
34:   3f e3 01 82     shlqbyi $2,$3,12
38:   23 80 00 02     stqr    $2,0         // errno をセーブ
3c:   35 00 00 00     bi      $0           // 呼び出し関数に復帰

00000040 :
40:   00 00 21 00     stop                 // stop OPCLASS
44:   00 00 00 00     stop                 // ここに OPCODE + 引数のアドレス が入る
48:   35 00 01 00     bi      $2           // 30: にジャンプ
4c:   00 20 00 00     lnop

よって、スタック上の stop OPCLASS 命令を発行して SPE が停止した時のス タックはこのようになります。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |        <---- input SP
         |------------|
         |            |
         |------------|
         |   命令列   |        <---- $3
         |------------|
         |    引数    | <---- start of parameters
         `------------'
                        low memory

ちなみに引数が 2 つの場合はスタックフレームはこんな感じです。

         |   Stack    | high memory
         |   Parms    |
         |            |
         |------------|
         |  Link Reg  |
         |------------|
         | Back Chain |        <---- input SP
         |------------|
         |            |
         |------------|
         |   命令列   |        <---- $3
         |------------|
         |   引数 2   |
         |------------|
         |   引数 1   | <---- start of parameters
         `------------'
                        low memory

ちょっと謎なのが stack_instr 以下のコードをスタック上にセーブしてから 実行しているところです。spu_syscall.S のコメントを見ると "リエントラント にするためだ" と書いてあり、たしかに OPCLASS と引数のアドレスを書くんで、 1 SPE 内に複数のスレッドがいて、スレッド切り替え時に LS の内容が変更され ないなら必要ですが、現在のようにスレッドが切り替わる場合は LS の内容も全 部入れ換えている場合は必要ないかな、、と。まあ、今後の備え?

libc 解析 take.1 (spu_run syscall)

これから数回に渡って、"PPE Serviced SPE C Library Functions" について 解説しようと思います。この処理は SPE thread, Linux kernel, libspe が密接 にからんでいますが、1 回目は Linux kernel が行う処理についてです。

この処理の詳細は libraries_SDK.pdf p.66 "4.2 PPE Serviced SPE C Library Functions" にあります。

4.2 PPE Serviced SPE C Library Functions

  1. The SPE constructs a local store image of the input and outout parameters.
  2. The SPE creates a 32-bit message consisting of an opcode and pointer to the local store parameter image array.
  3. The SPE executes a Stop and Signal instruction.
  4. The PPE detects the Stop and Signal.
  5. The PPE invoke a specialist to service the request according to the message opcode.
  6. The PPE services the requested function.
  7. The PPE returns results in the local store image array.
  8. The PPE resumes SPE execution.
  9. The SPE returns that results back to the caller.

この内、Linux kernel が行うのは 4. だけで、実際には SPE thread の実行 を開始する spu_run システムコールの内部で処理されています。spu_run に関し てはドキュメントがlinux/Documentation/filesystems/spufs.txt にあるので訳 してみますと、、

------------------------------------------------------------------------------
SPU_RUN(2)                 Linux Programmer's Manual                SPU_RUN(2)



名前
       spu_run - spu context を実行する


SYNOPSIS
       #include 

       int spu_run(int fd, unsigned int *npc, unsigned int *event);

DESCRIPTION
       The  spu_run system call is used on PowerPC machines that implement the
       Cell Broadband Engine Architecture in order to access Synergistic  Pro-
       cessor  Units  (SPUs).  It  uses the fd that was returned from spu_cre-
       ate(2) to address a specific SPU context. When the context gets  sched-
       uled  to a physical SPU, it starts execution at the instruction pointer
       passed in npc.

       spu_run システムコールは Cell Broadband Engine Architecture を実装した 
       PowerPC 上で Synergistic Processor Units (SPUs) にアクセスするために使
       用される。SPU context を特定するために spu_create(2) の返り値である fd
       を使用する。context が物理 SPU を獲得後、npc で指定された命令ポインタの
       さす命令の実行を開始する。

       Execution of SPU code happens synchronously, meaning that spu_run  does
       not  return  while the SPU is still running. If there is a need to exe-
       cute SPU code in parallel with other code on either  the  main  CPU  or
       other  SPUs,  you  need to create a new thread of execution first, e.g.
       using the pthread_create(3) call.

       SPU 命令は同期的に実行されるため、SPU 実行中は spu_run は復帰しません。
       メイン CPU や他の SPU 上のコードと並列に実行する必要のある時は、例えば 
       pthread_create(3) を使用して、最初に新しいスレッドを生成する必要があり
       ます。

       When spu_run returns, the current value of the SPU instruction  pointer
       is  written back to npc, so you can call spu_run again without updating
       the pointers.

       spu_run から復帰する時に SPU 命令ポインタの現在値が npc に書き込まれるた
       め、命令ポインタを更新することなく再度 spu_run を呼び出すことができます。

       event can be a NULL pointer or point to an extended  status  code  that
       gets  filled  when spu_run returns. It can be one of the following con-
       stants:

       event は NULL ポインタか spu_run から返る時の拡張ステータスコードが格納
       されるポインタを指定でき、その値は以下の定数にいづれかになる。

       SPE_EVENT_DMA_ALIGNMENT
              A DMA alignment error

       SPE_EVENT_SPE_DATA_SEGMENT
              A DMA segmentation error

       SPE_EVENT_SPE_DATA_STORAGE
              A DMA storage error

       If NULL is passed as the event argument, these errors will result in  a
       signal delivered to the calling process.

       event 引数に NULL が指定された場合、これらのエラーはシグナルとして呼び出
       したプロセスに配送される。


返り値
       spu_run  returns the value of the spu_status register or -1 to indicate
       an error and set errno to one of the error  codes  listed  below.   The
       spu_status  register  value  contains  a  bit  mask of status codes and
       optionally a 14 bit code returned from the stop-and-signal  instruction
       on the SPU. The bit masks for the status codes are:

       spu_run は spu_status レジスタの値か、エラーを指す -1 を返し、以下に示す
       エラーコードのいづれかを errno に格納する。spu_status レジスタの値はステ
       ータスコードのビットマップと、SPU 上で実行された stop-and-signal 命令で
       指定された 14 ビットの値を含む。ステータスコードのビットマスクは、

       0x02   SPU was stopped by stop-and-signal.

       0x04   SPU was stopped by halt.

       0x08   SPU is waiting for a channel.

       0x10   SPU is in single-step mode.

       0x20   SPU has tried to execute an invalid instruction.

       0x40   SPU has tried to access an invalid channel.

       0x3fff0000
              The  bits  masked with this value contain the code returned from
              stop-and-signal.

       There are always one or more of the lower eight bits set  or  an  error
       code is returned from spu_run.

       下位 8 ビットの内、常に 1 つ以上のビットが立ったものか、エラーコードが
       spu_run から返される。

エラー
       EAGAIN or EWOULDBLOCK
              fd is in non-blocking mode and spu_run would block.

       EBADF  fd is not a valid file descriptor.

       EFAULT npc is not a valid pointer or status is neither NULL nor a valid
              pointer.

       EINTR  A signal occured while spu_run was in progress.  The  npc  value
              has  been updated to the new program counter value if necessary.

       EINVAL fd is not a file descriptor returned from spu_create(2).

       ENOMEM Insufficient memory was available to handle a page fault result-
              ing from an MFC direct memory access.

       ENOSYS the functionality is not provided by the current system, because
              either the hardware does not provide SPUs or the spufs module is
              not loaded.


ノート
       spu_run  is  meant  to  be  used  from  libraries that implement a more
       abstract interface to SPUs, not to be used from  regular  applications.
       See  http://www.bsc.es/projects/deepcomputing/linuxoncell/ for the rec-
       ommended libraries.

       spu_run は SPU へのより抽象的なインタフェースを実装するライブラリから使
       用することが意図されており、通常のアプリケーションから使用されることは意
       図されていない。推奨するライブラリとしては
       http://www.bsc.es/projects/deepcomputing/linuxoncell/ を参照。

準拠
       This call is Linux specific and only implemented by the ppc64 architec-
       ture. Programs using this system call are not portable.

       この関数は Linux 特有であり、ppc64 アーキテクチャでのみ実装されている。
       このシステムコールを使用したプログラムはポータブルではない。

バグ
       The code does not yet fully implement all features lined out here.

       コードはここに記述した機能を全て実装していない。

筆者
       Arnd Bergmann 

関連項目
       capabilities(7), close(2), spu_create(2), spufs(7)



Linux                             2005-09-28                        SPU_RUN(2)

------------------------------------------------------------------------------

と長々と訳してみましたが、関連部分を超要約すると、"SPE thread は PPE に何かしてほしい場合は、お願いをオペランドに指定して stop 命令を発行 してね。で、PPE thread は spu_run の返り値としてお願いを受け取ることがで きるから、お願いに応じて実際の処理をしてちょ〜だい" ということです。

返り値についてもエラーでなければ CBE_Public_Registers_v10.pdf p.75 "3.4.3.4 SPU Status Register (SPU_Status)" の値がそのまま返り値となりま す。

あと、注意する点としては spu_run から復帰した時は SPE thread は必ず止 まっているということです。この後、PPE thread が spufs 経由で SPE のメモリ イメージ (/spu/*/mem) を読み書きをしますが、止まっていない場合は spufs 経 由のメモリイメージへ書き込みをしても正しく反映されません。(この処理で扱う システムコールは同期的なので当然止まっているわけですが、、)

内部的には、SPE スケジューラやコンテキストの処理がからんできて結構難 しいのですが、今回説明する処理を理解する上では spu_run システムコールの 仕様がわかっていれば十分なのでこの辺でお茶を濁しておきます。

spufs in linux-2.6.19-rc1

先週、linux kernel v2.6.19 用の新機能マージ期間が終わって、 linux-2.6.19-rc1 がリリースされましたが、cell 用の拡張にも新機能が滑り込 みでマージされました。ログを拾い読みしてみると以下の機能が追加されている ようです。

  1. [POWERPC] spufs: scheduler support for NUMA.
  2. [POWERPC] spufs: make mailbox functions handle multiple elements
  3. [POWERPC] spufs: Add infrastructure needed for gang scheduling

2. の commit log を見てみると libspe2 なんてキーワードも出てきています。

その他の詳細は 2006-06-28 以降の commit log を参照してください。

spufs で linux tree を検索

libspe 解析 take.2

前回からちょっと時間が経ってしまいましたが、今回は SPE プログラムのイメー ジを PPE のプログラムに組み込む場合の話です。SPE thread を生成する前にそ の実行イメージを (PPE プログラムの空間から) 見えるようにする必要があるの ですが、それには 2 つの方法があります。

  • PPE プログラム生成時に SPE のオブジェクトをリンクする
  • PPE プログラムから spe_open_image() する

ここで後者に関しては spe_open_image() を実行すると SPE プログラムを mmap() し、返り値として spe_program_handle_t 型の変数 (ハンドラ) を返すの で、これをspe_create_thread() の引数として与えればよいことになります。

前者の場合も SPE オブジェクトは PPE プログラムにリンクされているんだ し、何かマクロでもかまして終わりだろと思って Toolchain に付属のサンプルプ ログラムを見てみると、ハンドラは定義されているが extern されていて、実体 がソースコードのどこにも見当たりません。もしかして libspe で何かやってい るのか?と思って見ましたが、何も見付けられず、、

で、サンプルの make 時のログでも見るかということで眺めてみたら、、見 知らぬものがありました。SPE オブジェクトを PPE プログラムにリンクする場合 には ar されたライブラリをリンクに使用しますが、このライブラリを作成する 前に ppu-embedspu というコマンドを実行しています。これは何?と思って見て みると shell script で書かれていて、引数に ハンドラ名や SPE プログラムを 指定すると、SPE プログラムの加工と対応するハンドラの作成をしてくれるよう です。

そんなこと書いてあったか?と思いマニュアルを見直すとたしかにありまし た。

CBE_SDK_Guide_1.1.pdf, p.28 "The ppu-embedspu tool that is part of the GNU tool chain is used to link a PPU executable and a SPU executable into a single Cell BE executable."

覚えてねーよ。

kernel 解析 take.2 (syscall だけ見るって無理があるわな)

2 つしかない SPE 用の syscall (sys_spu_create, sys_spu_run) くらい見ておくか、と思って 見始めたらどんどん深みにはまって気づいたら最深部までいってます。そのおかげでなかなか興 味深いことがわかったのでまずはメモということで。

  • SPE プログラム用のスケジューラがあり、物理数以上の SPE プログラムを扱うことが可能。
  • ディスパッチ時に退避、復帰される SPE のコンテキストには、128 個ある GPR (General-Purpose Register)、256KB の LS (Local Store)、MFC 制御レジスタ等 のほぼ全ての資源が含まれる (巨大コンテキスト)。
    (参考 include/asm-powerpc/{spu.h,spu_csa.h})
  • コンテキストが巨大ということは復帰、退避処理も長いです。退避 54 STEP、復帰 75 STEP って 、、、
    (参考 arch/powerpc/platforms/cell/spufs/switch.c)
  • PPE から制御できる資源は PPE が退避、復帰する。が、SPE の GPR は PPE からは制御できないので、、
  • GPR を退避、復帰するために、まず PPE から LS を 16KB だけ MFC を使って退避し、空いた領域に プログラムを別途転送、このプログラムから GPR の退避、復帰と LS の残り 240KB (256KB - 16KB) の 退避、復帰を行っている。
    (参考 arch/powerpc/platforms/cell/spufs/spu_{save,restore}.c)
  • SPE のプログラムは、PPE 上の対応する SPE thread の優先度をそのまま使用している。 ということは libspe-1.1.0 が SPE thread の優先度変更に対応していないので、現状では やっぱり SPE プログラムの優先度変更はできない。
    (参考 arch/powerpc/platforms/cell/spufs/context.c: spu_acquire_runnable())

なんか、スゴい、、

kernel 解析 take.1 (2.6.18-rc6 との差分調査)

Cell 用 Linux kernel を解析をするまえに upstream (v2.6.18-rc6) との差分を調査しておこうと思います。後から大きく違うことがわかると ショックだし、、といっても Cell 用のディレクトリに関する差分を大まかに ながめただけで、powerpc 自体や include に関する調査はしていません。


$diff -Nru cbe-linux-2.6.16/arch/powerpc/platforms/cell/ \
            linux-2.6.18-rc6/arch/powerpc/platforms/cell/ \
            | grep "^diff -Nru" | gawk '{print $3}'
cbe-linux-2.6.16/arch/powerpc/platforms/cell/Kconfig
cbe-linux-2.6.16/arch/powerpc/platforms/cell/Makefile
cbe-linux-2.6.16/arch/powerpc/platforms/cell/cbe_regs.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/cbe_regs.h
cbe-linux-2.6.16/arch/powerpc/platforms/cell/interrupt.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/interrupt.h
cbe-linux-2.6.16/arch/powerpc/platforms/cell/iommu.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/pci.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/perfmon.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/perfmon.h
cbe-linux-2.6.16/arch/powerpc/platforms/cell/pervasive.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/setup.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/smp.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spider-pic.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spu_base.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spu_callbacks.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spu_priv1_mmio.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/Makefile
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/backing_ops.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/file.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/hw_ops.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/inode.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/run.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/sched.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/switch.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/syscalls.c

upstream にないファイル


cbe-linux-2.6.16/arch/powerpc/platforms/cell/pci.c

ハードに関連する暫定対策のようで upstream にはコミットされないようです。(pci-fixup-hack.diff 参照)


cbe-linux-2.6.16/arch/powerpc/platforms/cell/perfmon.c
cbe-linux-2.6.16/arch/powerpc/platforms/cell/perfmon.h

performance monitor のサポート。(cell-perfmon.diff 参照)

irq 関連

upstream での irq 関連の変更への追従。

semaphore から mutex に変更

upstream では semaphore を使うのをやめて mutex を使う方向になっているのでこれに追従。

mutex から semaphore に変更


cbe-linux-2.6.16/arch/powerpc/platforms/cell/spufs/sched.c

upstream 2.6.16 では、まだ semaphore を使っているのでまだコミットされていないだけでしょう。

NUMA (node) 関連

まだコミットされてないだけのようだが、差分見るだけではよくわからない。(spufs-sched-numa-2.diff 参照)

libspe 解析 take.1

やっと解析開始ということで、第一回は libspe から始めます。

まずはマニュアルということで、SPE Runtime Management Library, Version 1.1  (libspe_v1.1.pdf) を読んで、解析に必要な情報を洗い出してみます。

  • SPE thread と SPE group という管理単位がある
  • SPE thread は 処理単位 (spe で実行されるプログラム) 毎に生成される
  • SPE thread は SPE group に属する
  • SPE thread の属性 (policy, priority) は 所属する SPE group によって決まる
  • (当然だが) SPE thread と SPE group は複数生成可能

ということで、これらを管理しているデータ構造と制御関数を探してみます。

データ構造

spe.h を見てみると以下の構造体があります。

struct group_list SPE group リストの先頭データ
struct grpsElem SPE group リストデータ
struct group_store SPE group 管理データ
struct grpListElem SPE thread リストデータ
struct thread_store SPE thread 管理データ

図で表すとこんな感じです。


+----------+    +--------+                         +--------+
|group_list| -> |grpsElem| ----------------------> |grpsElem| -
+----------+    +--------+                         +--------+
                     |                                  |
                     V                                  V       
              +-------------+                    +-------------+
              | group_store |                    | group_store |
              |             |                    |             |
              |+-----------+|                    |+-----------+|
              ||grpListElem||                    ||grpListElem||
              ++-----------++                    ++-----------++
                     |                                  |
                     V
               +-----------+    +--------------+
               |grpListElem| -> | thread_store |
               +-----------+    +--------------+
                     |
                     V
               +-----------+    +--------------+
               |grpListElem| -> | thread_store |
               +-----------+    +--------------+
                     |

制御関数

次にこれらのデータを制御する関数にどんなものがあるのか見てみると、spe.c に以下の関数がありました。

srch_thread() SPE thread 識別子 (speid) の検査
srch_group() SPE group 識別子 (gid) の検査
add_group_to_groups() SPE group を group list に追加
add_group_to_groups() SPE thread を SPE group に追加
remove_thread_from_group() SPE thread を SPE group から削除

となっており、一旦作成した SPE group は削除できないみたいです。

実際の使用方法

実際の使用方法に関しては、SPE thread のライフサイクル (生成、実行、終了) に関わる libspe の 3 API と SPE thread のスタート関数を見てみます。また、libspe はスレッドの管理に pthread を使用し、spe の制御は syscall を使用していますので、これらの関数を適当に抜き出しました。

なお、これらの関数は全て spe.c 内に書かれています。

SPE group 生成関数


spe_create_group()
       -> spe_gid_setup()            // SPE group 管理データ生成
            -> calloc()              // SPE group 識別子を生成
            -> add_group_to_groups() // SPE group を group list に追加

SPE thread 生成関数


spe_create_thread()
  -> srch_group()                    // SPE group 識別子を検査
  -> spe_setup()                     // SPE thread 管理データ生成
       -> spe_gid_setup()            // SPE group が指定されていない場合
            -> calloc()              // SPE group 識別子を生成
            -> add_group_to_groups() // SPE group を group list に追加
       -> calloc()                   // SPE thread 識別子を生成
       -> spe_create()
            -> syscall(spe_create)   // spu context の生成
       -> add_thread_to_group()
  -> pthread_create(,,spe_thread,)   // SPE thread の生成 (spe_thread() を実行)

SPE thread スタート関数


spe_thread()
  -> do_spe_run()
       -> spe_run()
            -> syscall(spe_run)      // spu context の実行
                                     // return すると終了

SPE thread の状態変化 (終了) を待つ関数


spe_wait()
  -> srch_thread()                   // SPE thread 識別子を検査
  -> pthread_join()                  // SPE thread の終了待ち
  -> spe_cleanup()                   // SPE thread 管理データ削除
  -> remove_thread_from_group()      // SPE thread を SPE group から削除

わかったこと

  • spe_{get,set}_priority() より pthread_setschedparam がコメントになっているので priority 操作は未対応?
  • spe_{get,set}_affinity() より affinity 操作は未対応
  • spe_{get,set}_context() より context 操作は未対応

TAG データ作成

早速、構築した環境を使ってソースコードの TAG データを作成してみます。解析のターゲットにしている Linux kernel と libspe は BSC (Barcelona Supercomputer Center) にありますので、以下をダウンロードします。

http://www.bsc.es/projects/deepcomputing/linuxoncell/cellsimulator/sdk1.1/kernel-2.6.16-bsc4.2.src.rpm
http://www.bsc.es/projects/deepcomputing/linuxoncell/stable/libspe/libspe-1.1.0.tar.gz

まずは Linux kernel からですが、rpm には linux-2.6.16 と、quilt で管理するファイルの入ったディレクトリ (patches/) が kernel-2.6.16.tar.gz として入っていますので、Linux kernel を展開した後に quilt を使って Cell 用 kernel のソースコードを作成します。

$ rpm -ivh kernel-2.6.16-bsc4.2.src.rpm
$ tar xvzf ~/rpm/SOURCES/linux-2.6.16.tar.bz2
$ mv linux-2.6.16 cbe-linux-2.6.16
$ cd cbe-linux-2.6.16/
$ tar xvzf ~/rpm/SOURCES/kernel-2.6.16.tar.gz

では、quilt を使ってこれから適用する patch の状態を見てみます。

$ quilt series -v

  patches/hvc-console-rework-4.diff
  patches/hvc-console-rtas-4.diff
  patches/cell-detect.diff
            :
  patches/spufs-dma-events-2.diff
  patches/spufs-correct-dma-exceptions.diff
  patches/SDK

patch を全部適用します。

$ quilt push -a

で、patch の状態を確認すると

$ quilt series -v

+ patches/hvc-console-rework-4.diff
+ patches/hvc-console-rtas-4.diff
+ patches/cell-detect.diff
            :
+ patches/spufs-dma-events-2.diff
+ patches/spufs-correct-dma-exceptions.diff
= patches/SDK

という具合に適用されている patch の先頭には + が付き、この内、最上位の patch には = が付きます。全ての patch をはずす場合は

$ quilt pop -a

です。push, pop に -a を指定すると管理している全ての patch が対象となり、指定しないと一つだけが対象となります。あとは、最上位の patch の表示の仕方くらい覚えておけばソースコードを解析はできます。

$ quilt diff

で、gtags 実行

$ gtags

ちなみに TAG データのサイズはこのくらいです。ほぼ 1GB ですが、最近の HDD のサイズを考えれば問題にならないでしょう。

GPATH: 3137536 byte
GTAGS: 115400704 byte
GRTAGS: 758595584 byte
GSYMS: 93298688 byte

libspe も同様に解凍して gtags します。

$ tar xvzf libspe-1.1.0.tar.gz
$ cd libspe-1.1.0/

$ gtags

こちらの TAG データのサイズはこのくらいです。

GPATH: 16384 byte
GTAGS: 81920 byte
GRTAGS: 352256 byte
GSYMS: 188416 byte

あ〜、やっと解析準備完了。