MPI で順序の決まっていない通
信を行うのが何故つらいか?
+Boost.MPI 問題点
東京大学
大学院新領域創成科学研究科
メディカル情報生命専攻
笠原 雅弘
背景1(情報科学の人向けに簡略化)
• 1ノードのメモリには入りきらない巨大グラフを考える。
• SSD/HDD等にグラフを追い出さずに全部オンメモリで計算できる限り
においては演算効率はそこまで必要ではないケースを考える。
• さしあたって、隣接リスト表現でグラフサイズ10TBぐらいとする。
• ノード s とRank r の対応関係はハッシュ関数 r=f(s)で決まっている。
• メモリーが足りずインデックス張れないので他に選択肢が無い。
ノード境界 ノード境界 ノード境界
Rank 0 Rank 1 Rank 2 Rank 3
背景2(情報科学の人向けに簡略化)
• Rankの数はいくつでも良いが議論を簡単にするため1024を想定。
• グラフは比較的スパースで、スケールフリーなものを想定してください。
(本当に扱いたい問題はスケールフリー性を全く満たさないが
説明がしやすいためそう仮定する。)
ノード境界 ノード境界 ノード境界
Rank 0 Rank 1 Rank 2 Rank 3
背景3(情報科学の人向けに簡略化)
• グラフ上でいろいろな種類の演算を行いたい。数学的にはとても汚い
ヒューリスティックを多用するので、特定演算のみをサポートする並列グ
ラフライブラリや並列DBは役に立たない。
ノード境界 ノード境界 ノード境界
Rank 0 Rank 1 Rank 2 Rank 3
演算の例1
• 1000個のノードペア (a_1, b_1), (a_2, b_2), …, (a_1000, b_1000) が与えられて
いる。ペア間の距離が 10000 以下かどうかをそれぞれ判定する。
• 前提条件にある通り、メモリはキツいので前処理や補助データ構造を使う
のは無理筋。また、ペア数が少ないので単純に両側から深さ 5000 まで
DFS して、積集合が空かどうか判定するのが最速だろうと考えた。
ノード境界 ノード境界 ノード境界
Rank 0 Rank 1 Rank 2 Rank 3
DFSの実装
• 始点ノードから出発してノード内は普通にDFSし、ノードをまたぐ場合に
メッセージ(探索開始ノード、残り距離、始点ノード、付帯情報)を送っ
て探索を進める。付帯情報はアプリ specific なそこそこ大きなデータ。
• 各ノードで、map<Rank内のノード, map<探索開始ノード, 探索済みかどう
かの真偽値> > を持っておけば探索の重複は防げる.
Y
X
Z
ノード境界 ノード境界 ノード境界
Rank 0 Rank 1 Rank 2 Rank 3
• 各Rankは2つのスレッドを立ち上げる。1つのスレッドではメッセージ受
け待ちループをひたすら回し、1つのスレッドで探索する。
• メッセージを受け取ったら探索スレッドにロックフリーキューで
メッセージを渡す。探索スレッドはノード境界をまたぐことが
分かったらメッセージをMPI_ISendし、
ノード内の探索を続ける。
• ノードや付帯情報がPODでない
ので Boost.MPI を使って送りたい。
(例:ノードが std::string で
表される場合。付帯情報に複雑な
オブジェクトが含まれる場合。)
• 探索開始地点だけは別処理
(自分が開始地点を持っていたら
自分にメッセージ送ればいい。)
MPIで実装する手法(案)
E
ノード境界 ノード境界
Rank x
メッセージが
来たので E から
DFS開始!
デッドロックについての当初の考察
• MPI_ISend するときに送り先バッファーが一杯のままで永遠に
送られない可能性はあるか?
• MPI_IRecv する専属スレッドが居るので starvation の心配はあるかもし
れないが、待てばいつか処理が終わるのは確実。
• MPI_Init でマルチスレッド対応している、と返事を返しつつ実
際には対応していないように見受けられる MPI 実装とかがあっ
た気がしたけど、今回は本質的には関係無いのでとりあえずこ
の問題は気にしないでおく。
• 実際には頑張ってシングルスレッド化してもみたりした。
Boost.MPI でこれを実装して死んだ理由
• Boost.MPI では一回の MPI_IRecv 呼び出しが実は二回の
MPI_IRecv になっている。
Boost.MPI で例えば vector<int> を ANY Rank から受け取ると・・・・
フェーズ1:
データのサイズを受け取って、バッファーを確保
フェーズ2:
データの中身を受け取る
ANY Rank から
メッセージを
受け取り
フェーズ1で
受け取った
Rank から
メッセージを
受け取りフェーズ1が終わった瞬間にフェーズ1のデータ
の送り元以外の Rank からメッセージが一杯来て
MPI の受信バッファーが詰まるとデッドロック
MPIやめてAPGASにしたら幸せになった(気がする)
そう断言する理由
• 実アプリでデッドロックが起きたときに、どこで止まっている
かを調べたら「フェーズ2の Recv」で止まっていた。もちろん
タイミングの問題なので動くときもあり、止まるときもあり。
• Linux/Windows の両方で確認済み。
• MPI over TCP なのが問題の可能性はある (テストは IPoIB)
• RDMA over Infiniband だとひょっとして動くかも?未検証。
• MPI実装はOpenMPI, MPICH, MVAPICH を試して全部何らかの理由で駄目
だったおぼろげな記憶がある。Linux では OpenMPI, Windows で MPICH に結
局落ち着いたが、理由は忘れてしまった(ちゃんと記録を取っておけば良
かった)。
識者への質問
• 大きなメッセージは Rendevouz Protocol で送るのが普通であろ
う。そうすると、デッドロックが起きた際には受信バッファー
が小さな envelope だけでも埋まってしまうことが示唆されるこ
とになる。Infiniband HCA 上の queue などならともかく、TCP で
やる分にはメインメモリ上に(このアプリにとっては)実用上
ほぼ無制限の envelope queue を取れそうだが、何故埋まってし
まうのか。
• もっとも、queue サイズなどのパラメータを調整することでこの問題
を解決できたとしても次スライドの問題があるので、やはり実用的で
ないことには変わりないのだが・・・。
解決案
• Recv は常に MPI_ANY_SOURCE にすれば良い
• オートマトンを組んで、サイズだけ受け取っている状態を保存できれ
ば、受信バッファーが必ずいつかは空くことが保証できる。
• Boost.MPI の Recv 関連のコードを完全に実装しなおしになるし、もはや C++ で書
きたくないコードに・・・。(Boost.Context)
• MPIはやっぱり駄目なのでMPIをやめる
• 通信パターン(受信者と送信者の組)が決まっている、もしくは多少
の順序前後しか起こらないケースを除き、MPI は不適切なプログラミ
ングパラダイムなのでは。
• ノード間タスク並列が書きやすい PGAS を使おう。
• こちらの道はこちらの道でまた別の苦労があるが、MPIの苦労よりは耐えられる
レベルだし、機会があればどこかでまた紹介する。
おまけの相談
• バイオ系のポスドク・大学院生は「 configure? make? 聞いたこ
と無いけど何ソレ?」って人がほとんどなので、MVAPICH2
を・・・とか言い始めると、tar ball の展開方法から gcc のイン
ストール方法などを教えたりで、ビルドさせるだけで半年掛
かって、ソフトウェアの著者に助けを求めるメールが殺到して
研究にならないのが目に見えている。
• MPI アプリをバイナリで配布したいんだけどどうしたらいいの・・・
• TCP上で通信する限りにおいては PGAS 系の言語はバイナリで配れるのが嬉しい。
MPI だと場合によってはキューイングシステムと合体していたりして over TCP で
もバイナリの意味が・・・。

MPI で順序の決まっていない通信をするとツラい訳