これはおそらく MPICH/p4 の TCP/IP の利用のしかたに問題があるのであろう とは思われるのですが、ここでは MPICH をなんとかする代わりに直接ソケッ トシステムコールを使うより低レベルな通信ライブラリを実装することにしま す。そのようにする理由は、基本的には MPI は不必要に面倒くさいからです。
例えば、単純な send/receive の関数が、 MPI ではそれぞれ 6 個とか 7 個 の引数を取ります。 そのほとんどは、あんまり意味がないものです。さらに、 MPI の仕様には無限に沢山の関数が規定されていますが、上の send/receive 以外のものを使うことは滅多にありません。 そうい うわけで、 MPI のコールを改良するよりは、プログラムを書く枠組みとして は MPI を利用するのですがいくつかの実際に使う関数についてはもうちょっ と便利で速いものをソケットを使って作っておこうというのが tcplib.c を作っ た動機です。
いうまでもないと思いますがこのライブラリは「無保証」であり、このライブ ラリを使った結果起きたいかなる問題についても作者は責任を負うものではあ りません。このライブラリの著作権は作者である牧野に属しますが、この ライブラリを利用する人はこれを好きなように改変し、再配布してもかまいま せん。 GPL や LGPL というわけでもありません。バグ等の報告は makino@astrogrape.org まで。
このライブラリは MPICH/p4 over TCP/IP と併用することが前提になっていますので、まず MPICH が使える環境にして下さい。 MPICH は 1.2.2 でテストしています。 tcplib.tgz をダウンロード、展開すると、 tcplib というディレクトリができます。そこで
make mpiperftestを実行して、実行ファイルが正常にできたら、
mpirun -np 2 mpiperftestと実行してみます。 以下のような出力がでればとりあえず使えるものと思われます。
mpirun -np 2 mpiperftest Process 0 of 2 on g6host24 Myid, R, L= 0 1 1 Myid, R, L= 1 0 0 receivebuf original size = 87380 4 receivebuf new size = 262142 sendbuf original size = 16384 4 sendbuf new size = 65536 TCP NODELAY = 0 4 New TCP NODELAY = 1 4 Barrier count = 2000 wall clock time = 0.634774 317.387000 us/synch Allreduce count = 2000 wall clock time = 0.832985 416.492500 us/call TCP Barrier level 2 count = 2000 wall clock time = 0.401075 200.537500 us/synch TCP Allreduce count = 2000 wall clock time = 0.399945 199.972500 us/call size, count = 1 2000 wall clock time = 0.402044 0.079593 MB/s size, count = 1 2000 wall clock time = 0.800342 0.039983 MB/s size, count = 4 2000 wall clock time = 0.705533 0.181423 MB/s size, count = 4 2000 wall clock time = 0.821402 0.155831 MB/s size, count = 16 2000 wall clock time = 0.402461 1.272173 MB/s size, count = 16 2000 wall clock time = 0.804487 0.636430 MB/s size, count = 64 2000 wall clock time = 0.516256 3.967024 MB/s size, count = 64 2000 wall clock time = 0.800336 2.558925 MB/s size, count = 256 2000 wall clock time = 0.894697 9.156172 MB/s size, count = 256 2000 wall clock time = 1.599854 5.120467 MB/s size, count = 1024 2000 wall clock time = 4.715987 6.948280 MB/s size, count = 1024 2000 wall clock time = 1.598627 20.497589 MB/s size, count = 4096 1464 wall clock time = 14.288063 6.715025 MB/s size, count = 4096 1464 wall clock time = 2.292835 41.845446 MB/s size, count = 16384 366 wall clock time = 15.780060 6.080123 MB/s size, count = 16384 366 wall clock time = 1.720875 55.753442 MB/s size, count = 65536 91 wall clock time = 17.563468 5.432891 MB/s size, count = 65536 91 wall clock time = 1.532701 62.256380 MB/s size, count = 262144 22 wall clock time = 18.364200 5.024705 MB/s size, count = 262144 22 wall clock time = 1.441711 64.003596 MB/s size, count = 1048576 5 wall clock time = 13.327071 6.294412 MB/s size, count = 1048576 5 wall clock time = 1.302986 64.379878 MB/s size, count = 4194304 1 wall clock time = 10.723848 6.257909 MB/s size, count = 4194304 1 wall clock time = 1.035049 64.836413 MB/s Process 1 of 2 on g6host25出力結果はみての通りですが、最初に MPI_Barrier を 2000 回、 MPI_AllReduce も 2000 回呼んで一回当りの時間をだします。次に TCPLIB での対応する関数を呼び、同様に時間をだします。上の例では特に AllReduce ではほぼ 1/2 の時間で終わっているのがわかります。
次にメッセージ長を 8 バイト実数で 1, 4, .... 4M まで変えて、適当な回数 MPI_SendRecv と TCPLIB の対応する関数を呼んで時間を測ります。サイズが同じものが 2 行続いて、上のが MPI、下が TCPLIB です。
メッセージが短いと MPIのほうが速いですが、 長いと TCPLIB が圧倒的に 速くなります。 これは Linux kernel 2.4.17、NIC は NS83820 のもの、CPU, MB は Athlon XP 1800+ に ECS K7S6A (SiS 745 chipset) という組合せの場 合で、ここまで大きな差がでることは稀かもしれません。NIC に NetGear GA620T を使った場合には、 MPI でも 40MB/s 程度は出たような記憶(すみま せん、データが今はないので) があります。なお、 TCPLIB では 80 MB/s く らいになりました。
int tcp_request_full_MPI_connection()TCPLIB の初期設定を行ないます。具体的には、各 MPI ノードが他のすべての ノードとのソケット接続を確立します。このため、この関数は全ノードで SIMD 的に呼ばれる必要があります。つまり、 MPI_Allxxx な関数と同じよう に、全てのノードがこの関数をよばないと次に進まないようになっています。
また、この関数は MPI のいろんな機能を使うので、これが呼ばれる前に MPI_Init が呼ばれて MPI の初期化が終わっている必要があります。
なお、 MPI_Finalize にあたるような TCPLIB の利用を終了する関数はいまの ところ準備されていません。プログラムが終了するまでソケットは解放されま せん。
実際に使うかどうかとは無関係に全部のノード間のソケット接続を作るのはも ちろん無駄で、100 を超えるようなノード数であれば見直す必要があると考え られます。手元にそんな巨大なクラスタがないので現在のところ無駄を放置し てあります。16ノード程度であれば特に問題はないようです。
接続に失敗したら 0でないエラーコードを返すというのが仕様ですが、現在の 実装ではエラーが起きると内部で異常終了するのでエラーは戻らないです。
int tcp_transfer_data_by_MPIname(int othermpiid, int direction, int length, void* message_buffer)1対1通信を行ないます。
othermpiid: 通信相手を指定します。 MPI ノード番号を与えます。
direction: TCPLIB_SEND (送信)または TCPLIB_RECV(受信)を指定します。
length: バイト単位でメッセージ長を指定します。 MPI_Recv とは異なり、
length で指定しただけのメッセージを受け取るまで待つので、受け取る側は
事前に受け取るメッセージ長を確実に知っている必要があります。
message_buffer: 送る側ではメッセージが入っている領域の先頭、受け取る側
ではメッセージが入る領域の先頭になります。
エラーコードは帰らないので無視して下さい。
int tcp_sendreceive_data_by_MPIname(int target_id, int nsend, void* send_buffer, int source_id, int nreceive, void * receive_buffer);MPI_SendRecv と同じく、双方向の転送を行ないます。メッセージ長はバイトで指定します。 受け取る側は 事前に受け取るメッセージ長を確実に知っている必要があります。
int tcp_simd_transfer_data_by_MPIname(int target_id, int *sendparms, int nparms, void* send_buffer, int source_id, int * receiveparms, void * receive_buffer)MPI_SendRecv と同じく、双方向の転送を行ないます。 sendparms の最初の要素にメッセージ長を入れます。メッセージ長はバイトで指定します。2番目以降の要素は付加 的な情報を送るのに使うことが出来ます。特にメッセージ本体以外に付加的な 情報がなければ nparms に 1 を指定すればいいことになります。 nparms の 値は、受ける側と送る側で同じでないといけないことに注意して下さい。
この関数では、受けとる側は長さを指定しません。従って、バッファサイズが不足しないことの保証は別に行う必要があります。
この関数はソケットシステムコールを非同期で呼び出し、最高の性能を出すよ うにチューニングされています。
void tcp_barrier(int level)MPI_Barrier と同様なバリア関数ですが、ノード数が 2 のべき乗でないと正しく動 作しません。 level にはべきの数字(例えば 16 ノードなら 4 )をあたえます。
double tcp_allmax(double myvalue)MPI_AllReduce と同様な関数ですが、1つの実数の最大値をあたえます。 ノード数が 2 のべき乗でないと正しく動作しません。
2002/8/27 バグ、制限事項のセクションを追加。ポート番号、ソケットのパラメータ設定の項目を記述。
2002/8/27 実行例に説明を付ける。