狠狠撸

狠狠撸Share a Scribd company logo
1/22	
短距離ハイブリッド並列分子動力学コードの	
 ?
設計思想と説明のようなもの?並列編?	
 ?
東大物性研	
 ?
渡辺宙志	
2014年8月5日
2/22	
概要	
本資料の目的	
?並列プログラム特有の「設計の難しさ」を共有したい	
 ?
?っていうか単に「MPIの気持ち悪さ」を共有したい	
 ?
設計思想	
?クラスが肥大化しすぎないようにしたい	
 ?
?不必要なクラスを作り過ぎないようにしたい	
 ?
?なるべくややこしいこと(通信の隠蔽とか)をしない	
 ?
開発の歴史	
まず?at-?‐MPI版を作成(Ver.	
 ?1)	
 ?
その後、ハイブリッド並列版をスクラッチから作成	
 ?(Ver.	
 ?2)	
 ?
Ver.	
 ?1からVer.	
 ?2で設計思想が変化	
 ?
3/22	
コードの概観	
h6p://mdacp.sourceforge.net/	
ファイルの置き場所	
言語:C++	
 ?
ライセンス:	
 ?修正BSD	
 ?
ファイル数:50ファイル	
 ?(*.ccと*.hがほぼ半数ずつ)	
 ?
ファイル行数:	
 ?5000	
 ?lines	
 ?(ぎりぎり読める程度?)	
 ?
計算の概要	
 ?
?短距離古典分子動力学法	
 ?(カットオフ付きLJポテンシャル)	
 ?
?相互作用、カットオフ距離は全粒子で固定	
 ?
?MPI+OpenMPによるハイブリッド並列化	
 ?
 プロセス/スレッドの両方で領域分割(pseudo-?‐?at-?‐MPI)	
 ?
?アルゴリズムの解説	
 ?
Prog.	
 ?Theor.	
 ?Phys.	
 ?126	
 ?203-?‐235	
 ?(2011)	
 ?arXiv:1012.2677	
Comput.	
 ?Phys.	
 ?Commun.	
 ?184	
 ?2775-?‐2784	
 ?(2013)	
 ?arXiv:1210.3450
4/22	
MPIラッパークラス	
 ?(1/2)	
とりあえずMPIのラッパークラスは作って置きたくなる	
 ?
Communicatorクラス	
 ?(communicator.cc/.h)	
 ?
静的メソッドのみ含む、事実上の名前空間	
 ?
void	
 ?
Communicator::SendInteger(int	
 ?&number,	
 ?int	
 ?dest_rank){	
 ?
	
 ?	
 ?MPI_Send(&number,	
 ?1,	
 ?MPI_INT,	
 ?dest_rank,	
 ?0,	
 ?MPI_COMM_WORLD);	
 ?
}	
例:MPI_Sendのラッパー	
 ?
例:std::vectorをやりとりするためのラッパー	
 ?
void	
 ?
Communicator::SendRecvIntegerVector(	
 ?
	
 ?	
 ?	
 ?	
 ?std::vector<int>	
 ?&send_bu?er,	
 ?int	
 ?send_number,	
 ?int	
 ?dest_rank,	
 ?
	
 ?	
 ?	
 ?	
 ?std::vector<int>	
 ?&recv_bu?er,	
 ?int	
 ?recv_number,	
 ?int	
 ?src_rank);	
 ?
ラッパークラスの役割:	
 ?
型の明示、std::vectorの扱い、コミュニケータの隠蔽
5/22	
MPIラッパークラス	
 ?(2/2)	
MPI_InitとMPI_Finalizeの隠蔽もすぐに思いつく	
 ?
?main関数とライフタイムを共有する適当なクラス(ここではMDManager)を用意する	
 ?
?そのコンストラクタでMPI_Initを、デストラクタでMPI_Finalizeを呼び出す	
 ?
int	
 ?
main(int	
 ?argc,	
 ?char	
 ?**argv)	
 ?{	
 ?
	
 ?	
 ?MDManager	
 ?mdm(argc,	
 ?argv);	
 ?
	
 ?	
 ?if	
 ?(mdm.IsValid())	
 ?{	
 ?
	
 ?	
 ?	
 ?	
 ?ProjectManager::GetInstance().ExecuteProject(&mdm);	
 ?
	
 ?	
 ?}	
 ?else	
 ?{	
 ?
	
 ?	
 ?	
 ?	
 ?mout	
 ?<<	
 ?"Program	
 ?is	
 ?aborted."	
 ?<<	
 ?std::endl;	
 ?
	
 ?	
 ?}	
 ?
}	
←	
 ?ここでMPI_Initが呼ばれている	
 ?
←	
 ?関数を抜けるときにMPI_Finalizeが呼ばれる	
 ?
main.cc	
※	
 ?このコードは異常終了処理を考慮していない。正しく異常終了させる(=ユー
ザの都合で異常終了する際にMPI_Finalizeが呼ばれることを保証する) ために
は例外処理をするのが自然だが、手抜きにより実装していない。
6/22	
通信をどう設計するか?	
 ?(1/3)	
とりあえず単純領域分割、?at-?‐MPIのみ考える	
 ?
すると、領域更新を担当するクラスを作るのが自然	
 ?
→	
 ?ここではMDUnitと名付ける	
MDUnit	
 MDUnit	
 MDUnit	
 MDUnit	
実空間	
分割された領域それぞれをMDUnitのインスタンスが管理	
 ?
→ 通信まわりをどう設計すべきか?	
 ?
7/22	
通信をどう設計するか?	
 ?(2/3)	
案1:	
 ?MDUnit同士が行う	
 ?
MDUnit	
 MDUnit	
?「隣の領域に誰がいるか」をMDUnitが自分で知っている必要がある	
 ?
?「領域更新」という局所的な役割と、「全体把握」という大局的な	
 ?
役割の同居がとても気持ち悪い	
 ?
→	
 ??at-?‐MPI版では案1を採用	
MDUnit	
 MDUnit
8/22	
通信をどう設計するか?	
 ?(3/3)	
案2:	
 ?MDUnitを管理するMDManagerクラスを作る	
 ?
MDUnit	
 MDUnit	
 MDUnit	
 MDUnit	
MDManager	
?MDUnitは自分が全体のどこに位置するか知らない	
 ?
?通信は全てMDManagerを通して行う	
 ?
?局所的役割と大局的役割の分離	
 ?
→ ハイブリッド版では案2を採用
9/22	
MPIの気持ち悪さ	
 ?(1/3)	
ユーザ	
こういう動作を期待	
こいつらだけが	
 ?
並列動作する	
MDManager	
MDUnit	
MDUnit	
MDUnit	
MDUnit	
こいつが管理	
すくなくともこういうイメージでMDManagerを作った
10/22	
MPIの気持ち悪さ	
 ?(2/3)	
実際にはこうなってる	
MDManager	
 MDUnit	
MDManager	
 MDUnit	
MDManager	
 MDUnit	
MDManager	
 MDUnit	
こいつらみんな	
 ?
並列動作する	
並列動作するインスタンスを管理する「ただひとつの管理インス
タンス」が存在しない	
 ?
→このようにクラスを分ける意味はあったのだろうか?	
 ?
ユーザ	
プロセス数に関係なく「ユーザから見てただひとつのインスタンスに	
 ?
見える」オブジェクトがあれば、少なくとも設計はスッキリする?	
※	
 ?ハイブリッド版では、一つのMDManager(プロセス)が複数のMDUnit(スレッド)を管理す
るという意味もあるが???
11/22	
MPIの気持ち悪さ	
 ?(3/3)	
MDManager	
通信はMDManagerを通してのみ行いたい	
MDUnit	
 MDUnit	
MDManager	
しかし実際には、ソースのどこからでもどこへでもMPI通信できる	
→	
 ?MPIには本質的に「スコープ」が存在しない	
MDUnitに隣接する領域のランクを教えないことで	
 ?
擬似的に「スコープ」を導入
12/22	
どの情報を誰が管理すべきか	
 ?(1/2)	
MPIでは、ノードをまたぐ通信量をなるべく減らすように	
 ?
プロセスを配置する	
 ?
0	
 1	
 4	
 5	
2	
 3	
 6	
 7	
8	
 9	
 11	
 12	
10	
 11	
 13	
 14	
ハイブリッドだとさらにややこしくなる。	
 ?
→	
 ?どの領域に誰がいるかの「地図」の管理が必要	
1ノード4プロセス、4ノード計算のプロセス配置例
13/22	
どの情報を誰が管理すべきか	
 ?(2/2)	
案1:	
 ?MPIInfoクラスを作って、そこで地図を管理	
 ?
	
 ?	
 ?	
 ?	
 ?	
 ?	
 ?	
 ?	
 ?	
 ?通信するクラスがMPIInfoクラスのインスタンスを持つ	
案2:	
 ?MDManagerクラスが地図を直接管理してしまう	
?at-?‐MPIコードの開発では案1を採用したが、	
 ?
ハイブリッドコードの開発では案2を採用	
 ?
ハイブリッドコードでは、MDManagerのコンストラクタ、デストラクタで
MPI_Init/Finalizeを呼び出しており、MPI関連の情報を分離できていないこと、
及び分離することのメリットがあまりないことによる
14/22	
通信まわりの実装	
 ?(1/4)	
アルゴリズム	
?相互作用距離よりも遠い粒子をペアリストに登録し、
しばらくリストを使いまわす(Bookkeeping法)	
 ?
?端にある粒子の座標のみ通信(短距離相互作用)	
 ?
?もらった粒子をさらに転送することで、斜め方向の通
信を省く(詳細は論文参照)。	
考えるべきこと	
?自分の粒子と他から借りている粒子をどうやって
区別するか	
?送られてくる粒子情報が「どこから来た」か保存す
べきか
15/22	
通信まわりの実装	
 ?(2/4)	
自分の粒子と他から借りている粒子の区別	
 ?
→	
 ?配列を共有、粒子数を2つ用意した	
データ配列	
自分が管理する粒子	
 送られて来た粒子	
ParocleNumber	
 ?(PN)	
TotalParocleNumber	
 ?(TPN)	
※この名前は良くなかった	
実空間
16/22	
通信まわりの実装	
 ?(3/4)	
送られてくる粒子情報が「どこから来た」か保存すべきか	
 ?
→「どこへ何を送るか」を覚えることで不要に	
一番最初に送るときに「誰にどの粒子を送るか」をテーブルに保存。	
 ?
また、誰から何粒子もらうかも記憶しておく(MPI_Sendrecvの引数で必要だから)。	
 ?
あとは同じ順番で送れば、同じ場所に同じ粒子の座標が送られてくるはず	
PN	
TPN	
1.	
 ?通信前にTPNをPNに合わせる	
2.	
 ?右から粒子をもらい、その数だけTPNをずらす	
 PN	
TPN	
3.	
 ?以上の手続きを左、前後、上下で繰り返す。
17/22	
通信まわりの実装	
 ?(4/4)	
自分の粒子と他から借りている粒子の区別	
 ?
→二体関数以上の計算で必要	
ポテンシャルエネルギーや圧力など、二体の関数について、そのまま計算する
と、重複する分だけダブルカウントしてしまう。	
 ?
→「自分が管理する粒子」と「借りた粒子」の寄与は半分にする。	
 ?
→「借りた粒子同士の寄与」は無視する	
 ?
粒子番号のチェックだけでできる(原始的?)	
三体以上の相互作用がある場合はどうするんだろう?
18/22	
main関数の引数を誰が受け取るか(1/4)	
?MPI情報管理クラス:	
 ?MPI_Initはargc,	
 ?argvを要求	
 ?
?パラメータクラス:	
 ?ファイル名の取得にargvが必要	
main関数の引数を要求するクラスが、少なくとも2つある	
誰がどうやって受け取るべきか?
19/22	
main関数の引数を誰が受け取るか(2/4)	
案1:	
 ?argc,	
 ?argvをMPI管理クラス(MPIInfo)とパラメータ管理
クラス(Parameterクラス)それぞれに渡し、それらのポイン
タを管理クラスに渡す。	
int	
 ?
main(int	
 ?argc,	
 ?char	
 ?*argv[]){	
 ?
	
 ?	
 ?MPIInfo	
 ?minfo(argc,	
 ?argv);	
 ?
	
 ?	
 ?Parameter	
 ?param(argc,	
 ?argv);	
 ?
	
 ?	
 ?MDUnit	
 ?mdu(&minfo,	
 ?&param);	
 ?
	
 ?	
 ?//なにか処理	
 ?
}	
?at-?‐MPI版コードではこちらを採用	
設計的にはこれがまっとうな気もする。	
 ?
20/22	
main関数の引数を誰が受け取るか(3/4)	
案2:	
 ?MDManagerにのargc,	
 ?argvを渡し、コンストラクタで
MPI_Initの処理やParameterのインスタンスを作る	
int	
 ?
main(int	
 ?argc,	
 ?char	
 ?*argv[]){	
 ?
	
 ?	
 ?MDManager	
 ?mdm(argc,	
 ?argv);	
 ?
}	
 ?
MDManager::MDManager(int	
 ?&argc,	
 ?char	
 ?**	
 ?&argv)	
 ?{	
 ?
	
 ?	
 ?MPI_Init(&argc,	
 ?&argv);	
 ?
	
 ?	
 ?MPI_Comm_size(MPI_COMM_WORLD,	
 ?&num_procs);	
 ?
	
 ?	
 ?MPI_Comm_rank(MPI_COMM_WORLD,	
 ?&rank);	
 ?
	
 ?	
 ?std::string	
 ?inpurile;	
 ?
	
 ?	
 ?if	
 ?(argc	
 ?>	
 ?1)	
 ?{	
 ?
	
 ?	
 ?	
 ?	
 ?inpurile	
 ?=	
 ?argv[1];	
 ?
	
 ?	
 ?}	
 ?else	
 ?{	
 ?
	
 ?	
 ?	
 ?	
 ?mout	
 ?<<	
 ?"#	
 ?Input	
 ??le	
 ?is	
 ?not	
 ?speci?ed.	
 ?input.cfg	
 ?is	
 ?used."	
 ?<<	
 ?std::endl;	
 ?
	
 ?	
 ?	
 ?	
 ?inpurile	
 ?=	
 ?"input.cfg";	
 ?
	
 ?	
 ?}	
 ?
	
 ?	
 ?param.LoadFromFile(inpurile.c_str());	
 ?	
 ?
}	
 ?
ハイブリッド版コードではこちらを採用
21/22	
main関数の引数を誰が受け取るか(4/4)	
Q.	
 ?なぜ全てMDManagerに詰め込んだのですか?	
 ?
分けたほうが設計がきれいだと思いますが?	
A.	
 ?分けるご利益があまりないと考えたから	
その他雑多な感想	
 ?
?MPIInfo、Parameterクラスのインスタンスは、どちらもMDManagerのメンバに
なっており、ライフタイムを共有している。MDManagerとライフタイムを共有する
クラスのインスタンスを外で作って渡す、というのがどうにも気持ち悪かった。	
 ?
?プロセスの化身であるMDManagerが、自分のランクを自分で知らない、という
のが気持ち悪い気がした。「プロセスの化身」は誰か?MDManagerか?
MPIInfoか?	
 ?
?main関数はなるべく簡素化したい(これは単に趣味)。
22/22	
まとめのようなもの	
「相互作用が全て同一」という条件を最大限に利用した設計	
 ?
?粒子番号しかチェックしなくて済むのでシンプル。	
 ?
?粒子番号に意味を付与するのは拡張性に欠ける。 いずれ追加情報の管理が
必要になりそう。	
 ?
→	
 ?通信まわりを最適化してしまうと、相互作用の詳細に強く依存し、毎回作りな
おしに近くなる? 通信をもう少し抽象的に扱いたい。	
 ?
通信の隠蔽を考慮していない	
 ?
?	
 ?原則としてMPI_Sendrecvしか使わないのでシンプル。デバッグが楽。	
 ?
?	
 ?計算が比較的重いからできたこと。強スケーリングを追求すると破綻。	
 ?
MPI、というかSPMDという設計思想に慣れるのに時間がかかった。	
 ?
SPMDは「通信に関わる全体的な視点」と「送受信に関わるプロセスの局所的な
視点」の両方同時に要求する。「慣れろ」と言われればそれまでだが???	
 ?
C++の言語仕様そのものに起因する問題で、設計にわりと苦しんだ	
 ?
っていうかC++はダメだと思う。GCのない言語で参照渡しの多用はいろいろ問題
がある。	
 ?

More Related Content

短距離ハイブリッド並列分子動力学コードの設計思想と説明のようなもの ?並列編?