570 likes | 682 Views
共有メモリマシン 上ã§ã® 並列計算プãƒã‚°ãƒ©ãƒŸãƒ³ã‚°. 金田憲二. 背景. 速ã計算ãŒã§ãã‚‹ã¨è‰²ã€…ã†ã‚Œã—ã„ã¯ãš 一 ã¤ä¸€ã¤ã®ãƒ—ãƒã‚»ãƒƒã‚µã¯ã¾ã ã¾ã é…ㄠ並列ã«è¨ˆç®—ã™ã‚‹ã“ã¨ã§é«˜é€ŸåŒ– 1 個ã®ãƒ—ãƒã‚»ãƒƒã‚µã§ 1000 時間ã‹ã‹ã‚‹è¨ˆç®—を〠1000 個ã®ãƒ—ãƒã‚»ãƒƒã‚µã‚’使ã£ã¦ 1 時間㧠!!!. ã©ã†ã‚„ã£ã¦ä¸¦åˆ—計算をã™ã‚‹ã®ã‹. 並列計算機を手ã«å…¥ã‚Œãªã‘れã°ã„ã‘ãªã„ プãƒã‚°ãƒ©ãƒ を記述ã—ãªã‘れã°ã„ã‘ãªã„ ※ 当然ã€å®Ÿç¾ã—ãŸã„計算ã®ç¨®é¡žã«ã‚ˆã£ã¦ 最é©ãªæ‰‹æ®µã¯ç•°ãªã‚‹. 並列計算機ã®ç¨®é¡ž. 共有メモリマシン 例) SunFire15K クラスタ
E N D
共有メモリマシン上での並列計算プログラミング共有メモリマシン上での並列計算プログラミング 金田憲二
背景 • 速く計算ができると色々うれしいはず • 一つ一つのプロセッサはまだまだ遅い • 並列に計算することで高速化 • 1個のプロセッサで1000時間かかる計算を、1000個のプロセッサを使って1時間で!!!
どうやって並列計算をするのか • 並列計算機を手に入れなければいけない • プログラムを記述しなければいけない ※当然、実現したい計算の種類によって 最適な手段は異なる
並列計算機の種類 • 共有メモリマシン 例)SunFire15K • クラスタ • ネットワークでつながった数百~数千台の計算機 例)IBM BladeCenter 例)Sun V210 x 128
並列計算を記述する方法 • 普通の言語 例)C/Java + スレッド/通信ライブラリ • とても大変 • 自動並列化コンパイラ • ユーザは並列計算を意識しないで済む • 性能などの面で問題がある • 並列プログラミング言語 例)Cilk、 Linda、KLIC、Stackthreads、… • 簡便に高性能な並列計算を実現可能
今回取りあげるのは • Cilk言語 • 90年代後半にMITで研究・開発 • C言語にいくつかのキーワードを追加 • 共有メモリマシン上で動作 • 再帰関数などは結構簡単に並列化 例)チェスの次手の探索
発表の概要~Cilk言語のチュートリアル~ • フィボナッチの並列化 • 言語の詳細 • 実装の概要
フィボナッチ数を計算するCプログラム #include <stdlib.h> #include <stdio.h> int fib (int n) { if (n<2) return (n) int x, y; x = fib (n-1); y = fib (n-2); return (x+y); } int main (int args, char *argv[]) { int n, result; n = atoi(argv[1]); result = fib (n); printf(“Result: %d\n”, result); return 0; }
並列に計算できる箇所は? fib (3); fib (2); fib (4); fib (3); fib (5); fib (4); fib (2); fib (1); fib (6); fib (2); fib (1); fib (3); fib (2); fib (1); fib (0);
並列に計算できる箇所は? #include <stdlib.h> #include <stdio.h> int fib (int n) { if (n<2) return (n) int x, y; x = fib (n-1); y = fib (n-2); return (x+y); } int main (int args, char *argv[]) { int n, result; n = atoi(argv[1]); result = fib (n); printf(“Result: %d\n”, result); return 0; } この2つの関数の呼び出しは 並列に実行できる
Cilkで記述された並列フィボナッチ #include <stdlib.h> #include <stdio.h> #include <cilk.h> cilk int fib (int n) { if (n<2) return (n) else { int x, y; x = spawn fib (n-1); y = spawn fib (n-2); sync; return (x+y); } } cilkint main (int args, char *argv[]) { int n, result; n = atoi(argv[1]); result = spawn fib (n); sync; printf(“Result: %d\n”, result); return 0; }
プログラム中に現れるキーワード • cilk • spawn • sync
cilk • spawnやsyncを呼び出す関数の先頭につける識別子 cilk int f() { … } ※以降この識別子のついた関数をcilk procedureと呼ぶ
spawn • 並列関数呼び出し • 関数の終了を待たずに呼び出し元が実行を続ける spawn f (x); g (); f (x)の終了を待たずにg ()も実行する
sync • spawnした関数の終了を待つ y =spawn f (x); … sync; printf (“%d”, y); f (x)の終了を待つ
コンパイル • コマンド名:cilk • gccと同じコンパイルオプションが使える $ cilk filename $ cilk -g -Wall -O2 fib.cilk -o cilk
実行 • --nprocで計算機のプロセッサ数を指定 • 例)64プロセッサ上でのfib (32)の計算 $filename --nproc n args $ ./fib --nproc 64 32 ※fib (32)の計算はSunFire 15K上で 約55倍のスピードアップ
プロファイリング (1/2) • 以下のオプションをつけてコンパイル • --cilk-profile • --cilk-critical-path • 以下のオプションをつけて実行 • --stats level
プロファイリング (2/2) $ cilk -cilk-profile -cilk-critical-path -O2 fib.cilk -o fib $ ./fib --nproc 4 --stats 1 30 Result: 832040 RUNTIME SYSTEM STATISTICS Wall-clock running time on 4 processors: 2.593270 s Total work = 10.341069 s Total work (accumulated) = 7.746886 s Critical path = 779.588000 us Parallelism = 9937.154003
言語の詳細 • Storage allocation • Locking • Inlets • Aborting • Timer • (プログラム例:n-queens)
Storage Allocation (1/2) • Stack memoryへの割り当て • Cilk procedure内では • C関数内では ※関数がreturnする時に自動的に開放される ptr = Cilk_alloca(size); ptr = alloca(size);
Storage Allocation (2/2) • Heap memoryへの割り当て • 通常のCと同じ ptr = malloc(size); free(ptr);
Locking • Cilkは相互排他ロックを提供する • スレッド間で共有されるデータへのatomicなアクセスを可能にする
Data raceの例 (1/2) • foo()が2を返す(ことを意図した)プログラム cilk int foo (void) { int x = 0; spawn bar(&x); x = x + 1; sync; return (x); } cilk void bar (int *px) { *px = *px + 1; return; }
Data raceの例 (2/2) • 実行によっては、値が正しく更新されない cilk int foo (void) { int x = 0; spawn bar(&x); x = x + 1; sync; return (x); } cilk void bar (int *px) { *px = *px + 1; return; } (2) xをread (= 0) (1) xをread (= 0) (3) xへwrite (= 1) (4) xへwrite (= 1) (5) 1をreturn
API (1/2) • 型 • ロックを初期化する関数 • ロックを取得する関数 • ロックを開放する関数 Cilk_lockvar Cilk_lock_init(l) Cilk_lock (l) Cilk_unlock(l)
API (2/2) • ロックを取得できるのは同時に一つの スレッド • ロックを取得できなかったスレッドは そのロックが開放されるまで待機
プログラム例1 #include <cilk-lib.h> Cilk_lockvar mylock; { Cilk_lock_init(mylock); Cilk_lock(mylock); /* critical section (atomicに実行したいコード) */ Cilk_unlock(mylock); }
プログラム例2 #include <cilk-lib.h> Cilk_lockvar mylock; cilk int foo (void) { int x = 0; Cilk_lock_init(mylock); spawn bar(&x); Cilk_lock(mylock); x = x + 1; Cilk_unlock(mylock); sync; return (x); } cilk void bar (int *px) { Cilk_lock (mylock); *px = *px + 1; Cilk_unlock(mylock); return; }
Inlets • spawnの返り値に使える操作というものが制限されている • そのためにspawnの返り値を操作するための特殊な機構
spawnに対する制限 (1/2) • spawnの返り値に対して許されている操作は = *= /= %= += -= &= ^= |= <<= >>= のみ y += spawn f (x);
spawnに対する制限 (2/2) • 以下のような操作は許されていない z = spawn f (x) + spawn g (y) h (spawn f (x)); ただしhはCの関数
Inlet • spawnの返り値を処理する特殊な関数 • Cilk procedure内で定義される • spawnの返り値を渡すことができる cilk int f (int x) { inlet int g (int y) { … } z = g (spawn h()) }
プログラム例 cilk int fib (int n) { int x = 0; inlet void summer (int result) { x += result; } if (n < 2) return n; summer (spawn fib (n-1)); summer (spawn fib (n-2)); sync; return (x); }
注意点 (1/2) • inlet中でspawnやsyncを呼び出すことはできない cilk int f (int x) { inlet int g (int y) { z = spawn h(); } z = g (spawn h()) }
注意点 (2/2) • Implicit atomicity • spawnの呼び出したスレッドと同じスレッドがinlet内を処理する cilk int fib (int n) { inlet void summer (int result) { x += result; } summer (spawn fib (n-1)); summer (spawn fib (n-2)); } xへのアクセスはatomicであることが保障されている
Aborting • spawnしたCilk procedureを中断させる機構 • 探索の枝狩りなどの目的に使用する
Abort • inlet内にabortプリミティンブをいれる cilk int f (int n) { inlet void g (int x) { ... abort; } … }
プログラム例 cilk void search(int n) { inlet void catch() { if (解が発見された) { abort; } … } for (x in 探索空間) { catch (spawn search (x)); } }
注意点 • abortが呼ばれた時点でまだspawnされていないタスクは中断されない cilk void foo(int n) { inlet void bar() { abort; } bar (spawn baz(17)); spawn baz(28); } (3) abortを呼び出す (1) baz(17)をspawn (2) baz(17)が終了 (4) baz(28)をspawn
Timer • 型 • wall-clock timeを返す関数 • Cilk_timeを秒に変換する関数 Cilk_time t = Cilk_get_wall_time() sec = Cilk_time_sec(t)
プログラム例:n-queens (1/8) • N×Nのチェスボード上で以下の条件を満たすN個のQueenの配置を求める [条件]Queen同士は互いを取れない Q Q Q Q Q Q Q Q
プログラム例:n-queens (2/8) #include <stdlib.h> #include <stdio.h> #include <string.h> #include <cilk.h> #include <cilk-lib.h> …
プログラム例:n-queens (3/8) … int safe(char *config, int i, int j) { int r, s; for (r = 0; r < i; r++) { s = config[r]; if (j==s || i-r==j-s || i-r==s-j) { return 0; } } return 1; } …
プログラム例:n-queens (4/8) … cilk char *nqueens(char *config, int n, int i) { char *new_config; char *done = NULL; int j; inlet void catch(char *res) { if (res != NULL) { if (done == NULL) { done = res; } abort; } } …
プログラム例:n-queens (5/8) … if (i==n) { char *result; /* put this good solution in heap, and return a pointer to it */ result = malloc(n*sizeof(char)); memcpy(result, config, n*sizeof(char)); return result; } …
プログラム例:n-queens (6/8) … /* try each possible position for queen <i> */ for (j=0; j<n; j++) { new_config = Cilk_alloca((i + 1) * sizeof(char)); memcpy(new_config, config, i*sizeof(char)); if (safe(new_config, i, j)) { new_config[i] = j; catch(spawn nqueens(new_config, n, i+1)); } if (done != NULL) { break; } } sync; return done;} …
プログラム例:n-queens (7/8) … cilk int main(int argc, char *argv[]) { int n; char *config; char *result; n = atoi(argv[1]); config = Cilk_alloca(n*sizeof(char)); result = spawn nqueens(config, n, 0); sync; …