狠狠撸
Submit Search
短距离ハイブリッド并列分子动力学コードの设计思想と説明のようなもの
?
5 likes
?
5,442 views
Hiroshi Watanabe
Follow
短距离古典惭顿コードの设计で苦労したところ、妥协したところなど。
Read less
Read more
1 of 18
Download now
Downloaded 16 times
More Related Content
短距离ハイブリッド并列分子动力学コードの设计思想と説明のようなもの
1.
1/18 短距離ハイブリッド並列分子動力学コードの ? 設計思想と説明のようなもの 東大物性研 ? 渡辺宙志 2014年7月30日
2.
2/18 概要 本資料の目的 ?並列プログラム特有の「設計の難しさ」を共有したい ?説明とソースコードを公開することで「よりよいコード」 ? が生まれることを期待 ? 設計思想 ?C++で開発のしやすさと計算速度をなるべく両立
? ?外部ライブラリになるべく依存しない ? ?設計と速度がぶつかったら速度を優先 ? 「妥協の産物」なので、速度面、設計面両方で中途半端 ? 文句があるなら自分でもっと良いコード作って公開してください
3.
3/18 コードの概観 h-p://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.
4/18 コードの動作イメージ MDManager MDUnit MDUnit MDManager MDUnit MDUnit ユーザ Project::Run Variables Variables Variables Variables MDManager: ?プロセスの化身。ユーザはこのインスタンスから操作する ? MDUnit:
?スレッドの化身。計算領域を管理する。 ? Variables: ?変数の実体を管理。MDUnitのメンバになっている。 ? Project: ?このクラスのRunメソッドが事実上のmain関数。 ? ProjectManager: ?インプットファイルから適切なProjectクラスを呼び出す Input ?File ProjectManager Modeを調べる 適切なプロジェクトを実行する
5.
5/18 インプットパラメータの扱い ?(1/2) 希望 ?なるべく汎用的に使いたい ?外部ライブラリに依存しない ?インプットファイルは手で編集する ? →XMLは使わない 実装 Mode=Benchmark
? Density=0.5 ? ThermalizeLoop=150 ? UnitLength=50 ? TimeStep=0.001 ? TotalLoop=1000 ? Ini_alVelocity=0.9 インプットファイル例 ?Parameterクラス(parameter.cc/parameter.h) ? ?文字列ハッシュで管理する(std::map) ?名前=値の形式でずらずら並べる。 ?Parameterクラスが全て「文字列」として保存 ?値を取り出す時に文字列を解釈
6.
6/18 インプットパラメータの扱い ?(2/2) 1. ?実行可能ファイルにインプットファイル名を渡す 2.
?main関数でファイル名を受け取り、MDManagerクラスに渡す 3. ?MDManagerクラスが、受け取ったファイル名からParameterクラ スのインスタンスを作成 4. ?Parameterクラスのインスタンスを通じて値を受け取る 流れ 値の受け取り方 ハッシュキーを渡し、値を受け取る ? double ?Parameter::GetDouble(string ?key) ? ハッシュキーが定義されていなかった場合、valueを値とする ?(より安全) ? double ?Parameter::GetDoubleDef(string ?key, ?double ?value) 特殊キー「Mode」 インプットファイルは必ずModeを定義しなければならない ? →複数のプロジェクトの管理(後述)
7.
7/18 複数のプロジェクトの扱い(1/4) 同じ計算カーネルを、異なる研究に使いたい → ?液滴衝突、界面張力測定、気液共存線??? ? ありがちなパターン こういうのは避けたい???
? ?(あとはswitch文みたいなのもイヤ) int ? main(void){ ? ? ?//ProjectA(); ? ? ?//ProjectB(); ? ? ?ProjectC(); ? }
8.
8/18 複数のプロジェクトの扱い(2/4) ?Singletonパターンを使う ?ProjectManagerクラスをSingletonに。 ?全てのプロジェクトはProjectクラスのサブクラスに ?コンストラクタで自分をProjectManagerに登録 ? ?実体をグローバル変数として宣言 ? プロジェクトの追加→Projectクラスの実装 やりたいこと ?研究テーマごとに「プロジェクト」として管理する ?プロジェクトを追加しても、main関数を含むコードは再コンパイ ルしない ?プロジェクトは文字列で指定する 実装
9.
9/18 複数のプロジェクトの扱い(3/4) class ?Benchmark ?:
?public ?Project ?{ ? private: ? public: ? ? ?Benchmark(void) ?{ ? ? ? ? ?//ここでプロジェクトマネージャクラスに自分を追加 ? ? ? ? ?ProjectManager::GetInstance().AddProject("Benchmark", ?this); ? ? ?}; ? ? ?void ?Run(MDManager ?*mdm); ? }; ? BenchmarkクラスはProjectクラスのサブクラス ベンチマークプロジェクトの例 ? benchmark.h benchmark.cc Benchmark ?bench; ? ←ここで実体が作られコンストラクタが呼ばれる ハッシュキー
10.
10/18 複数のプロジェクトの扱い(4/4) int ? main(int ?argc,
?char ?**argv) ?{ ? ? ?setvbuf(stdout, ?NULL, ?_IOLBF, ?0); ? ? ?MDManager ?mdm(argc, ?argv); ? ? ?if ?(mdm.IsValid()) ?{ ? ? ? ? ?ProjectManager::GetInstance().ExecuteProject(&mdm); ? ? ?} ?else ?{ ? ? ? ? ?mout ?<< ?"Program ?is ?aborted." ?<< ?std::endl; ? ? ?} ? } ? main関数 ?(main.cc) プロジェクトマネージャがインプットファイルを読み込み、文字列ハッシュから ? 該当するProjectクラスのインスタンスを取得。Project::Runを実行。 void ? Benchmark::Run(MDManager ?*mdm) ?{ ? //ユーザはここを記述する ? } 事実上のmain関数 ?(benchmark.cc) Mode=Benchmark ? Density=0.5 ? ThermalizeLoop=150 ? UnitLength=50 ? TimeStep=0.001 ? TotalLoop=1000 ? Ini_alVelocity=0.9 インプットファイル ハッシュキー
11.
11/18 変数の管理 ?(1/2) 分子動力学法で必要なデータは、運動量(p)と座標(q)。 Nを粒子数、Dを次元として以下のデータを保持。 double ?p[N][D];
? double ?q[N][D]; Q. ?なぜstd::vectorを使わないの? ? A. ?遅いから 世の中にはstd::vectorと生配列で死ぬほど最適化が変わるコンパイラがあるのです??? ? 同様の理由で、カットオフ長さもコンパイル時定数に mdcon?g.hにて重要な定数を宣言 const ?int ?D ?= ?3; ? const ?int ?N ?= ?1000000; ? const ?int ?PAIRLIST_SIZE ?= ?N ?* ?50; ? const ?double ?CUTOFF_LENGTH ?= ?2.5;
12.
12/18 変数の管理 ?(2/2) 誰のメンバにするか? 案1: ?
?MDUnitクラスのメンバとする ? 案2: ?Variablesクラスのメンバとして、Variablesクラスのインスタンスを MDUnitのメンバとする ? →案2を採用。あとで観測ルーチンを作る際、Variablesクラスのインスタ ンスを渡すのが自然だと思ったから ? アクセシビリティ 原則としてVariablesクラスのメンバは隠蔽したい ? しかし、どうしても直接いじる必要が出てくる ? → ?double ?q[N][D], ?double ?p[N][D]をpublicメンバに。 ? さらにMDUnit::GetVariablesでVariablesのインスタンスにもアクセス可能 ? (要するにフルオープン) ? Variablesクラスはほぼ構造体のような使い方に
13.
13/18 力の計算 ?(1/2) 力の計算はForceCalculatorクラスが担当 ? ただし、中身は静的メソッドのみ
? (ForceCalculatorはほぼ名前空間として使用) ただ力の計算をするクラスにインスタンスを与える意義を見いだせなかったので??? ForceCalculatorクラスがやること ? ?座標の更新 ?(UpdatePosi_onHalf) ? ?運動量の更新 ?(CalculateForce) 力の計算は、内部でそれぞれの機種に最適化されたルーチンを呼び出す ForceCalculator::CalculateForceの中身 CalculateForceNext(vars, ?mesh, ?sinfo); ? ? //CalculateForceBruteforce(vars,sinfo); ? //CalculateForceSorted(vars,mesh,sinfo); ? //CalculateForcePair(vars,mesh,sinfo); ? //CalculateForceUnroll(vars,mesh,sinfo); ←Q. ?こういうの嫌じゃなかったんですか? ? A. ?コンパイル時に力計算ルーチンが確定して いないと、Inter-?‐?le ?op_miza_onが効かないこ とがあるんです。 ? 設計思想はどうしてもコンパイラの仕様とぶつかる
14.
14/18 力の計算 ?(2/2) 座標の更新コード void ? ForceCalculator::UpdatePosi_onHalf(Variables
?*vars, ?Simula_onInfo ?*sinfo) ?{ ? ? ?const ?double ?dt2 ?= ?sinfo-?‐>TimeStep ?* ?0.5; ? ? ?const ?int ?pn ?= ?vars-?‐>GetPar_cleNumber(); ? ? ?double ?(*q)[D] ?= ?vars-?‐>q; ? ? ?double ?(*p)[D] ?= ?vars-?‐>p; ? ? ?for ?(int ?i ?= ?0; ?i ?< ?pn; ?i++) ?{ ? ? ? ? ?q[i][X] ?+= ?p[i][X] ?* ?dt2; ? ? ? ? ?q[i][Y] ?+= ?p[i][Y] ?* ?dt2; ? ? ? ? ?q[i][Z] ?+= ?p[i][Z] ?* ?dt2; ? ? ?} ? } ? というセンテンスで、以後、ローカルな二次元配列っぽく使う ? Variablesはクラスというより構造体として使っている ? Simula_onInfoクラスはシミュレーション情報を管理 ? (システムサイズや時間刻みなど) ?double ?(*q)[D] ?= ?vars-?‐>q; ?
15.
15/18 標準出力の扱い 並列コードでも標準出力を使いたい ? しかし、適当に使うとすぐデッドロックする よくあるパターン ? if
?(0==rank){ ? ? ?std::cout ?<< ?Energy() ?<< ?std::endl; ? } ? Energy()が内部でAllReduceしているとデッドロックする。 std::coutのラッパ、MPIStreamを作成 ? MPIStream ?mout; ?←グローバル変数として宣言 ? mout.SetRank(rank); ?← ?MDManagerでランク番号を教えてもらう ? mout ?<< ?で受け取った内容をostringstreamに保存しておく ? std::endlを受け取った時、rank0のマスタースレッドのみ出力してクリア 関数内で通信していてもデッドロックしない mout ?<< ?Energy() ?<< ?std::endl; ? 使い方 ? ?
16.
16/18 全体処理(1/2) ?MDUnit全てに対して行いたい処理がある(初期条件作成、観測、etc...) ? ?ユーザから見えるのはMDManagerであり、MDUnitを陽に扱いたくない ? ?ユーザは知っている情報をMDUnitは知らない
?(系の大きさ、時間刻み等) ? → ?Executorクラス+ExecuteAllメソッド+Executeメソッド ? MDManagerはExecuteAllメソッドを持つ ? MDManager::ExecuteAllはExecutorクラスのインスタンスを ? 支配下のMDUnit::Executeに渡すだけ void ? MDManager::ExecuteAll(Executor ?*ex) ?{ ? ? ?#pragma ?omp ?parallel ?for ?schedule(sta_c) ? ? ?for ?(int ?i ?= ?0; ?i ?< ?num_threads; ?i++) ?{ ? ? ? ? ?mdv[i]-?‐>Execute(ex); ? ? ?} ? } ?
17.
17/18 全体処理(2/2) MDUnit::ExecuteはExecutorクラスのExecuteに自分を入れて呼び出すだけ void ?Execute(Executor ?*ex)
?{ex-?‐>Execute(this);}; mdunit.h ユーザはExecutorクラスを継承し、その中身を書く MDManager MDUnit MDUnit Executor ExecuteAll Execute Execute ユーザが定義したExecutorのインスタンスは ? MDManagerを通じてMDUnitに配布される MDUnitは系のサイズや時間刻み等を知らないが、Executorクラスの ? インスタンスを作るのはProject::Runの中なので、系のサイズなどの ? 情報を使える(コンストラクタで渡せる) Executor::ExecuteにはMDUnitのインスタンスが渡されるので、 ? それを通じてVariablesのインスタンスに好き放題できる 要するにコールバック関数
18.
18/18 まとめのようなもの 性能と拡張性?保守性の両立の難しさ ? ? プロジェクトクラスによるプロジェクト管理 ? ? 変数のアクセシビリティ、固定長配列、コメントアウトによるメソッド選択
? C++を使っているのに、結局Fortranに毛が生えたようなコードに。 ? →これはある程度やむを得なかった。コードの抽象化は ? どうしてもコンパイラの最適化まわりとぶつかる。 ? 並列化構造をなるべく隠蔽したかった。 ? ? ?Executorクラス+ExecuteAllである程度実装。 ? ? ?ちょっと複雑なことをやろうとすると、並列化構造を意識せざるを得ない。 ? どの情報を誰が持つべきか? ? ?変数はVariablesクラスに。プロセス番号などはMPIInfoクラスに、シミュレーション情 報はSimula_onInfoクラスに分けた。 ? → ?このわけかたが良いかどうか自信がない。何か指針がほしい。 ? ?外部ライブラリに極力依存しない ? 「車輪の再開発」を行うことになるが、ライブラリ依存度が強いとスパコン環境で ? とてもとても苦労する。 ?
Download