Survey: Portable High-Performance Programs の一部 2000.4.12 遠藤 敏夫 今回の内容 Portable High-Performance Programs [Frigo 99(PhD)] テーマはportability Cache-oblivious algorithms ([Frigo et al. 99], PhD Chap 3) メモリ階層に対してportable 逐次の話 Portable parallel memory (PhD Chap 4) Cilk: 並列性に対してportable Cilkの性能モデルにメモリ階層の影響を追加 Frigo Cilkグループ(MIT)の一員 FFTWで1999年Wilkinson賞を受賞 FFTW: FFTに特化したコンパイラによって高 速化 Cilkの性能モデルにメモリ階層の影響を追 加 遠藤の行く手に立ちふさがる人物 Cache-oblivious algorithms Oblivious … a. 忘れっぽい、気づかない 対義語: aware, conscious プログラムのメモリアクセスパターンの工夫により、キャッ シュミスの少ない(=速い)プログラムを作ることができる でも、プログラマに各計算機固有(アーキテクチャに aware)の最適化をやらせたくない いくつかの問題に対して、cache-obliviousかつ性能のよ いアルゴリズムを提案 行列積、転置行列、ソート、FFT キーワードはdivide and conquer 準備: 理想キャッシュモデル(1) Cache CPU Main memory Q Z H L キャッシュは1階層のみ ラインサイズL(word), ライン数H キャッシュサイズ Z = LH (word) プログラムを走らせたときのキャッシュミス数Q 準備: 理想キャッシュモデル(2) Full associative ライン置換え規則: 将来のアクセスが最後である ようなラインを追い出す 局所性を完全に利用できる Z = Ω(L2) (L2オーダー以上)を仮定 この理想的キャッシュでのミス数Qが小さいアルゴ リズムを提案 行列積(1):単純方式 C = A B A, B, C: n×n行列 単純なアルゴリズム(iterativeアルゴリズム): for (i = 0; i < n; i++) for (j = 0; j < n; j++) for (k = 0; k < n; k++) C[i,j] += A[i,k] * B[k,j] 計算量はO(n3) 行列積(2) :単純方式の評価 C A B ×n 単純アルゴリズムでのキャッシュミス数Qは? ここでは、だいたいn < Z/3 < n2として考える (一行ならキャッシュに収まるが、行列全体はムリ) C: 全体writeを1回…QC = O(n2/L) A: 各行n回readしてから次へ… QA = O(n2/L) B: 全体readをn回…QB = O(n3/L) 結局、Q = O(n3/L) アルゴリズム変更で減らせる! 行列積(3) :Blocked方式 Blockedアルゴリズム 各行列をs×s小行列に分割 s=O(√Z) n s 小行列がキャッシュに収まるような s for (i = 0; i < n/s; i++) for (j = 0; j < n/s; j++) for (k = 0; k < n/s; k++) mul(s, A, B, C, i, j, k) この中は普通にiterative 行列積(4): Blocked方式の評価 一回のmul では 計算量はO(s3)で普通 キャッシュミスQ=O(s2/L)ですむ Blockedアルゴリズム全体では 計算量はO(n3)で普通 キャッシュミスQ=O(n3/Ls)=O(n3/L√Z) (nがいくつでも成立する式はQ=O(n+n2/L+n3/L√Z) ) Qは単純アルゴリズムより√Z倍減った! しかし、sをzに依存させる必要(cache-aware) 行列積(5): Recursive方式 Recursiveアルゴリズム 行列A, B, Cを4等分しながら 再帰呼び出し if (n == 1) C += A B 再帰の終点 else A→A11, A12, A21, A22 (B, Cも同様) C11 += A11 B11; C11 += A12 B21 C12 += A11 B12; C12 += A12 B22 C21 += A21 B11; C21 += A22 B21 C22 += A21 B12; C22 += A22 B22 サイズをn/2にして 再帰呼び出し (divide&conquer!) 行列積(6): Recursive方式の評価 枝別れしていったいつかは、全行列がキャッシュ に収まるサイズになる その時のサイズはs=O(√Z)のはず 計算量、ミス数の議論はBlockedと同じ Q=O(n+n2/L+n3/L√Z) プログラム中にZ, Lに依存する変数は現れない (cache- oblivious) Blockedアルゴリズムと同等(違ったとしても定数倍)のQ 行列積(7): recursiveは速いか? 0.35 0.3 0.25 iter rec-1 rec-4 rec-32 0.2 0.15 0.1 0.05 0 0 200 400 600 (調査: 遠藤) 横軸… n, 縦軸… (実行時間 / n3) venus(Ultra SPARC 360MHz) Iterative(iter)とrecursive(rec-X)を比較 考察: iterはn>256くらいで遅くなる rec-Xはnにほぼ依存しないが、rec-1は遅すぎる 再帰を4や32で止めるとiterに勝つ Funnel sort (1) 入力 n個 マージソートの一種 多数のk-merger(図中三角)の組み合わせ 結果列(長さn) 一番右はn1/3-merger 計算量=O(n log n), Q=O((n/L)(1+log Z n)) Funnel sort (2): k-merger k-mergerの外部仕様 長さk2×k本の入力列 →長さk3の出力列 k-mergerの内部構造 √k個の√k-merger L1…L√k √k本のFIFOキュー(長さ2k3/2) 1個の√k-merger R √k本 k本 L1 L2 R Funnel sort (3): k-mergerの詳細 以下を、入力データがなくなるまで繰り返す Liのうち、バッファが半分以下しか埋まっていない mergerたちを動作させる → 全バッファが少なくとも半分埋まる Rを動作させる 最終的に、各Liはk回づつ、Rはk3/2回動作する 内部mergerに小出しにさせることにより、バッファサイ ズを小さく押さえるというのがツボらしい k-mergerのデータサイズはO(k2) (自分の出力バッファ 除く) Funnel sort (4): Qの概略 外/内方向の再帰について k-merger を1回動かしたときのミス数 QM(k) kがO(√Z)より大きい…QM(k) ≦ 2k3/2 QM(√k) + k2 kがO(√Z)以下…QM(k) = O(k3/L) 帰納法により、 QM(k)=O(k3/L+k3 log Z k/L) 左右方向の再帰について funnel sort全体のミス数Q(n) nがO(Z)より大きい…Q(n) = n1/3 Q(n2/3) + QM(n1/3) nがO(Z)以下…Q(n)=O(n/L) 帰納法により、Q(n) = O((n/L)(1+log Z n)) Funnel sort(5): 他ソートとの比較 NがZよりも大きいとき、 Funnel sort では Q(n) = O((n/L) log Z n) 2way merge sortでは Q(n) = O((n/L) log2(n/Z))多分 たとえば Z=216, n=224のとき、 log Z n : log2(n/Z) = 24/16 : 24-16 = 1.5 : 8 nが十分大きければ、log Z倍funnel sortが有利 Cache-oblivious algorithmまとめ いくつかの問題に対し、Cache-obliviousかつミス 数の少ないアルゴリズムを提案・議論 少なくともsortでは、ミス数は漸近的最適 基本方針はdivide&conquer 行列積・FFTではlower boundが知られてないらしい future work: cache-obliviousアルゴリズムが cache-awareアルゴリズムに勝てない場合はある のか? Portable parallel memory ここから後半です。 ここでの結論は、 TP(Z,L) = O((T1+μQ)/P + μHT∞) 背景: Cilk(1) 文脈: 並列言語Cilkは、 細粒度マルチスレッド機能を提供 プログラマはCPU数などを気にせず並列プログラミ ング 並列プログラムの性能モデルを提供 1CPUでの実行時間 T1と、クリティカルパス長さ T∞ から、並列実行時間を求めることができる しかし、モデルにメモリ階層の影響が入っていなか った → 本研究により追加 背景: Cilk(2) 行列積 のプログラム例(文法はいい加減) if (n == 1) C = A B else A→A11, A12, A21, A22 (B, Cも同様) spawn(C11 = A11 B11); spawn(T11 = A12 B21) spawn(C12 = A11 B12); spawn(T12 = A12 B22) spawn(C21 = A21 B11); spawn(T21 = A22 B21) spawn(C22 = A21 B12); spawn(T22 = A22 B22) sync; matrix_add(n, T, C) /* C += T を並列実行 */ 任意の場所でのfork /親によってjoin lock/unlockなし 背景: Cilk(3) タスク クリティカルパス中 のタスク 依存関係 T1: 全タスク量(1プロセッサでの実行時間) T∞: クリティカルパス長 のとき、 Pプロセッサでの平均実行時間 O(T1/P + T∞) メモリ階層を考慮した 性能モデル 分散共有メモリを仮定 Location-consistency (後述) プログラムのメモリアクセスは各メモリに対して均 等に起こると仮定 遠藤の研究では、ばらつきがある場合に対応 メモリモデル(1) 各プロセッサは以下を持つ: サイズZ=H×LのLRUキャッシュ 十分なサイズのメモリ キャッシュミス時は、自分または他プロセッサの メモリからキャッシュへコピー 平均ミス(fetch)コスト μ CPU CPU CPU CPU cache cache cache cache memory memory memory memory メモリモデル(2): location-consistency Relaxed consistency modelの一種 Cilkのスレッドスケジューラと連携 location-consistencyの定義: Cをスレッド依存グラフとしたとき、任意の location(cache line) lについてCのtopological sort Tlが 存在し、lへのreadの結果はTlにおける直前のwriteの 結果である 直観: タスクu→vという依存関係があるならuでの write結果は必ずvに見える メモリモデル(3) Location consistencyを実現するために プロセッサp上のタスクuと、q上のvに、u→v と いう依存関係があるとき、 pはu後に全cache lineをreconcile(write back) qはv前に全cache lineをreconcile, flush メモリモデルへの意見 ハードウェアDSMで実装するなら、CPUが全reconcile命 令などを備えている必要 ソフトウェアDSMだとすると、よそのmemoryから直接 cacheへ通信できるという前提が不自然 relaxed モデルを採用した理由は、不定期に起こる invalidationを避けるためと考えられる 別プロセッサ間タスク依存のあるときのみ、余分に reconcile&flush 遠藤の研究では(ほぼ)sequential consistencyである マシンを対象。その変わりプログラムがほとんど invalidationを起こさないことを仮定 記号・用語説明(1) T1: 全仕事量(ミスコストなし) T1(Z,L): 全仕事量(ミスコスト込み) μ: 一回のtransfer (fetch/reconcile)平均時間 Q: 逐次でのミス数 T∞: クリティカルパス長(ミスコストなし) T1(Z,L) = T1 +μ Q T∞(Z,L)というのは使わない TP(Z,L): Pプロセッサでの実行時間 記号・用語説明(2) Subcomputation :仕事グラフを以下の時点で区 切ったもの プロセッサが仕事を得た時点 /ひまになった時点 同期待ちを起こした時点/ 起こされた時点 並列実行時のtransferの分類 intrinsic transfer: 逐次に実行したときでも起こるもの (総数Q) extrinsic transfer: 並列実行のときのみ起こるもの 用法: あるsubcomputationがN個のextrinsic transferを引き起 こした transfer数の議論 各subcomputationに、最大3Hのextrinsic transfer が対応(Hはcache line数、H=Z/L) subcomputation開始時に最大Hのreconcile subcomputation実行中に、逐次の時より最大H余計に ミス(fetch) subcomputation終了時に最大Hのreconcile 並列時の平均キャッシュミス数は Q(Z,L) + O(HPT∞) 並列化オーバヘッドの議論 event=task steal試行+extrinsic transfer 各プロセッサが以下の仮定を満たすとき、 event回数の、全プロセッサでの合計は平均で O(HPT∞) task steal成功の間にはH回以上のtransfer task steal試行の間には1回以上のtransfer 27HP回のeventのうち、少なくとも4P回がsteal 試行 4P回steal試行があれば、1/2以上の確率でcritical pathにそって仕事が進む event回数×μが並列化オーバヘッド 並列実行時間の議論 PプロセッサでのCilkプログラム平均実行時間は TP(Z,L) = O((T1(Z,L)/P + μHT∞) = O((T1+μQ)/P + μHT∞) 各プロセッサの消費する時間の合計は、 計算+intrinsic transferに T1(Z,L) eventに O(μHPT∞) 補足: アクセス衝突 これまでの議論はtransfer時に衝突がないことを 仮定。衝突があると、transferコストはμ以上にな るかも → transfer対象がランダムで平均しているという仮定 なので、衝突コストを入れてもせいぜい定数倍、という ことを証明 意見: 確かにこの仮定が成り立つなら、定数倍という 結果は感覚的に納得できる。しかしOrigin2000のよう なマシンではその仮定が崩れうる。遠藤の研究はそこ に注目。 Portable parallel memoryまとめ Cilkの実行時間モデルにキャッシュの影響を追 加 Cilkのスレッドスケジューラ、location-consistency、アク セスがbalanced、という仮定のもとで TP(Z,L) = O((T1+μQ)/P + μHT∞) divide&conquerプログラムにおいて、使用メモリ 量の上限を解析(未survey) 自分の研究について 並列時キャッシュミス = 逐次時キャッシュミス + O(H × steal回数) というのを先にやられてしまっ た Frigoはオーダー解析をメインに行っているので、 定数部にも注目し、実験を通して予測と実測の 比較をメインにやっていきたい プログラムを並列GCに限定(invalidationがおそらくあ まり起こらない、タスクグラフが簡単)
© Copyright 2024 ExpyDoc