Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

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

4 600 vues

Publié le

短距離古典MDコード、主に通信まわりの設計で苦労したところ、悩んだところなど。

Publié dans : Ingénierie
  • Soyez le premier à commenter

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

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

×