tcplib.c 利用の手引

Copyright 2002 牧野淳一郎

はじめに

tcplib.c の目的は、通常の TCP/IP ネットワーク環境で動作している MPI プ ログラムに対して、MPICH/p4 よりもオーバーヘッドが小さい(こともある)低 レベル通信関数を提供することです。 MPICH/p4 を使った通信では、TCP/IP ソケットを使った通信に比較して、レイテンシもスループットもかなり悪くな ることがあります。特に Gigabit Ethernet を使った場合には、現在のところ TCP/IP を使った場合に比べて数分の1にまで性能が落ちることも珍しくあり ません。

これはおそらく 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 く らいになりました。

関数群の説明 --- MPI program API

tcplib が提供する関数群は、 の2つからなります。ここでは前者を MPI program API、後者を low level API と呼ぶことにします。このセクションではまず MPI program API につい て説明します。
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 のべき乗でないと正しく動作しません。

low level API

そのうちに書きます。すみません。

Compile and Link

ヘッダファイルとして tcplib.h が準備されています。 tcplib.c を適当なオプションでコンパイル・リンクして使って下さい。 MPI の中で使う時には -DMPIMODE を指定してコンパイルして下さい。

サンプルプログラム

mpiperftest.c をご覧下さい。これはサンプルというわけではなく、単に意味のないメッセージを送って速度を測るプログラムです。

バグ、制限事項など

ポート番号

現在の実装はポート番号きめうちで、そのポートを占有するので、1 CPU (というか、カーネル)につき1つしかこれを使うプログラムは動きません。これは割合不便なのでそのうちにダイナミックに使えるポートを捜すような仕掛けをつけたいと思ってはいます。

ソケットのパラメータ設定

バッファサイズとかいろいろ変更してますが、処理が怪しいです。

終わりに

このライブラリは、東京大学理学系(あれ、情報理工でしたっけ?)の稲葉真理様とのメイルでの議論と、情報理工学研究科(当時)の下見淳一郎様からいただいたプログラムが元になって発生したものです。

変更記録

2002/8/27 変更記録をつけることにする。

2002/8/27 バグ、制限事項のセクションを追加。ポート番号、ソケットのパラメータ設定の項目を記述。

2002/8/27 実行例に説明を付ける。