並列プログラミングの practice 課題を解いてもらうための布教活動
DESCRIPTION
並列プログラミングの Practice 課題を解いてもらうための布教活動. 田浦. 並列問題解決のステップ. decomposition ( 並列性抽出): 問題のタスクへの分割 mapping ( 負荷分散): タスクを API レベルのスレッド/プロセスにマッピング coordination: タスク間の通信,依存関係を API または instruction レベルの通信・同期に翻訳する. 最も簡単な例. - PowerPoint PPT PresentationTRANSCRIPT
並列プログラミングのPractice
課題を解いてもらうための布教活動
田浦
並列問題解決のステップ decomposition ( 並列性抽出 ):
• 問題のタスクへの分割 mapping ( 負荷分散 ):
• タスクを API レベルのスレッド / プロセスにマッピング
coordination:• タスク間の通信,依存関係を API または
instruction レベルの通信・同期に翻訳する
最も簡単な例 for (i = 0; i < n; i++) {
a[i] = i * i * d;}s = 0;for (i = 0; i < n; i++) { s += a[i]; }
Decomposition
順序制約 ( 依存関係 ): • A B タスク A の終了後にタスク B を開始 関係のないものはどのような順番・タイミン
グで実行されるかわからない ( という前提で正しく動くように decomposition を行う )
a[0] = … a[1] = … a[2] = … a[n – 1] =…
s = 0; for (i = 0; i < n; i++) s += a[i];
Mapping
タスクを API レベルの並列性にマッピング• MPI :
• 並列性の源 : プロセス (mpirun –np P …)
• プロセス数は固定• Pthreads :
• 並列性の源 : スレッド (pthread_create)
• スレッド数は可変だが,多数作りすぎると性能ロスが大きい
• もちろんどの道 CPU は固定である
本例題のタスク スレッドmapping
P 個のスレッド (t = 0, …, P – 1) スレッド t は i [nt / P, n(t + 1) / P) の更新
を行えばよい ( 等分 )
…スレッド 0スレッド 1スレッド 2 スレッド P – 1
スレッド 0
スレッド 0
Coordination 通信
• 実行するタスクが必要とするデータを送る ( 分散メモリ )
同期• 順序制約 ( 依存関係 ) を満たすための調停• i.e., 順序制約が満たされたタスクを実行
並行アクセスの制御• 順序制約のない複数のタスクによる,同一メモリロ
ケーションへのアクセスを調停 ( 共有メモリ )• 原子的な更新• 排他制御
本例題における Coordination( 共有メモリ )
initially: run = done = 0; master() { /* thread 0 */
run = 1; work(0); atomic_increment_n_done(); while (n_done < P); calc_sum();}
worker(t) { /* thread t */ while (run == 0); work(t); atomic_increment_n_done(); }
本例題における Coordination( 分散メモリ )
master() { /* processor 0 */ for p = 1, …, P – 1 send(p, run); work(0); n_done++; while (n_done < P) { received(done) => n_done++; } calc_sum();}
worker(t) { /* thread t */ receive(run()); work(t); send(master, done); }
より一般的な場合 タスク間の仕事量がばらつく
• 負荷 != タスクの「数」 タスクの数や形が,プログラムを実行
しながら判明する• タスクの数 ? 負荷 ?
対処法 :• タスクを細かく分散する ( 大数の法則 )• 動的に mapping をする ( 動的負荷分散 )
例 : Quicksort
sort(a, l, h) { … sort(a, c, h); sort(a, l, c);}
l c h
Qucksort の tasksort(0, 100)
sort(0, 37) sort(37, 100)
sort(0, 10) sort(10, 37) sort(37, 64) sort(64, 100)
… … … …
例 2: fib
fib(n) { if (n < 2) return 1; else return fib(n – 1) + fib(n – 2);}
fib の taskfib(5)
fib(4) fib(3)
fib(3) fib(2) fib(2) fib(1)
fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
+ + +
+ +
+
スレッド / プロセス タスクmapping の二つのスタイル
1 スレッド / プロセス 1 タスク ( 細粒度スレッド )• プログラムを単純に記述可能• スレッド / プロセス CPU の mapping は不確定
(OS そのほかに依存 ) 1CPU 1 スレッド (SPMD)
• タスク スレッド / プロセスの mapping を明示的に記述 ( プログラムが複雑 )
• タスク数 >> CPU 数のときは唯一の選択肢• プロセス数固定のモデル (e.g., MPI) でも唯一の選
択肢
一般的な SPMD テンプレート タスクをあらわす構造体
• タスクの状態 : 実行可・不可 「実行可能」なタスクを保持する構造
体 (Runnable Queue, Task Queue, Scheduling Queue)
Task Queue
一般的な SPMD テンプレートの動作
各スレッド (= プロセッサ ) の動作 while (!finishsed) {
t = get_task(); execute(t);}
task queue( 実行可能 task)
t
t t
execute新たに { 作られた・実行可能になった }task
各スレッドの動作 execute(t) {
アプリ固有の動作 including … create_task: t’ = a new task created by executing t; mark t’ runnable (e.g., enqueue(t’)); … enable_task: t’ = task enabled by t’; mark t’ runnable (e.g., enqueue(t’));}
Task Queue の Design Choice
• Shared: 全体でひとつ.共有• Private: スレッドごとにひとつ
Shared Task Queue
自然に空いているプロセッサに task が実行される
ハードウェア共有メモリ以外では非現実的
ハードウェア共有メモリでも台数が多く,タスク粒度が小さいときはボトルネックになる
Private (per Thread) Task Queue
スレッド ( プロセッサ ) にひとつの task queue
スケーラブル 複雑化する問題 : 負荷分散
• Static• Dynamic
Private Task Queue + Static Load Balancing
全スレッドが offline に合意できる方法で taskがどの local task queue に入るかを決めておく• e.g., Quicksort : 木のノードの位置を符号化,それ
をハッシュする task 生成 : 適切なスレッドの queue に
enqueue 各スレッドは自分の local queue からのみ task
を取り出して実行 task が細かい粒度でスレッドに分配される
Private Task Queue + Dynamic Load Balancing
task 生成 : 任意の queue ( ほとんどの場合自分の private queue) に task を enqueue
何らかの方針で, private queue の間でtask を移動• PULL : 自分の private queue にタスクがな
い場合,他の (e.g., random) スレッドのqueue から task を奪う
• PUSH : 自分の private queue の task を時折他のスレッドに移す
Taxonomy
Shared
PrivateStatic L.B.
Dynamic L.B.Lazy (PULL)
Proactive (PUSH)
Lazy Task Creation
Private queue + dynamic L.B. + lazy (PULL) load distribution
Task queue は deque ( 両端から出し入れ可能な queue) ローカル
実行
負荷分散
Lazy Task Creation の動作 task 生成 先頭に push; 直ちに実行 task 終了 先頭から pop; 次を実行 task enabled 末尾に挿入 負荷分散 末尾の要素を移動
Lazy Task Creation の効果
… … …
まともな台数効果を得るための tips (1)
tips 以前の tips (Solaris のみ )• pthread_setconcurrency( 台数 )• thr_setconcurrency( 台数 )• いずれかを用いて実際の並列度 (利用し
たい CPU 数 ) を指定• 指定しないと 1 ( いくらスレッドを作って
も無駄 )
まともな台数効果を得るための tips (2)
世の中はボトルネックだらけ• mutex lock は極力使わない
• compare & swap など, lock を用いない atomic update
• どうしても lock が必要なら, critical section を極力短く (< 数十命令 ) した上で spin lock
• 系 : libc default の malloc を使わない• もっとスケーラブルなライブラリを使う• http://www.yl.is.s.u-tokyo.ac.jp/gc/
• 系 : malloc を使ってそうなライブラリ (e.g., STL) を使わない
すでに動的負荷分散機能を持つ言語
StackThreads/MP, MTCAMP• C/C++ のライブラリ , 拡張言語• http://www.yl.is.s.u-tokyo.ac.jp/mtcamp/• http://www.yl.is.s.u-tokyo.ac.jp/sthreads/
Cilk• C の拡張言語• http://supertech.lcs.mit.edu/cilk/
KLIC• 並列論理型言語• http://www.klic.org/
MTCAMP
C の拡張情報理工の SunFire15K (istsun0) にイン
ストール済み (利用法は講義 HP に近日掲載 )
特徴 : 多数 (>> CPU 数 ) のスレッド生成を許容する
構文 3 つの拡張 : mtc_sync, mtc_fork, --- mtc_sync {
… mtc_fork [shared( 変数名 ,…)] <文 >; … ---;}
例 int fib(int n){ if (n < 2) return 1; else { int a, b; ST_POLLING(); mtc_sync { mtc_fork shared(a) a = fib(n - 1); b = fib(n - 2); ---; ST_POLLING(); return a + b; }}}