研究日誌。

大規模なグラフ処理に対してメモリ階層構造を考慮した高性能なソフトウェアを開発。

PC クラスタ設置。

2007-08-28 23:15:08 | Weblog
研究室の電源工事と空調工事が完成したので、計算機のセッティングを行った。
電源ケーブル、ネットワークケーブルなど、かなりの数を要したので、
ラックとラックの間がかなりごちゃごちゃになっている。

発熱量が思ったよりも多く、冷房能力が通常の6台分ほどあるエアコン2台で冷やしても、
ラックとラックの間にいると、20℃ 強ではなんとなくぬるいといった感じだ。

ただ、冷房もまだまだフルパワーというわけではないので、増やせそうだととのこと。
ラックを増やすことも検討しているそうで、とても楽しみである。

研究室がやっと”研究室”らしくなってきた。

行列積演算2。

2007-08-18 17:29:20 | Weblog
■Cell への適応■
実験1、2より、512 x 512 という比較的小さな行列の積演算においても、かなりの速度向上が見られた。サイズを大きくすればよりその効果は得られるだろう。さらに複数 SPE を用いて処理させて、高速化していきたい。

考慮しなければならないことがいくつかあるが、やはり SPE に載るデータ量である。SPE の LS は かなり小さく、実行するためのバイナリも含めて 256KB しか格納できない。double の 512 x 512 の行列1つだけでそのほとんどを占領してしまう。

このことから「オリジナルの入力行列2つを格納し、決められた場所を演算させる」といったことは物理的に不可能である。よって必然的に「処理に必要な部分を DMA 転送でもらってきて演算して返す」といったスタイルになっていく。

すでに2つめの入力行列Bは転置されているため、i 行 j 列目の要素は、Aの i 行目とBの j 行目を内積することで求められる。行列A、Bからそれぞれ1行ずつ SPE へ DMA 転送し、内積を行いその結果を PPE へ返すことになる。今回はより大きなサイズの行列を扱えるように1要素ずつ演算することにする。

大まかにはつぎのような流れで処理を行うことになっている。


[PPE]
1.SPE の準備をする。
2.割り当てられるのを待っている SPE を探し、パラメータを割り当てる。
  
 [SPE]
 1.DMA 転送しながらパラメータを受け取り、フラグがスタートになるのを待つ。
 2.演算に必要な行列2つを、少しずつ DMA 転送し、演算していく。
 3.処理が終われば、DMA 転送にて、結果を [PPE] へ戻す。
 4.1~3をフラグがストップになるまで繰り返す。
   (SPE スレッドは起動したままにする)
 [SPE]
  
3.処理の終わっている SPE から結果を受け取り、格納する。
4.すべての処理が終わるまで繰り返し、終わるとフラグをストップにする。
5.SPE の後処理を行う。
[PPE]


実行してみると演算しないで処理が終了してしまう要素がいくつかあるときがある。毎回違う要素で、うまくいくこともある。調べてみるといくつかの要素を複数回計算してしまい、その分演算していない要素ができてしまったようなのである。

同期処理にはフラグを用いてるのだが、どうもうまく機能していないのかも知れない。デッドロックのようなものが起きてしまい、処理が終わっていないうちに新たなパラメータを振り分けられてしまっているのかもしれない。いずれにしてもどうにかデバッグしていくしかない。





※マンデルブロにおいても同じようなことが確認。
何度も描いてみるとうまくいかないときもあることを確認した。また描画するサイズを 512 x 512 ではなく、4096 x 4096 のように大きくすると、演算ができていない箇所が増えてくる。もちろん毎回失敗するわけでもなく、成功することがあるため、原因を見つけるのが難しい。

行列積演算1。

2007-08-17 16:15:49 | Weblog
Cell を用いて行列の積演算を考えているが、まずは通常のC言語でのプログラムを作成し、どのように演算させれば効率的であるかを調べてみた。

2次元配列で用意された行列ではそのまま演算してしまうと、データのアドレスが飛び飛びになってしまい、それがボトルネックとなってしまう。また、SPE へ割り振る際にも連続していないと1要素ずつ DMA 転送することになってしまい、効率が悪い。

上記の2つの理由から、そのまま演算させるよりも、AxBであれば、AxtBとして、行列Bを転置させることがその2つの観点からも都合が良いようである。都合が良いといっても、転置する際にかかる処理量がどれほど影響するかも考慮しなければならないため、次のような条件において実験してみた。

■実験環境■
・PenM
 OS : Vine 4.1 (on VMware)
 CPU : Pentium M 1.66MHz
 Mem : 512MB

・PS3
 OS : Fedora Core 6
 CPU : Cell Broadband Engine 3.2GHz
 Mem : 256MB


入力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
 ex) in_A[BUFSIZE][BUFSIZE], in_B[BUFSIZE][BUFSIZE]

出力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
 ex) out[BUFSIZE][BUFSIZE]


■実験1■
まずは何も工夫をせずに最も簡単なタイプなもので測定してみた。サイズは BUFSIEZ = 512 というあまり大きななものではないが、予想以上に実行時間がかかってしまった。下記のように 512 x 512 x 512 回演算することになるため、データが飛び飛びであることが影響しているのであろうか。ここでの PS3 は PPE のみを使用しているので、Pentium M と比べて倍ほどかかってしまっている。

for (i = 0; i < BUFSIZE; i++) {
 for (j = 0; j < BUFSIZE; j++) {
  for (k = 0; k < BUFSIZE; k++) {
   out[i][j] = out[i][j] + in_A[i][k] * in_B[k][j];
  }
 }
}

PenM:  8.5 8.3 8.5 9.0 8.8 [s]
PS3 : 17.4 18.2 17.4 17.5 18.0 [s]




■実験2■
AxBという演算を、AxtB(Aに転置したBをかける)として演算する。事前に行列Bを転置してしまうことで、連続した演算になるため、高速化が期待できる。PenM では 1/5 、PS3 においては 1/10 とかなり改善させている。転置に要した時間も全体から見てもかなり小さい。今回は安直に tmp_in_B[][] という2次元配列を用意してしまったが、メモリを無駄遣いしないためにも、入れ換えをすることで転置にするようにするのもよいのかもしれない。

for (i = 0; i < BUFSIZE; i++) {
 for (j = 0; j < BUFSIZE; j++) {
  tmp_in_B[i][j] = in_B[j][i];
 }
}
for (i = 0; i < BUFSIZE; i++) {
 for (j = 0; j < BUFSIZE; j++) {
  for (k = 0; k < BUFSIZE; k++) {
   out[i][j] = out[i][j] + in_A[i][k] * tmp_in_B[j][k];
  }
 }
}

PenM:
積演算に要した時間    1.5 1.4 1.5 1.4 1.4 [s]
そのうち転置に要した時間  20[ms] 程度

PS3:
積演算に要した時間    1.6 1.6 1.6 1.6 1.6 [s]
そのうち転置に要した時間 300[ms] 程度

マンデルブロ描画。

2007-08-09 19:57:39 | Weblog
初めて触れた Cell プログラムでもある月刊 ASCII に載っていた
マンデルブロ描画のプログラムを CellSDK2.1、libspe2 用に書き直すことにした。

ちなみにマンデルブロ集合は次の漸化式
 z[n+1] = z[n]^2 + c
 z[0] = 0
で定義される複素数列 {z[n]} n∈N が n → ∞ の極限で
無限大に発散しないという条件を満たす複素数 c 全体が作る集合のことである。

次のような漸化式にすることで、複素数を用いなくても求めることができる。
今回のプログラムもこの形式を利用している。
 x[n+1] = x[n]^2 - y[n]^2 + a
 y[n+1] = 2x[n]y[n] + b

さて、Cell プログラムは複数のコンパイル方法があるが、
make.footer を用いた方法はどの環境でも同様に make できるので重宝している。

マンデルブロ描画は計算量が多いため、スレッド起動のオーバーヘッドが全体に占める割合はかなり小さい。
先日の内積プログラムでは、実行時間が数ミリ秒であるのでスレッド起動の時間が効いてきてしまう。これでは Cell のポテンシャルを引き出すのは難しい。double であることも影響しているのだろう。単純な演算で速度を出すのは難しいようだ。float ver. も作成し、比較してみよう。

以下が実行結果である。
SPE time とは、PPE 側から計測した SPE の実行時間だが、
スレッド起動等も入っているので、実質の計算時間であると思ってよいだろう。
処理には SIMD を用いていないだが、単一 CPU でこの早さはすごい。


$ time ./mandel > sample.ppm
Default Value is used
==================================================
C = (-0.750000, 0.000000), width = 2.500000
size      : 512
SPE time    : 257.824048[msec]
Total SPE time : 1486.337769 [msec]
==================================================
real 0m0.646s
user 0m0.614s
sys 0m0.032s

LU1814 エラー。

2007-08-07 18:13:25 | Weblog
大学では「Symantec AntiVirus Corporate Edition」という、ウィルス対策ソフトにお世話になっているのだが、ある時期を境に Live Update を行うと次のようなエラーが出るようになってしまった。

LU1814: LiveUpdateが更新リストを取り込めませんでした。 LiveUpdate が利用可能なシマンテック製品とコンポーネントの更新版のカタログファイルを取り込めませんでした。インターネットに接続できることを確認して LiveUpdate を再び実行してください。

もちろんインターネット(プロキシも設定済み)には接続できているし、なんらかの設定にミスがあるのかと思い、 Symantec Client Security から探してみたが、めぼしいものがない。再インストールしてもうまくいかない。

そこで Google 先生に聞いてみると、
LiveUpdate を実行すると「LU1814: LiveUpdate が更新リストを取り込めませんでした」というエラーが表示される
というまさに欲しているページを教えてくれた。

そのページにある「S32luhl1.dll ファイルを削除する」という対処法を試してみたらうまくいった。

LiveUpdate のインストールフォルダ内に s32luhl1.dll ファイルが存在する場合、LiveUpdate は接続先の設定内容に関わらず、強制的に LAN 接続で LiveUpdate サーバーに接続を行います。この場合、LiveUpdate の接続先に LAN 接続が設定されていなければ、LiveUpdate は接続先を発見できずに失敗します。

Cell での実行時間計測。

2007-08-07 01:58:38 | Weblog
Time Base Register というものを用いる。
Fixstars さんの Tutorial に ppe_prof.h として、ヘッダーファイルも用意されているので、
include することでとても簡単に用いることができる。

ただし、Play Station 3 で実行する場合は Cell Simulator のときと比べ、
timebase の値が異なるので注意が必要である。

timebase は、次のようにすると調べられる。
Play Station 3 で実行すると、79800000 clock であることがわかる。
ちなみに Cell Simulator では 2000000000 clock であった。

# cat /proc/cpuinfo
timebase : 79800000

これは、 79800000 Hz (79.800MHz) を意味しており、
79.800MHz 刻む毎に Time Base Register の値が 1 増えるため、次の式で実行時間を計測できる。

[Time Base Register の値の増加数] / [timebase] = 処理時間 [ sec]
[Time Base Register の値の増加数] / [timebase] * 1000.0f = 処理時間 [msec]




ppe_prof.h の実装は以下のように行う。


#include "ppe_prof.h"
#define SPU_DECREMENTER_INITIAL_VALUE 0x7FFFFFFFU

unsigned int profile;

spu_writech(SPU_WrDec, SPU_DECREMENTER_INITIAL_VALUE);
profile = spu_readch(SPU_RdDec);

/* 計測する処理をこのようにしてはさむ */

profile -= spu_readch(SPU_RdDec);
time = profile / 79800000 * 1000.0f;

内積プログラム。

2007-08-06 12:54:21 | Weblog
Cell を 用いて大きなベクトルの内積プログラムを書いてみた。

行列のサイズが大きくなっていくと、PPE か SPE いずれかが DMA 転送できるサイズに分割してあげなくてはならない。というのも DMA 転送は最大サイズが 16 kbyte であるためである。様々なところで PPE の処理能力がボトルネックであるとさんざん聞いてきたので、PPE には「各 SPE に担当させる範囲の割り振り」、「各 SPE からの処理結果の統合」といった最小の Job のみにとどめるようにした。SDK 2.1 の Sample の Euler Particle-System Simulation がとても参考になった。

■前提
・ベクトルのサイズはある程度大きく、SPE にのらない。
・ある程度の精度が必要であるため、Cell の得意とする float ではなく double を用いる。
・処理に用いる2つのベクトルはともに double 配列で用意する。
・内積結果は単に double で格納する。

■流れ
[PPE]処理に用いるベクトルの用意

[PPE]各 SPE が担当する範囲を「 DMA 転送に用いる構造体」に格納する。

[PPE]spe_context_create 、 spe_program_load 、 pthread_create で SPE Program を実行。

 [SPE]まずは「構造体」を DMA 転送し、パラメータを得る。

 [SPE]担当する範囲が終わるまで、2つのベクトル を DMA 転送し、内積演算する。

 [SPE]最終的な内積結果を PPE へ DMA 転送で返す。

[PPE]pthread_join で SPE の終了待ち。

[PPE]各 SPE の内積結果を1つにまとめる。


実行結果を見てみると、うまくいっているようだ。かなり小さい桁では若干の誤差があるが、 double の精度よりもさらに小さいところであるので、無視してよいだろう。また実行時間計測してみて、SPE 1つ1つの実行はかなり高速であるのだが、全体ではあまり早くない。やはりスレッド起動に時間がかかっているか、サイズを変えても全体の実行時間はあまり小さくならない。100要素でも、1000000 要素でも、6 [msec] ほどかかってしまう。まずは SPE Program を double buffer ver. にするなどいろいろ試してみることにしよう。